mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge origin/main into PR 8134
Co-authored-by: Muhammad Wasif Ali Wasif <[email protected]>
This commit is contained in:
commit
490adefc09
338 files changed
+13805
-1584
No files matched your search
@@ -692,6 +692,15 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_fragment_barycentric COMMAND agc_fragment_barycentric_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_float32_flush_control_tests EXCLUDE_FROM_ALL tests/Float32FlushControl.cpp)
|
||||
target_include_directories(agc_float32_flush_control_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
set_target_properties(agc_float32_flush_control_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_float32_flush_control_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_float32_flush_control_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_float32_flush_control COMMAND agc_float32_flush_control_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_pipeline_link_optimization_tests EXCLUDE_FROM_ALL tests/PipelineLinkOptimization.cpp)
|
||||
target_include_directories(agc_pipeline_link_optimization_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
set_target_properties(agc_pipeline_link_optimization_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
@@ -811,6 +820,19 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_f32_denorm_preserve_tests EXCLUDE_FROM_ALL tests/F32DenormPreserve.cpp)
|
||||
target_link_libraries(agc_f32_denorm_preserve_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_f32_denorm_preserve_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_f32_denorm_preserve_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_f32_denorm_preserve_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_f32_denorm_preserve COMMAND agc_f32_denorm_preserve_tests)
|
||||
set_tests_properties(agc_f32_denorm_preserve PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_f32_denorm_preserve PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_spirv_branch_targets_tests EXCLUDE_FROM_ALL tests/SpirvBranchTargets.cpp)
|
||||
target_link_libraries(agc_spirv_branch_targets_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_spirv_branch_targets_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
@@ -981,6 +1003,10 @@ add_test_executable(agc_vop1_control_decode_tests EXCLUDE_FROM_ALL tests/Vop1Con
|
||||
target_link_libraries(agc_vop1_control_decode_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_vop1_control_decode_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_vop1_control_decode_tests)
|
||||
add_test_executable(agc_vertex_input_validation_tests EXCLUDE_FROM_ALL tests/VertexInputValidation.cpp)
|
||||
target_link_libraries(agc_vertex_input_validation_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_vertex_input_validation_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_vertex_input_validation_tests)
|
||||
add_test_executable(agc_vop2_encoding_decode_tests EXCLUDE_FROM_ALL tests/Vop2EncodingDecode.cpp)
|
||||
target_link_libraries(agc_vop2_encoding_decode_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_vop2_encoding_decode_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
@@ -1389,6 +1415,11 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_unchanged_cpu_stamps PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test(NAME agc_partial_watch_restoration COMMAND agc_driver_recorder_tests --partial-watch-restoration-only)
|
||||
set_tests_properties(agc_partial_watch_restoration PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_storage_kept_unit_settle COMMAND agc_driver_recorder_tests --kept-unit-settle-only)
|
||||
set_tests_properties(agc_storage_kept_unit_settle PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
|
||||
if(WIN32)
|
||||
@@ -1396,6 +1427,12 @@ if(BUILD_TESTING)
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_storage_baseline_settled COMMAND agc_driver_recorder_tests --settled-baseline-only)
|
||||
add_test(NAME agc_storage_clear_hand_over COMMAND agc_driver_recorder_tests --clear-hand-over-only)
|
||||
set_tests_properties(agc_storage_clear_hand_over PROPERTIES SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_storage_clear_hand_over PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_storage_integer_clear COMMAND agc_driver_recorder_tests --integer-clear-only)
|
||||
set_tests_properties(agc_storage_integer_clear PROPERTIES SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
@@ -1587,6 +1624,7 @@ target_include_directories(agc_host_import_retire_tests PRIVATE ${LIBS_INCLUDE_D
|
||||
target_link_libraries(agc_host_import_retire_tests PRIVATE libSceAgcDriver libc)
|
||||
set_target_properties(agc_host_import_retire_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_host_import_retire_tests)
|
||||
target_sources(agc_host_import_retire_tests PRIVATE tests/HostImportFailure.cpp)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_host_import_retire_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_host_import_retire COMMAND agc_host_import_retire_tests)
|
||||
@@ -1796,6 +1834,7 @@ add_test_executable(agc_driver_bda_device_tests EXCLUDE_FROM_ALL
|
||||
tests/BdaShader.cpp
|
||||
tests/BdaExecution.cpp
|
||||
tests/BdaContracts.cpp
|
||||
tests/HostImportFailure.cpp
|
||||
)
|
||||
|
||||
target_include_directories(agc_driver_bda_device_tests PRIVATE
|
||||
@@ -1807,6 +1846,15 @@ target_include_directories(agc_driver_bda_device_tests PRIVATE
|
||||
target_link_libraries(agc_driver_bda_device_tests PRIVATE libSceAgcDriver SDL2-static libc)
|
||||
set_target_properties(agc_driver_bda_device_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_bda_device_tests)
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME agc_driver_host_import_failure_handles COMMAND agc_driver_bda_device_tests --host-import-failures)
|
||||
set_tests_properties(agc_driver_host_import_failure_handles 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_host_import_failure_handles PROPERTIES
|
||||
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_driver_mesh_tests EXCLUDE_FROM_ALL tests/Mesh.cpp)
|
||||
target_include_directories(agc_driver_mesh_tests PRIVATE
|
||||
@@ -1854,6 +1902,14 @@ foreach(agcExecutionTestName IN LISTS ANYPS5_AGC_EXECUTION_TESTS)
|
||||
message(FATAL_ERROR "ANYPS5_AGC_EXECUTION_TESTS names ${agcExecutionTestName}, which has no source in tests/execution")
|
||||
endif()
|
||||
endforeach()
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME agc_driver_indirect_thread_dimensions_cpu COMMAND agc_driver_indirect_thread_dimensions_tests)
|
||||
set_tests_properties(agc_driver_indirect_thread_dimensions_cpu PROPERTIES ENVIRONMENT "APS5_NO_GPU_THREAD_DIMENSIONS=1" SKIP_RETURN_CODE 77 TIMEOUT 60)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_indirect_thread_dimensions_cpu PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
if(ANYPS5_ENABLE_SPIRV_TOOLS)
|
||||
target_compile_definitions(agc_driver_stencil_null_feedback_tests PRIVATE ANYPS5_ENABLE_SPIRV_TOOLS=1)
|
||||
target_link_libraries(agc_driver_stencil_null_feedback_tests PRIVATE SPIRV-Tools-static)
|
||||
@@ -2091,7 +2147,7 @@ if(BUILD_TESTING)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_bindless agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
foreach(agcTest IN ITEMS agc_vertex_input_tests agc_mesh_output_budget_tests agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_screenshot_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests)
|
||||
foreach(agcTest IN ITEMS agc_vertex_input_tests agc_mesh_output_budget_tests agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vertex_input_validation_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_barrier_batch_tests agc_shader_disk_cache_tests agc_screenshot_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests} agc_fma_mix_once_tests agc_f16_single_convert_tests)
|
||||
if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests")
|
||||
continue()
|
||||
endif()
|
||||
@@ -2151,6 +2207,27 @@ if(BUILD_TESTING)
|
||||
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()
|
||||
if(Python3_Interpreter_FOUND)
|
||||
set(prebuiltDiscoveryModes plain relative plain-long)
|
||||
if(WIN32)
|
||||
list(APPEND prebuiltDiscoveryModes extended extended-forward device extended-long)
|
||||
endif()
|
||||
foreach(prebuiltDiscoveryMode IN LISTS prebuiltDiscoveryModes)
|
||||
set(prebuiltDiscoveryTest agc_shader_prebuilt_discovery_${prebuiltDiscoveryMode})
|
||||
add_test(NAME ${prebuiltDiscoveryTest} COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_shader_cache_prebuilt_discovery.py
|
||||
$<TARGET_FILE:agc_shader_disk_cache_tests> ${prebuiltDiscoveryMode})
|
||||
set_tests_properties(${prebuiltDiscoveryTest} PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(${prebuiltDiscoveryTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
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
|
||||
@@ -2408,9 +2485,11 @@ if(BUILD_TESTING)
|
||||
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
|
||||
@@ -2472,8 +2551,10 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_driver_draw_cache_reuse_inline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0;APS5_VERIFY_DRAW_RECIPE=1;APS5_VERIFY_REGISTER_KEYS=1" SKIP_RETURN_CODE 77)
|
||||
set_tests_properties(agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline PROPERTIES FAIL_REGULAR_EXPRESSION "FATAL:")
|
||||
set_property(TEST agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off APPEND PROPERTY ENVIRONMENT "APS5_VERIFY_DRAW_RECIPE=1" "APS5_VERIFY_REGISTER_KEYS=1")
|
||||
add_test(NAME agc_driver_draw_data_hit_user_words_verify COMMAND agc_driver_draw_data_hit_user_words_tests)
|
||||
set_tests_properties(agc_driver_draw_data_hit_user_words_verify PROPERTIES ENVIRONMENT "APS5_VERIFY_DRAW_DATA_HITS=1" SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(agc_driver_draw_cache_reuse_off agc_driver_draw_cache_reuse_verify agc_driver_draw_cache_reuse_inline agc_driver_draw_data_hit_user_words_verify PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ struct DispatchVariant {
|
||||
std::vector<std::int8_t> runBases;
|
||||
std::uint64_t relocatableWords = 0;
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> shaderDataPatches;
|
||||
std::uint64_t userDataMask = ~std::uint64_t{0};
|
||||
std::shared_ptr<const ShaderRecompiler::ShaderVertexStageInfo> vertexInfo;
|
||||
|
||||
std::shared_ptr<ShaderMemory> memory;
|
||||
|
||||
@@ -126,6 +126,7 @@ struct StageCapture {
|
||||
std::uint64_t forgetSerial = 0;
|
||||
std::uint32_t pushOffset = 0;
|
||||
ShaderRecompiler::SrtUserDataUse userDataUse;
|
||||
std::uint64_t userDataMask = ~std::uint64_t{0};
|
||||
};
|
||||
|
||||
struct RelocatableUserWord {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_FLOAT32FLUSHCONTROL_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_FLOAT32FLUSHCONTROL_HPP
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
struct Float32FlushControl {
|
||||
bool probe = false;
|
||||
bool declared = false;
|
||||
};
|
||||
|
||||
inline Float32FlushControl Float32FlushControlFor(VkDriverId driver, VkBool32 flushToZero, VkShaderFloatControlsIndependence independence) {
|
||||
const bool declared = flushToZero == VK_TRUE && independence != VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE;
|
||||
return {declared || driver == VK_DRIVER_ID_MOLTENVK, declared};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
// Starts the journal replay if the device has not (a headless device starts it at its first
|
||||
// draw or dispatch) and joins it (tests; the teardown joins it anyway).
|
||||
void WaitForPipelineJournalReplay();
|
||||
void SavePipelineCache();
|
||||
// Compute pipelines this device built, by dispatches and by the journal replay (tests).
|
||||
std::uint64_t ComputePipelinesCreated() const;
|
||||
~VulkanDevice();
|
||||
@@ -274,7 +276,7 @@ public:
|
||||
int cpuReason;
|
||||
double argumentReadMs;
|
||||
};
|
||||
IndirectOutcome DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr<PreparedDispatch> prepared = nullptr, std::shared_ptr<const Recipe>* recipe = nullptr);
|
||||
IndirectOutcome DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr<PreparedDispatch> prepared = nullptr, std::shared_ptr<const Recipe>* recipe = nullptr, const std::array<std::uint32_t, 3>& threadGroup = {});
|
||||
// A value-equal dispatch-cache hit with a recipe (design_cpu_final M4), in two steps.
|
||||
// PrepareRecipe, WITHOUT GuestMemory::GpuMutex: the pre-checks (recipes on, the recipe's
|
||||
// device is this one, its template and pipeline objects still alive), the template's
|
||||
@@ -296,7 +298,7 @@ public:
|
||||
// with the recipe's, else the process aborts. `refreshByWords`: the driver's data-only hit
|
||||
// (`shader` carries live flat-SRT words the recipe's hash does not name), so the template's
|
||||
// data refresh is decided by the per-word compare instead of the hash.
|
||||
RecipeOutcome DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify = nullptr, bool refreshByWords = false);
|
||||
RecipeOutcome DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify = nullptr, bool refreshByWords = false, const std::array<std::uint32_t, 3>& threadGroup = {});
|
||||
// Whether dispatch-cache hits use recipes (APS5_NO_DISPATCH_RECIPE unset) and whether every
|
||||
// hit is verified (APS5_VERIFY_RECIPE=1).
|
||||
static bool DispatchRecipes();
|
||||
@@ -352,7 +354,7 @@ private:
|
||||
ShaderRecompiler::SpirvTarget buildTarget() const;
|
||||
Graphics::Context buildContext() const;
|
||||
// Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups.
|
||||
IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe);
|
||||
IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe, const std::array<std::uint32_t, 3>& threadGroup = {});
|
||||
// The stage-A pre-sync over `surfaces` (see PrepareDispatch): the serial waited for, 0 none.
|
||||
std::uint64_t presync(std::span<const Graphics::PresyncSurface> surfaces);
|
||||
std::shared_ptr<ComputePipelineObjects> computePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span<const std::uint32_t> layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace AgcDriver {
|
||||
|
||||
// Starts sampling the calling thread when APS5_SAMPLE_WORKER names an output file.
|
||||
void StartWorkerSampler();
|
||||
void StartCommitterSampler();
|
||||
void StopWorkerSampler();
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ public:
|
||||
if (bytes == 0 || bytes > size || address < base || address - base > size - bytes) return false;
|
||||
const auto first = (address - base) / BlockBytes;
|
||||
const auto last = (address - base + bytes - 1) / BlockBytes;
|
||||
return std::none_of(excluded.begin() + first, excluded.begin() + last + 1, [](auto generation) { return generation != 0; });
|
||||
const auto firstPage = (address - base) % BlockBytes / PageBytes;
|
||||
const auto lastPage = (address - base + bytes - 1) % BlockBytes / PageBytes;
|
||||
if (first == last) return (excluded[first] & pageMask(firstPage, lastPage + 1)) == 0;
|
||||
if ((excluded[first] & pageMask(firstPage, PagesPerBlock)) != 0 || (excluded[last] & pageMask(0, lastPage + 1)) != 0) return false;
|
||||
return std::none_of(excluded.begin() + first + 1, excluded.begin() + last, [](auto mask) { return mask != 0; });
|
||||
}
|
||||
|
||||
bool ExcludedSpan(std::uint64_t address, std::size_t bytes, std::uint64_t* begin, std::uint64_t* end) const {
|
||||
@@ -40,10 +44,12 @@ public:
|
||||
if (!excludedBlocks(address, bytes, first, last)) return false;
|
||||
bool changed = false;
|
||||
for (auto block = first; block <= last; ++block) {
|
||||
changed = changed || excluded[block] == 0;
|
||||
const auto pages = (std::min<std::uint64_t>(BlockBytes, size - block * BlockBytes) + PageBytes - 1) / PageBytes;
|
||||
const auto mask = pageMask(0, pages);
|
||||
changed = changed || excluded[block] != mask;
|
||||
auto& stamp = excludedGenerations[block];
|
||||
stamp = std::max(stamp, std::max<std::uint64_t>(generation, 1));
|
||||
excluded[block] = 1;
|
||||
excluded[block] = mask;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
@@ -58,21 +64,28 @@ public:
|
||||
const auto offset = address - base;
|
||||
if (offset >= size) return false;
|
||||
bytes = static_cast<std::size_t>(std::min<std::uint64_t>(bytes, size - offset));
|
||||
const auto first = offset / BlockBytes + (offset % BlockBytes != 0);
|
||||
const auto firstPage = offset / PageBytes + (offset % PageBytes != 0);
|
||||
const auto stop = offset + bytes;
|
||||
const auto end = stop / BlockBytes + (stop == size && stop % BlockBytes != 0);
|
||||
const auto endPage = stop / PageBytes + (stop == size && stop % PageBytes != 0);
|
||||
if (firstPage >= endPage) return false;
|
||||
const auto first = firstPage / PagesPerBlock;
|
||||
const auto last = (endPage - 1) / PagesPerBlock;
|
||||
bool changed = false;
|
||||
for (auto block = first; block < end; ++block) {
|
||||
if (excluded[block] != 0 && excludedGenerations.at(block) < generation) {
|
||||
for (auto block = first; block <= last; ++block) {
|
||||
const auto mask = pageMask(block == first ? firstPage % PagesPerBlock : 0, block == last ? (endPage - 1) % PagesPerBlock + 1 : PagesPerBlock);
|
||||
if ((excluded[block] & mask) != 0 && excludedGenerations.at(block) < generation) {
|
||||
changed = true;
|
||||
excluded[block] = 0;
|
||||
excludedGenerations.erase(block);
|
||||
excluded[block] &= static_cast<std::uint16_t>(~mask);
|
||||
if (excluded[block] == 0) excludedGenerations.erase(block);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::uint16_t pageMask(std::size_t first, std::size_t end) {
|
||||
return static_cast<std::uint16_t>(((1u << end) - 1u) & ~((1u << first) - 1u));
|
||||
}
|
||||
bool excludedBlocks(std::uint64_t address, std::size_t bytes, std::size_t& first, std::size_t& last) const {
|
||||
if (bytes == 0 || size == 0) return false;
|
||||
if (address < base) {
|
||||
@@ -88,9 +101,11 @@ private:
|
||||
return true;
|
||||
}
|
||||
static constexpr std::size_t BlockBytes = 65536;
|
||||
static constexpr std::size_t PageBytes = 4096;
|
||||
static constexpr std::size_t PagesPerBlock = BlockBytes / PageBytes;
|
||||
std::uint64_t base = 0;
|
||||
std::size_t size = 0;
|
||||
std::vector<std::uint8_t> excluded;
|
||||
std::vector<std::uint16_t> excluded;
|
||||
std::unordered_map<std::uint64_t, std::uint64_t> excludedGenerations;
|
||||
};
|
||||
|
||||
|
||||
@@ -147,11 +147,16 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
||||
}
|
||||
}
|
||||
if (partialLastGroup) compute.partialThreads = partialGroups;
|
||||
std::array<std::uint32_t, 3> threadGroup{};
|
||||
if (indirectArguments != 0 && (packet[4] & 0x20u) != 0) {
|
||||
for (std::uint32_t axis = 0; axis < 3; ++axis) threadGroup[axis] = std::max(readRegister(queue.shader, 0x207 + axis) & 0xffffu, 1u);
|
||||
compute.partialThreads = {~0u, ~0u, ~0u};
|
||||
}
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderRecompiler::ShaderStage::Compute, address, std::span(snapshot.code).subspan(codeOffset), snapshot.headerAddress, snapshot.header},
|
||||
{(packet[4] & 0x8000u) != 0 ? 32u : 64u, 0, userData, compute, std::nullopt, std::nullopt, memory, DispatchFloatMode(snapshot, queue.shader)},
|
||||
localDevice->ComputeTarget((packet[4] & 0x8000u) != 0 ? 32u : 64u),
|
||||
{0, 0, 0, 128}
|
||||
{0, 0, 0, threadGroup != std::array<std::uint32_t, 3>{} ? 0u : 128u}
|
||||
};
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
static double captureMs = 0, keyMs = 0, recompileMs = 0, deviceMs = 0;
|
||||
@@ -572,7 +577,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
||||
try {
|
||||
if (recipeHit != nullptr) {
|
||||
VulkanDevice::IndirectOutcome outcome{0, 0};
|
||||
const auto result = localDevice->DispatchRecipe(compiled, groups[0], groups[1], groups[2], indirectArguments, address, recipeHit, outcome, VulkanDevice::VerifyRecipes() ? prepared : nullptr, dataHit);
|
||||
const auto result = localDevice->DispatchRecipe(compiled, groups[0], groups[1], groups[2], indirectArguments, address, recipeHit, outcome, VulkanDevice::VerifyRecipes() ? prepared : nullptr, dataHit, threadGroup);
|
||||
if (result == RecipeOutcome::Rebuild) {
|
||||
|
||||
recipeHit = nullptr;
|
||||
@@ -581,7 +586,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
||||
}
|
||||
if (indirectArguments != 0) countIndirect(outcome.cpuReason, outcome.argumentReadMs);
|
||||
} else if (indirectArguments != 0) {
|
||||
const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr);
|
||||
const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr, threadGroup);
|
||||
countIndirect(outcome.cpuReason, outcome.argumentReadMs);
|
||||
} else {
|
||||
localDevice->Dispatch(compiled, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr);
|
||||
|
||||
@@ -48,7 +48,8 @@ void Driver::countIndirect(int path, double readMs) {
|
||||
int Driver::indirectArgumentsPath(std::span<const std::uint32_t> packet, std::uint64_t arguments) {
|
||||
static const bool gpuIndirect = std::getenv("APS5_NO_GPU_INDIRECT") == nullptr;
|
||||
if (!gpuIndirect) return IndirectDisabled;
|
||||
if ((packet.back() & 0x20u) != 0) return IndirectThreadDimensions;
|
||||
static const bool gpuThreadDimensions = std::getenv("APS5_NO_GPU_THREAD_DIMENSIONS") == nullptr;
|
||||
if ((packet.back() & 0x20u) != 0 && !gpuThreadDimensions) return IndirectThreadDimensions;
|
||||
if (arguments % 4 != 0) return IndirectMisaligned;
|
||||
return IndirectGpu;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,14 @@ std::shared_ptr<const ShaderRecompiler::RecompileResult> Driver::materializeDraw
|
||||
}
|
||||
stageCapture.capture = capture;
|
||||
timing.Mark("materialize");
|
||||
const auto walked = capture->userDataUse.asValue | capture->userDataUse.asBase;
|
||||
auto mask = walked == 0 ? ~std::uint64_t{0} : walked | stageCapture.compiled->userDataRegisterReads;
|
||||
if (vertexInfos[i]) {
|
||||
for (const auto reg : {static_cast<std::uint32_t>(vertexInfos[i]->fetchAttribReg), static_cast<std::uint32_t>(vertexInfos[i]->fetchBufferReg)}) {
|
||||
mask |= reg + 1u < 64u ? std::uint64_t{3} << reg : ~std::uint64_t{0};
|
||||
}
|
||||
}
|
||||
stageCapture.userDataMask = mask;
|
||||
phaseTiming.Phase(DrawRowRecompile);
|
||||
return stageCapture.compiled;
|
||||
}
|
||||
@@ -160,6 +168,7 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP
|
||||
variant->shader = programs[i].snapshot;
|
||||
variant->forgetSerial = stageCapture.forgetSerial;
|
||||
variant->pushOffset = stageCapture.pushOffset;
|
||||
variant->userDataMask = stageCapture.userDataMask;
|
||||
if (vertexInfos[i]) variant->vertexInfo = std::make_shared<const ShaderRecompiler::ShaderVertexStageInfo>(*vertexInfos[i]);
|
||||
|
||||
std::vector<ShaderRecompiler::MemoryRegion> regions(stageCapture.regions.begin(), stageCapture.regions.end());
|
||||
|
||||
@@ -215,9 +215,16 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::DrawCache);
|
||||
std::optional<SampledReadScope> sampling;
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) {
|
||||
if (roles[i] == Role::GeometryBack || programs[i].userData != dataEntry->decode->programs[i].userData) continue;
|
||||
if (roles[i] == Role::GeometryBack || programs[i].userData.size() != dataEntry->decode->programs[i].userData.size()) continue;
|
||||
const auto& now = programs[i].userData;
|
||||
const auto& was = dataEntry->decode->programs[i].userData;
|
||||
auto& candidates = dataCandidates[i];
|
||||
for (const auto& variant : dataEntry->stages[i]) {
|
||||
bool usedEqual = true;
|
||||
for (std::size_t w = 0; w < now.size() && usedEqual; ++w) {
|
||||
if (now[w] != was[w] && (w >= 64u || ((variant->userDataMask >> w) & 1u) != 0)) usedEqual = false;
|
||||
}
|
||||
if (!usedEqual) continue;
|
||||
if ((i == 0 && variant->pushOffset != 0) || std::any_of(candidates.begin(), candidates.end(), [&](const auto& candidate) { return candidate.first->pushOffset == variant->pushOffset; })) continue;
|
||||
std::vector<ShaderRecompiler::MemoryRegion> regions;
|
||||
appendEntryRegions(*variant, regions);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -142,6 +143,7 @@ void DrawPipeline::report(std::chrono::steady_clock::time_point now) {
|
||||
|
||||
void DrawPipeline::run() {
|
||||
PerformanceTrace::BindThread("draw committer");
|
||||
StartCommitterSampler();
|
||||
for (;;) {
|
||||
Commit* commit = nullptr;
|
||||
SpinUntil(HandoffSpinWindow(), [&] { return outstanding.load(std::memory_order_acquire) != 0; });
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/ThreadPriority.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Screenshot.hpp"
|
||||
#include "prx/libkernel/Time/include/SleepWake.hpp"
|
||||
#include <mutex>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
void Submit(const Packet* packet, std::uint32_t queue) try {
|
||||
KernelNoteGpuSubmit_nid_no_patch();
|
||||
DriverDetail::Driver::Get().Submit(packet, queue);
|
||||
} catch (const ProcessShutdown&) {
|
||||
LibcAwaitExit_nid_postfix();
|
||||
|
||||
@@ -183,6 +183,10 @@ Driver::Driver() {
|
||||
InstallAsyncLog_nid_no_patch();
|
||||
static const bool driverMemory = (MapDriverMemory(), true);
|
||||
static_cast<void>(driverMemory);
|
||||
LibcSetExitDrain_nid_no_patch([] {
|
||||
const auto current = Driver::Get().device.Load();
|
||||
if (current) current->SavePipelineCache();
|
||||
});
|
||||
static_cast<void>(GuestMemory::TrackerGeneration());
|
||||
ShaderMemory::SetWaitedMsProvider(&Graphics::Recorder::ThreadWaitedMs);
|
||||
try {
|
||||
|
||||
@@ -925,7 +925,7 @@ struct WriteTracker {
|
||||
if (first >= stop) return;
|
||||
bool started = false;
|
||||
for (auto block = blockOf(first); block <= blockOf(stop - 1); ++block) {
|
||||
if (!coverage.Covers(blockBegin(block), 1)) continue;
|
||||
if (!coverage.Covers(blockBegin(block), static_cast<std::size_t>(std::min<std::uint64_t>(WriteBlockBytes, base + size - blockBegin(block))))) continue;
|
||||
if (!started) ++generation;
|
||||
started = true;
|
||||
stampHint(block);
|
||||
@@ -935,7 +935,7 @@ struct WriteTracker {
|
||||
bool hinted(std::uint64_t first, std::uint64_t stop) const {
|
||||
if (first >= stop || first < base || stop > base + size) return false;
|
||||
for (auto block = blockOf(first); block <= blockOf(stop - 1); ++block) {
|
||||
if (hintOf(block) == 0 || coverage.Covers(blockBegin(block), 1)) return false;
|
||||
if (hintOf(block) == 0 || coverage.Covers(blockBegin(block), static_cast<std::size_t>(std::min<std::uint64_t>(WriteBlockBytes, base + size - blockBegin(block))))) return false;
|
||||
}
|
||||
return !GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(static_cast<std::uintptr_t>(first), static_cast<std::size_t>(stop - first));
|
||||
}
|
||||
@@ -1204,6 +1204,7 @@ void watchPrivateMapping(std::uintptr_t address, std::size_t bytes, std::uint64_
|
||||
++tracker.generation;
|
||||
tracker.noteCpuStore(address, address + bytes, tracker.generation);
|
||||
for (auto block = tracker.blockOf(address); block <= tracker.blockOf(address + bytes - 1); ++block) {
|
||||
if (const auto& leaf = tracker.leaves[block / WriteTracker::LeafBlocks]; leaf != nullptr) leaf->hintBlocks[block % WriteTracker::LeafBlocks] = 0;
|
||||
if (const auto found = tracker.driverPieces.find(block); found != tracker.driverPieces.end()) found->second.dropped = std::max<std::uint32_t>(found->second.dropped, tracker.generation);
|
||||
tracker.stamp(block, tracker.generation, StampKind::Cpu);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/HostHdr.hpp"
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
|
||||
namespace AgcDriver {
|
||||
@@ -24,7 +25,7 @@ bool SameDisplay(const HostDisplayHdr& a, const HostDisplayHdr& b) {
|
||||
|
||||
void Publish(HostHdrStatus& status, const HostDisplayHdr& display, bool surfaceHdr10) {
|
||||
const bool displayChanged = !SameDisplay(status.display, display);
|
||||
const bool available = display.hdr && surfaceHdr10;
|
||||
const bool available = display.hdr && surfaceHdr10 && std::getenv("APS5_FORCE_SDR") == nullptr;
|
||||
const bool availabilityChanged = available != status.available;
|
||||
status.display = display;
|
||||
status.surfaceHdr10 = surfaceHdr10;
|
||||
|
||||
@@ -538,14 +538,8 @@ PerformanceOverlay::PerformanceOverlay(const Graphics::Context& context, std::si
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &push;
|
||||
Graphics::Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout performance overlay");
|
||||
const auto createModule = context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(PERFORMANCE_OVERLAY_VERT_SPV);
|
||||
moduleInfo.pCode = PERFORMANCE_OVERLAY_VERT_SPV;
|
||||
Graphics::Check(createModule(context.device, &moduleInfo, nullptr, &vertex), "vkCreateShaderModule performance overlay vertex");
|
||||
moduleInfo.codeSize = sizeof(PERFORMANCE_OVERLAY_FRAG_SPV);
|
||||
moduleInfo.pCode = PERFORMANCE_OVERLAY_FRAG_SPV;
|
||||
Graphics::Check(createModule(context.device, &moduleInfo, nullptr, &fragment), "vkCreateShaderModule performance overlay fragment");
|
||||
vertex = Graphics::CreateShaderModule(context, PERFORMANCE_OVERLAY_VERT_SPV, sizeof(PERFORMANCE_OVERLAY_VERT_SPV), "vkCreateShaderModule performance overlay vertex");
|
||||
fragment = Graphics::CreateShaderModule(context, PERFORMANCE_OVERLAY_FRAG_SPV, sizeof(PERFORMANCE_OVERLAY_FRAG_SPV), "vkCreateShaderModule performance overlay fragment");
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
|
||||
@@ -70,14 +70,8 @@ PresentationPass::PresentationPass(const Graphics::Context& context, std::size_t
|
||||
tableWrites[i].pBufferInfo = &tableInfo;
|
||||
}
|
||||
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(tableWrites.size()), tableWrites.data(), 0, nullptr);
|
||||
const auto createModule = context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(PRESENTATION_PASS_VERT_SPV);
|
||||
moduleInfo.pCode = PRESENTATION_PASS_VERT_SPV;
|
||||
Graphics::Check(createModule(context.device, &moduleInfo, nullptr, &vertex), "vkCreateShaderModule presentation vertex");
|
||||
moduleInfo.codeSize = sizeof(PRESENTATION_PASS_FRAG_SPV);
|
||||
moduleInfo.pCode = PRESENTATION_PASS_FRAG_SPV;
|
||||
Graphics::Check(createModule(context.device, &moduleInfo, nullptr, &fragment), "vkCreateShaderModule presentation fragment");
|
||||
vertex = Graphics::CreateShaderModule(context, PRESENTATION_PASS_VERT_SPV, sizeof(PRESENTATION_PASS_VERT_SPV), "vkCreateShaderModule presentation vertex");
|
||||
fragment = Graphics::CreateShaderModule(context, PRESENTATION_PASS_FRAG_SPV, sizeof(PRESENTATION_PASS_FRAG_SPV), "vkCreateShaderModule presentation fragment");
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DeviceFault.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/FragmentBarycentric.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DrawInputsInPlace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp"
|
||||
@@ -321,6 +322,7 @@ struct VulkanDevice::State {
|
||||
bool float16DenormPreserve = false;
|
||||
bool float32FlushToZeroCandidate = false;
|
||||
bool float32FlushToZero = false;
|
||||
bool float32DenormPreserve = false;
|
||||
bool probingFloatControls = false;
|
||||
bool demoteToHelperInvocation = false;
|
||||
bool earlyFragmentSampleMaskTestBeforeSampleCounting = false;
|
||||
@@ -916,10 +918,7 @@ ComputePipelineReplayResult VulkanDevice::ReplayComputePipeline(const ComputePip
|
||||
check(state->DeviceFunction<PFN_vkCreatePipelineCache>("vkCreatePipelineCache")(state->device, &cacheInfo, nullptr, &replay.cache), "vkCreatePipelineCache replay");
|
||||
result.cacheMilliseconds = elapsed(phase);
|
||||
phase = std::chrono::steady_clock::now();
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = capture.spirv.size() * sizeof(std::uint32_t);
|
||||
moduleInfo.pCode = capture.spirv.data();
|
||||
check(state->DeviceFunction<PFN_vkCreateShaderModule>("vkCreateShaderModule")(state->device, &moduleInfo, nullptr, &objects.module), "vkCreateShaderModule replay");
|
||||
objects.module = Graphics::CreateShaderModule(state->DeviceFunction<PFN_vkCreateShaderModule>("vkCreateShaderModule"), state->device, state->graphicsPipelineLibrary, capture.spirv.data(), capture.spirv.size() * sizeof(std::uint32_t), "vkCreateShaderModule replay");
|
||||
result.moduleMilliseconds = elapsed(phase);
|
||||
phase = std::chrono::steady_clock::now();
|
||||
std::array<std::array<std::uint64_t, 4>, 2> descriptorCounts{};
|
||||
@@ -1163,10 +1162,12 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
|
||||
check(enumerateDeviceExtensions(selected, nullptr, &extensionCount, availableExtensions.data()), "vkEnumerateDeviceExtensionProperties");
|
||||
const auto hasExtension = [&](const char* name) { return std::any_of(availableExtensions.begin(), availableExtensions.end(), [&](const auto& item) { return std::strcmp(item.extensionName, name) == 0; }); };
|
||||
VkDriverId driverId{};
|
||||
if (hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
|
||||
driverId = driver.driverID;
|
||||
state->driverInfo = std::string(driver.driverName) + " " + driver.driverInfo;
|
||||
state->individuallyFreeDescriptorSets = driver.driverID == VK_DRIVER_ID_MESA_LLVMPIPE;
|
||||
state->subgroupControlBarriers = SubgroupControlBarrierUsable(driver.driverID);
|
||||
@@ -1329,9 +1330,11 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
}
|
||||
state->float16DenormPreserve = floatControls.shaderDenormPreserveFloat16 == VK_TRUE &&
|
||||
floatControls.denormBehaviorIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL;
|
||||
state->float32FlushToZeroCandidate = floatControls.shaderDenormFlushToZeroFloat32 == VK_TRUE && floatControls.denormBehaviorIndependence != VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE && std::getenv("APS5_NO_HW_F32_FLUSH") == nullptr;
|
||||
if (state->float32FlushToZeroCandidate) state->capabilities.push_back(spv::CapabilityDenormFlushToZero);
|
||||
if (state->float16DenormPreserve && std::find(state->capabilities.begin(), state->capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormPreserve)) == state->capabilities.end()) state->capabilities.push_back(spv::CapabilityDenormPreserve);
|
||||
const auto float32Flush = Float32FlushControlFor(driverId, floatControls.shaderDenormFlushToZeroFloat32, floatControls.denormBehaviorIndependence);
|
||||
state->float32FlushToZeroCandidate = float32Flush.probe && std::getenv("APS5_NO_HW_F32_FLUSH") == nullptr;
|
||||
if (state->float32FlushToZeroCandidate && float32Flush.declared) state->capabilities.push_back(spv::CapabilityDenormFlushToZero);
|
||||
state->float32DenormPreserve = floatControls.shaderDenormPreserveFloat32 == VK_TRUE && floatControls.denormBehaviorIndependence != VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE && std::getenv("APS5_NO_HW_F32_PRESERVE") == nullptr;
|
||||
if ((state->float16DenormPreserve || state->float32DenormPreserve) && std::find(state->capabilities.begin(), state->capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormPreserve)) == state->capabilities.end()) state->capabilities.push_back(spv::CapabilityDenormPreserve);
|
||||
deviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
|
||||
deviceExtensions.push_back(VK_KHR_8BIT_STORAGE_EXTENSION_NAME);
|
||||
state->capabilities.push_back(4448);
|
||||
@@ -2121,6 +2124,10 @@ void VulkanDevice::WaitForPipelineJournalReplay() {
|
||||
if (state->journalReplay != nullptr) state->journalReplay->Wait();
|
||||
}
|
||||
|
||||
void VulkanDevice::SavePipelineCache() {
|
||||
if (state->pipelineCache) state->pipelineCache->SaveNow();
|
||||
}
|
||||
|
||||
std::uint64_t VulkanDevice::ComputePipelinesCreated() const {
|
||||
return state->computePipelinesCreated.load(std::memory_order_relaxed);
|
||||
}
|
||||
@@ -3640,6 +3647,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::Target() const {
|
||||
require(state->shaderProfile != nullptr, "shader device profile is unavailable");
|
||||
auto target = state->shaderProfile->Target();
|
||||
target.float32FlushToZero = state->float32FlushToZero;
|
||||
target.float32DenormPreserve = state->float32DenormPreserve;
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -4358,6 +4366,8 @@ struct RecordedDispatch {
|
||||
std::shared_ptr<Graphics::ShaderResources::DrawBindings> bindings{};
|
||||
std::uint64_t cpuArguments = 0;
|
||||
int cpuReason = 0;
|
||||
std::array<std::uint32_t, 3> threadGroup{};
|
||||
std::array<std::uint32_t, 3> threadLimit{};
|
||||
};
|
||||
|
||||
VkDevice VulkanDevice::Device() const {
|
||||
@@ -4614,8 +4624,8 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std
|
||||
static_cast<void>(dispatch(shader, x, y, z, 0, snapshots, programAddress, std::move(prepared), recipe));
|
||||
}
|
||||
|
||||
VulkanDevice::IndirectOutcome VulkanDevice::DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe) {
|
||||
return dispatch(shader, 0, 0, 0, arguments, snapshots, programAddress, std::move(prepared), recipe);
|
||||
VulkanDevice::IndirectOutcome VulkanDevice::DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe, const std::array<std::uint32_t, 3>& threadGroup) {
|
||||
return dispatch(shader, 0, 0, 0, arguments, snapshots, programAddress, std::move(prepared), recipe, threadGroup);
|
||||
}
|
||||
|
||||
void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& outcome, char* groupsText) {
|
||||
@@ -4623,6 +4633,7 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out
|
||||
auto& timer = *record.timer;
|
||||
const auto& context = *record.context;
|
||||
const auto arguments = record.arguments;
|
||||
const bool threadDimensions = record.threadGroup != std::array<std::uint32_t, 3>{};
|
||||
// Decided here, after the resource build (which may retire imports and wait for recorded
|
||||
// work) and before the dispatch itself is recorded, so the import cannot be dropped before
|
||||
// the record. The build already recorded the copy-ins of its misaligned and staged regions
|
||||
@@ -4649,7 +4660,8 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out
|
||||
outcome.cpuReason = 8;
|
||||
} else if (labelPending() || state->CopiedWriterOverlaps(arguments, 12)) {
|
||||
outcome.cpuReason = 2;
|
||||
} else if ((record.argumentImport = Graphics::HostImportFor(context, arguments, 12)) == nullptr) {
|
||||
} else if ((record.argumentImport = Graphics::HostImportFor(context, arguments, 12)) == nullptr || (threadDimensions && record.argumentImport->address == 0)) {
|
||||
record.argumentImport = nullptr;
|
||||
// Valid until the next refreshImports, which only runs under GuestMemory::GpuMutex (held
|
||||
// here, by every Dispatch caller) and Keeps a retired VkBuffer while the recorder is busy.
|
||||
outcome.cpuReason = 3;
|
||||
@@ -4666,6 +4678,10 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out
|
||||
Graphics::FinishLeaseWork();
|
||||
std::array<std::uint32_t, 3> groups{};
|
||||
GuestMemory::Read(arguments, std::as_writable_bytes(std::span(groups)), 4);
|
||||
if (threadDimensions) {
|
||||
record.threadLimit = groups;
|
||||
for (std::size_t axis = 0; axis < groups.size(); ++axis) groups[axis] = groups[axis] / record.threadGroup[axis] + (groups[axis] % record.threadGroup[axis] != 0 ? 1u : 0u);
|
||||
}
|
||||
outcome.argumentReadMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count();
|
||||
record.x = groups[0];
|
||||
record.y = groups[1];
|
||||
@@ -4742,6 +4758,24 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
|
||||
}
|
||||
recordStep(PhaseRecordDataRefresh);
|
||||
}
|
||||
std::shared_ptr<Graphics::Buffer> threadArguments;
|
||||
if (record.threadGroup != std::array<std::uint32_t, 3>{}) {
|
||||
const auto data = resources.shaderDataBuffer(*record.shader);
|
||||
const auto limitDword = record.shader->program->imageMetadataDword;
|
||||
Graphics::Require(data != VK_NULL_HANDLE && limitDword >= 3, "Vulkan dispatch: a thread-dimension dispatch has no ShaderData thread limit");
|
||||
const VkDeviceSize limitByte = static_cast<VkDeviceSize>(limitDword - 3) * sizeof(std::uint32_t);
|
||||
Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
if (argumentImport != nullptr) {
|
||||
threadArguments = std::make_shared<Graphics::Buffer>(context, 24, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
Graphics::Require(recorder.RecordDispatchArguments(commands, argumentImport->address + (arguments - argumentImport->base), threadArguments->DeviceAddress(), &record.threadGroup), "Vulkan dispatch: thread-dimension indirect arguments need the argument kernel");
|
||||
const VkBufferCopy limit{12, limitByte, 12};
|
||||
context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer")(commands, threadArguments->Handle(), data, 1, &limit);
|
||||
} else {
|
||||
context.Resolved(&Graphics::DeviceFunctions::cmdUpdateBuffer, "vkCmdUpdateBuffer")(commands, data, limitByte, sizeof(record.threadLimit), record.threadLimit.data());
|
||||
}
|
||||
resources.forgetShaderDataWords(*record.shader);
|
||||
covered = 0;
|
||||
}
|
||||
using CommandClass = Graphics::Recorder::CommandClass;
|
||||
if (Graphics::Recorder::BarrierValidate()) {
|
||||
if (argumentImport != nullptr) {
|
||||
@@ -4775,7 +4809,11 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
|
||||
}
|
||||
VkBuffer indirectBuffer = argumentImport != nullptr ? argumentImport->buffer : VK_NULL_HANDLE;
|
||||
VkDeviceSize indirectOffset = argumentImport != nullptr ? Graphics::HostImportOffset(*argumentImport, arguments) : 0;
|
||||
if (argumentImport != nullptr && argumentImport->address != 0) {
|
||||
if (threadArguments != nullptr) {
|
||||
indirectBuffer = threadArguments->Handle();
|
||||
indirectOffset = 0;
|
||||
recorder.Keep(std::move(threadArguments));
|
||||
} else if (argumentImport != nullptr && argumentImport->address != 0) {
|
||||
auto checked = std::make_shared<Graphics::Buffer>(context, 12, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
if (recorder.RecordDispatchArguments(commands, argumentImport->address + (arguments - argumentImport->base), checked->DeviceAddress())) {
|
||||
indirectBuffer = checked->Handle();
|
||||
@@ -4876,10 +4914,7 @@ std::shared_ptr<ComputePipelineObjects> VulkanDevice::createComputePipeline(cons
|
||||
objects->destroyModule = state->DeviceFunction<PFN_vkDestroyShaderModule>("vkDestroyShaderModule");
|
||||
objects->destroyLayout = state->DeviceFunction<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout");
|
||||
objects->destroyPipeline = state->DeviceFunction<PFN_vkDestroyPipeline>("vkDestroyPipeline");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = shader.spirv.size() * sizeof(std::uint32_t);
|
||||
moduleInfo.pCode = shader.spirv.data();
|
||||
check(state->DeviceFunction<PFN_vkCreateShaderModule>("vkCreateShaderModule")(state->device, &moduleInfo, nullptr, &objects->module), "vkCreateShaderModule");
|
||||
objects->module = Graphics::CreateShaderModule(state->DeviceFunction<PFN_vkCreateShaderModule>("vkCreateShaderModule"), state->device, state->graphicsPipelineLibrary, shader.spirv.data(), shader.spirv.size() * sizeof(std::uint32_t), "vkCreateShaderModule");
|
||||
objects->resourcesLayout = setLayout;
|
||||
const std::array<VkDescriptorSetLayout, 2> setLayouts{setLayout, shader.workgroupMemoryDwords != 0 ? state->workgroupMemoryLayout : VK_NULL_HANDLE};
|
||||
const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::ComputePushConstantBytes};
|
||||
@@ -4939,11 +4974,7 @@ VkPipeline VulkanDevice::splitComputePipeline(const Graphics::Context& context,
|
||||
std::array<ShaderRecompiler::PipelineSpecializationConstant, ShaderRecompiler::PipelineSpecialization::WorkgroupOffsetWords> offsets{};
|
||||
for (std::uint32_t component = 0; component < offsets.size(); ++component) offsets[component] = {ShaderRecompiler::PipelineSpecialization::WorkgroupOffsetBase + component, base[component]};
|
||||
const Graphics::PipelineSpecialization specialization(shader, offsets);
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = shader.spirv.size() * sizeof(std::uint32_t);
|
||||
moduleInfo.pCode = shader.spirv.data();
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
check(state->DeviceFunction<PFN_vkCreateShaderModule>("vkCreateShaderModule")(state->device, &moduleInfo, nullptr, &module), "vkCreateShaderModule");
|
||||
VkShaderModule module = Graphics::CreateShaderModule(state->DeviceFunction<PFN_vkCreateShaderModule>("vkCreateShaderModule"), state->device, state->graphicsPipelineLibrary, shader.spirv.data(), shader.spirv.size() * sizeof(std::uint32_t), "vkCreateShaderModule");
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
try {
|
||||
CreateComputePipeline(state->device, state->DeviceFunction<PFN_vkCreateComputePipelines>("vkCreateComputePipelines"), context.pipelineCache, module, objects.layout, computePipelineFlags(), computeStageFlags(shader), specialization.Info(), computeSubgroupSize(shader), &pipeline, context.pipelineCacheSynchronization);
|
||||
@@ -4994,8 +5025,19 @@ std::shared_ptr<ComputePipelineObjects> VulkanDevice::computePipeline(const Grap
|
||||
return finish(std::move(objects), nullptr);
|
||||
}
|
||||
|
||||
VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipeOut) {
|
||||
VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipeOut, const std::array<std::uint32_t, 3>& threadGroup) {
|
||||
startJournalReplay();
|
||||
for (const auto& binding : shader.bindings) {
|
||||
if (binding.role != ShaderRecompiler::DescriptorRole::GuestBuffers) continue;
|
||||
for (std::size_t at = 0; at + 4u <= binding.guestDescriptor.size(); at += 4u) {
|
||||
const auto* words = binding.guestDescriptor.data() + at;
|
||||
const auto base = static_cast<std::uint64_t>(words[0]) | (static_cast<std::uint64_t>(words[1] & 0xffffu) << 32u);
|
||||
const auto stride = (words[1] >> 16u) & 0x3fffu;
|
||||
const auto records = static_cast<std::uint64_t>(words[2]);
|
||||
const auto bytes = stride != 0 ? records * stride : records;
|
||||
if (base != 0 && bytes != 0) static_cast<void>(Graphics::FlushDepthPlanes(base, static_cast<std::size_t>(bytes)));
|
||||
}
|
||||
}
|
||||
state->recorder->BoundKeptBytes();
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
|
||||
PerformanceTimer timing("Vulkan.Dispatch");
|
||||
@@ -5132,6 +5174,10 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
||||
Graphics::ShaderResources::MoveProof proof;
|
||||
auto moved = cached->MovedDispatchBuffers(shaders[0], state->recorder.get(), DispatchMoveBytes(), proof);
|
||||
moves.checked.fetch_add(1, std::memory_order_relaxed);
|
||||
if (moved.has_value() && threadGroup != std::array<std::uint32_t, 3>{}) {
|
||||
const auto data = cached->shaderDataAllocation(shaders[0]);
|
||||
if (data.has_value() && std::ranges::any_of(*moved, [&](const Graphics::ShaderResources::MovedBuffer& entry) { return entry.allocation == *data; })) moved.reset();
|
||||
}
|
||||
if (moved.has_value() && !moved->empty()) {
|
||||
overlay = cached->PrepareDrawBindings(*state->recorder, *moved, true);
|
||||
if (overlay != nullptr && overlay->allocation.set == VK_NULL_HANDLE) overlay.reset();
|
||||
@@ -5234,6 +5280,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
||||
RecordedDispatch record{&context, &shaders[0], resources, objects, pushStages, &pushBytes, x, y, z, arguments, nullptr, programAddress, fromCache && TemplateDataRefresh() ? RecordedDispatch::DataRefresh::Words : RecordedDispatch::DataRefresh::None, 0, revalidateMs, &timer};
|
||||
record.resourceKeyMatched = fromCache;
|
||||
record.bindings = overlay;
|
||||
record.threadGroup = threadGroup;
|
||||
if (arguments != 0) decideIndirect(record, outcome, groupsText);
|
||||
// The indirect hold total below still covers the whole call of a CPU-resolved one.
|
||||
const bool indirect = record.arguments != 0 || outcome.cpuReason != 0;
|
||||
@@ -5308,7 +5355,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
|
||||
return outcome;
|
||||
}
|
||||
|
||||
RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify, bool refreshByWords) {
|
||||
RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify, bool refreshByWords, const std::array<std::uint32_t, 3>& threadGroup) {
|
||||
state->recorder->BoundKeptBytes();
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
|
||||
PerformanceTimer timing("Vulkan.DispatchRecipe");
|
||||
@@ -5404,6 +5451,7 @@ RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResu
|
||||
if (dataRefresh == RecordedDispatch::DataRefresh::Words) counters.refreshByWords.fetch_add(1, std::memory_order_relaxed);
|
||||
RecordedDispatch record{&context, &shaders[0], hit->resources, hit->objects, recipe.pushes ? VkShaderStageFlags{VK_SHADER_STAGE_COMPUTE_BIT} : VkShaderStageFlags{0}, &recipe.pushBytes, x, y, z, arguments, nullptr, programAddress, dataRefresh, recipe.dataWordsHash, 0, &timer};
|
||||
record.resourceKeyMatched = verify != nullptr;
|
||||
record.threadGroup = threadGroup;
|
||||
if (arguments != 0) decideIndirect(record, outcome, groupsText);
|
||||
const bool indirect = record.arguments != 0 || outcome.cpuReason != 0;
|
||||
const auto recordStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace {
|
||||
|
||||
std::jthread processSamplerThread;
|
||||
std::jthread workerSamplerThread;
|
||||
std::jthread committerSamplerThread;
|
||||
|
||||
// Debug aid: APS5_SAMPLE_WORKER=<file> samples the calling thread every millisecond and, every 20 s,
|
||||
// writes "module+offset self inclusive" lines to <file> for offline symbolization with nm.
|
||||
@@ -285,11 +286,7 @@ void StartProcessSampler() {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StartWorkerSampler() {
|
||||
StartProcessSampler();
|
||||
const char* path = std::getenv("APS5_SAMPLE_WORKER");
|
||||
void StartCallingThreadSampler(const char* path, std::jthread& slot) {
|
||||
if (path == nullptr) return;
|
||||
auto sampler = std::make_unique<Sampler>();
|
||||
sampler->path = path;
|
||||
@@ -302,7 +299,7 @@ void StartWorkerSampler() {
|
||||
sampler->stackHigh = reinterpret_cast<std::uint64_t>(block->StackBase);
|
||||
}
|
||||
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &sampler->target, THREAD_ALL_ACCESS, FALSE, 0)) throw std::system_error(GetLastError(), std::system_category(), "Duplicating sampler thread handle");
|
||||
workerSamplerThread = std::jthread([sampler = std::move(sampler)](std::stop_token token) {
|
||||
slot = std::jthread([sampler = std::move(sampler)](std::stop_token token) {
|
||||
auto flushed = std::chrono::steady_clock::now();
|
||||
while (!token.stop_requested()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
@@ -316,14 +313,28 @@ void StartWorkerSampler() {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StartWorkerSampler() {
|
||||
StartProcessSampler();
|
||||
StartCallingThreadSampler(std::getenv("APS5_SAMPLE_WORKER"), workerSamplerThread);
|
||||
}
|
||||
|
||||
void StartCommitterSampler() {
|
||||
StartCallingThreadSampler(std::getenv("APS5_SAMPLE_COMMITTER"), committerSamplerThread);
|
||||
}
|
||||
|
||||
void StopWorkerSampler() {
|
||||
processSamplerThread.request_stop();
|
||||
workerSamplerThread.request_stop();
|
||||
committerSamplerThread.request_stop();
|
||||
if (processSamplerThread.joinable()) processSamplerThread.join();
|
||||
if (workerSamplerThread.joinable()) workerSamplerThread.join();
|
||||
if (committerSamplerThread.joinable()) committerSamplerThread.join();
|
||||
}
|
||||
#else
|
||||
void StartWorkerSampler() {}
|
||||
void StartCommitterSampler() {}
|
||||
void StopWorkerSampler() {}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ public:
|
||||
std::vector<std::string> messages;
|
||||
};
|
||||
void SetImageTables(std::vector<ImageTableFaults> tables, std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges);
|
||||
struct SrtGuardFaults {
|
||||
std::uint32_t stage = 0;
|
||||
std::vector<std::uint32_t> flattenedSrt;
|
||||
};
|
||||
void SetSrtGuards(std::vector<SrtGuardFaults> guards);
|
||||
// APS5_PROFILE_DRAW: page tables served from the per-device cache and built anew (cumulative),
|
||||
// and how many recent tables the cache holds right now.
|
||||
struct TableCacheStats {
|
||||
@@ -52,11 +57,13 @@ private:
|
||||
// same device addresses share one buffer while one of them is alive (see the table cache in
|
||||
// BdaResources.cpp, which refers to it weakly).
|
||||
void markWrittenPages() const;
|
||||
void checkSrtGuard() const;
|
||||
std::shared_ptr<Buffer> table;
|
||||
std::unique_ptr<Buffer> fault;
|
||||
std::size_t tableBytes = 0;
|
||||
std::vector<ImageTableFaults> imageTables;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> imageTableRanges;
|
||||
std::vector<SrtGuardFaults> srtGuards;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BUFFERPOOL_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
@@ -24,6 +26,7 @@ struct BufferAllocation {
|
||||
VkDeviceSize offset = 0;
|
||||
bool slab = false;
|
||||
std::size_t bufferBytes = 0;
|
||||
std::uint32_t memoryType = 0;
|
||||
};
|
||||
|
||||
struct SlabSlot {
|
||||
@@ -63,6 +66,10 @@ public:
|
||||
static std::size_t Capacity(std::size_t bytes, VkMemoryPropertyFlags properties);
|
||||
static VkBufferUsageFlags Usage(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties);
|
||||
VkDeviceMemory AllocateMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional<std::uint32_t> hostType = std::nullopt, std::uint32_t* allocatedType = nullptr);
|
||||
static VkDeviceSize ImageBudget(VkDeviceSize deviceBudget);
|
||||
VkDeviceMemory TakeImageMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional<std::uint32_t> hostType = std::nullopt, std::uint32_t* allocatedType = nullptr);
|
||||
void PutImageMemory(VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept;
|
||||
std::size_t RetainedImages();
|
||||
std::optional<BufferAllocation> Take(std::size_t bytes, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
void Put(const BufferAllocation& allocation) noexcept;
|
||||
static bool SlabEligible(std::size_t capacity, VkDeviceSize alignment, VkDeviceSize size, VkDeviceSize atom);
|
||||
@@ -90,16 +97,18 @@ private:
|
||||
struct Slot {
|
||||
BufferAllocation allocation;
|
||||
std::uint64_t lastUse;
|
||||
std::list<Slot*>::iterator order;
|
||||
};
|
||||
struct SlotKey {
|
||||
std::size_t bytes;
|
||||
VkBufferUsageFlags usage;
|
||||
VkMemoryPropertyFlags properties;
|
||||
std::uint32_t memoryType = 0;
|
||||
bool operator==(const SlotKey&) const = default;
|
||||
};
|
||||
struct SlotKeyHash {
|
||||
std::size_t operator()(const SlotKey& key) const noexcept {
|
||||
return std::hash<std::size_t>{}(key.bytes) ^ (static_cast<std::size_t>(key.usage) << 32u) ^ (static_cast<std::size_t>(key.properties) << 48u);
|
||||
return std::hash<std::size_t>{}(key.bytes) ^ (static_cast<std::size_t>(key.usage) << 32u) ^ (static_cast<std::size_t>(key.properties) << 48u) ^ (static_cast<std::size_t>(key.memoryType) << 56u);
|
||||
}
|
||||
};
|
||||
// One retention tier: its slots by key (each list oldest first), their count and bytes, the
|
||||
@@ -127,6 +136,9 @@ private:
|
||||
// caller destroys those after releasing the mutex, so builds taking buffers on other threads
|
||||
// do not wait behind the Vulkan destroy calls. Nothing changes when the vector cannot grow.
|
||||
void evictOldest(Tier& tier, std::vector<BufferAllocation>& evicted);
|
||||
bool evictOldestImage(std::vector<BufferAllocation>& evicted);
|
||||
bool imagePooled(VkDeviceSize bytes, std::uint32_t memoryType) const;
|
||||
bool heapOverBudget();
|
||||
// The retained-slot bound of each tier (APS5_BUFFER_POOL_SLOTS, default `defaultSlots`), read once.
|
||||
static std::size_t MaxSlots();
|
||||
std::size_t maxSlots;
|
||||
@@ -137,14 +149,23 @@ private:
|
||||
PFN_vkFreeMemory freeMemory;
|
||||
PFN_vkAllocateMemory allocateMemory;
|
||||
PFN_vkMapMemory mapMemory;
|
||||
VkPhysicalDevice physical;
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
PFN_vkGetPhysicalDeviceMemoryProperties2 memoryProperties2;
|
||||
VkPhysicalDevice physical = VK_NULL_HANDLE;
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties{};
|
||||
PFN_vkGetPhysicalDeviceMemoryProperties2 memoryProperties2 = nullptr;
|
||||
std::mutex mutex;
|
||||
// Not `small`/`large`: <rpcndr.h> (via <windows.h>) defines `small` as a macro.
|
||||
Tier smallTier;
|
||||
Tier largeTier;
|
||||
Tier deviceTier;
|
||||
std::chrono::steady_clock::time_point heapRead{};
|
||||
bool heapOver = false;
|
||||
VkDeviceSize imageBudget = 0;
|
||||
VkDeviceSize imageBytes = 0;
|
||||
std::size_t imageSlots = 0;
|
||||
std::uint64_t imageHits = 0;
|
||||
std::uint64_t imageMisses = 0;
|
||||
std::uint64_t imageEvictions = 0;
|
||||
std::list<Slot*> imageOrder;
|
||||
std::uint64_t clock = 0;
|
||||
std::mutex slabMutex;
|
||||
std::unordered_map<std::uint64_t, Slab> slabs;
|
||||
@@ -160,7 +181,8 @@ private:
|
||||
};
|
||||
|
||||
std::shared_ptr<BufferPool> GetBufferPool(const Context& context);
|
||||
VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation);
|
||||
VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, std::uint32_t* allocatedType = nullptr);
|
||||
void ReleaseImageMemory(const Context& context, VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
|
||||
class TextureDetiler;
|
||||
class GpuColorTransfer;
|
||||
class BufferPool;
|
||||
@@ -71,6 +72,17 @@ inline void Check(VkResult result, const char* operation) {
|
||||
if (result != VK_SUCCESS) throw std::runtime_error(std::string("AGC graphics: ") + operation + ": Vulkan result " + std::to_string(result));
|
||||
}
|
||||
|
||||
inline VkShaderModule CreateShaderModule(PFN_vkCreateShaderModule create, VkDevice device, bool graphicsPipelineLibrary, const std::uint32_t* code, std::size_t bytes, const char* operation) {
|
||||
std::vector<std::uint32_t> keyed(code, code + bytes / sizeof(std::uint32_t));
|
||||
if (!graphicsPipelineLibrary && keyed.size() > 2) keyed[2] ^= 0x8000u;
|
||||
VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
info.codeSize = keyed.size() * sizeof(std::uint32_t);
|
||||
info.pCode = keyed.data();
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
Check(create(device, &info, nullptr, &module), operation);
|
||||
return module;
|
||||
}
|
||||
|
||||
inline void Require(bool condition, const char* reason) {
|
||||
if (!condition) throw std::runtime_error(std::string("AGC graphics: ") + reason);
|
||||
}
|
||||
@@ -337,6 +349,10 @@ struct Context {
|
||||
}
|
||||
};
|
||||
|
||||
inline VkShaderModule CreateShaderModule(const Context& context, const std::uint32_t* code, std::size_t bytes, const char* operation) {
|
||||
return CreateShaderModule(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule"), context.device, context.graphicsPipelineLibrary, code, bytes, operation);
|
||||
}
|
||||
|
||||
inline void EndRenderPass(const Context& context, VkCommandBuffer commands) {
|
||||
if (context.graphicsPipelineLibrary) context.Resolved(&DeviceFunctions::cmdEndRendering, "vkCmdEndRenderingKHR")(commands);
|
||||
else context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(commands);
|
||||
|
||||
@@ -9,13 +9,16 @@
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
class Texture;
|
||||
class StorageTexture;
|
||||
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target);
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten = true);
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint64_t address, std::size_t bytes);
|
||||
bool DepthPlanesStale();
|
||||
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples);
|
||||
std::uint64_t DepthSurfacesSerial();
|
||||
std::uint64_t DepthSurfacesReleased();
|
||||
|
||||
@@ -202,7 +202,7 @@ public:
|
||||
// ever submitted); WaitSerial(serial) then covers all recorded work.
|
||||
std::uint64_t SubmitAndEpoch();
|
||||
bool HasTimeline() const { return timeline != VK_NULL_HANDLE; }
|
||||
bool RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments);
|
||||
bool RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments, const std::array<std::uint32_t, 3>* threadGroup = nullptr);
|
||||
// Waits until every batch up to `serial` completed on the GPU. Called WITHOUT GuestMemory::GpuMutex:
|
||||
// it touches only the immutable timeline semaphore (the caller keeps the device alive). Requires
|
||||
// HasTimeline(); the debug switch APS5_NO_TIMELINE makes the device create the recorder without one.
|
||||
@@ -233,6 +233,7 @@ public:
|
||||
std::uint64_t end;
|
||||
bool computed;
|
||||
};
|
||||
static std::vector<std::size_t> QueuedWriteOrder(std::span<const QueuedWrite> writes);
|
||||
static std::vector<std::size_t> QueuedWriteGroups(std::span<const QueuedWrite> writes);
|
||||
// Waits only for the batches up to the newest one that writes the range (submitting the open
|
||||
// batch when it is that one); later batches stay in flight. Fences of one queue signal in
|
||||
@@ -808,6 +809,8 @@ private:
|
||||
VkSemaphore timeline = VK_NULL_HANDLE;
|
||||
VkSemaphore submitHold = VK_NULL_HANDLE;
|
||||
std::uint64_t submitHoldValue = 0;
|
||||
struct ProgressWatcher;
|
||||
std::unique_ptr<ProgressWatcher> progressWatcher;
|
||||
// Identity for a thread that released the mutex around a wait (syncThroughUnlocked): the
|
||||
// recorder may have been torn down meanwhile, so it is looked up by this id, not by pointer.
|
||||
std::uint64_t id = 0;
|
||||
|
||||
@@ -307,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 };
|
||||
@@ -336,6 +336,9 @@ public:
|
||||
private:
|
||||
friend class AgcDriver::VulkanDevice;
|
||||
bool refreshData(VkCommandBuffer commands, const CompiledShader& shader, Recorder* recorder);
|
||||
std::optional<std::size_t> shaderDataAllocation(const CompiledShader& shader) const;
|
||||
VkBuffer shaderDataBuffer(const CompiledShader& shader) const;
|
||||
void forgetShaderDataWords(const CompiledShader& shader);
|
||||
struct DescribedRange {
|
||||
const char* kind;
|
||||
std::uint64_t address;
|
||||
@@ -543,6 +546,7 @@ private:
|
||||
GuestBufferMemory guestMemory;
|
||||
std::size_t maxDrawSnapshotBytes = MaxDrawSnapshotBytes();
|
||||
std::unique_ptr<BdaResources> bda;
|
||||
std::vector<BdaResources::SrtGuardFaults> srtGuardFaults;
|
||||
bool usesBda = false;
|
||||
bool bdaReadOnly = true;
|
||||
std::vector<std::uint64_t> multisampleSources;
|
||||
@@ -592,6 +596,7 @@ 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;
|
||||
|
||||
@@ -46,13 +46,13 @@ struct OwnedImage {
|
||||
OwnedImage(const Context& context, VkImage image, VkDeviceMemory memory) : context(context), image(image), memory(memory) {}
|
||||
OwnedImage(const OwnedImage&) = delete;
|
||||
OwnedImage& operator=(const OwnedImage&) = delete;
|
||||
~OwnedImage() {
|
||||
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
|
||||
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
|
||||
}
|
||||
~OwnedImage();
|
||||
void Allocate(const Context& device, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation);
|
||||
Context context;
|
||||
VkImage image;
|
||||
VkDeviceMemory memory;
|
||||
VkDeviceSize memoryBytes = 0;
|
||||
std::uint32_t memoryType = 0;
|
||||
};
|
||||
|
||||
class Texture {
|
||||
@@ -197,6 +197,7 @@ public:
|
||||
void WriteBack();
|
||||
// Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead).
|
||||
void MarkDirty();
|
||||
void MarkWholeWritten();
|
||||
void MarkSliceDirty(std::uint32_t slice, std::uint32_t count = 1);
|
||||
void Flush();
|
||||
bool Dirty() const;
|
||||
@@ -366,6 +367,9 @@ public:
|
||||
static std::uint64_t SinglePassMoves();
|
||||
static std::uint64_t SlabRetiles();
|
||||
static std::uint64_t RefreshesProved();
|
||||
static std::uint64_t ClearHandOvers();
|
||||
static std::uint64_t HandedOverSuperseded();
|
||||
static void SetClearHandOver(bool enabled);
|
||||
static std::uint64_t UntrackedCompares();
|
||||
std::uint64_t GuestBytes() const;
|
||||
std::uint64_t BudgetBytes() const;
|
||||
@@ -505,6 +509,8 @@ private:
|
||||
// stays responsible for storing them until this image's own results supersede them
|
||||
// (markLayersPending). APS5_NO_ALIAS_BORROW=1 stores and re-reads as before.
|
||||
std::shared_ptr<StorageTexture> pendingAlias() const;
|
||||
std::optional<DccKeys> clearHandOver();
|
||||
void supersedeHandedOver(std::uint32_t first, std::uint32_t count);
|
||||
// The pending images FlushPending(address, bytes) would store (not the one a Refresh validates,
|
||||
// a pending unit inside the range), kept alive across the caller's use.
|
||||
static std::vector<std::shared_ptr<StorageTexture>> overlappingPending(std::uint64_t address, std::size_t bytes);
|
||||
@@ -585,6 +591,8 @@ private:
|
||||
std::weak_ptr<StorageTexture> borrowedFrom;
|
||||
std::uint64_t borrowedVersion = 0;
|
||||
std::vector<bool> borrowedUnits;
|
||||
std::vector<std::pair<std::weak_ptr<StorageTexture>, std::uint64_t>> handedOver;
|
||||
std::vector<bool> handedUnits;
|
||||
// Units of this image were borrowed since it was last written: the borrower's proofs
|
||||
// (the resource cache's serial memo) hold only while this image's version does, so the next
|
||||
// write here bumps the pending serial although the image was pending already.
|
||||
@@ -592,6 +600,7 @@ private:
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
VkDeviceSize memoryBytes = 0;
|
||||
std::uint32_t memoryType = 0;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
std::uint32_t defaultMip = 0;
|
||||
std::map<std::uint32_t, VkImageView> extraViews;
|
||||
@@ -610,6 +619,7 @@ private:
|
||||
// Results are on the GPU only (guarded by the pending-write registry lock).
|
||||
bool dirty = false;
|
||||
std::uint64_t version = 0;
|
||||
std::uint64_t writes = 0;
|
||||
// `original` holds the guest bytes; false after a GPU-side clear, which never read them.
|
||||
std::vector<bool> layerOriginal;
|
||||
std::map<std::uint64_t, std::uint64_t> keptAtUnwatch;
|
||||
|
||||
@@ -363,23 +363,36 @@ inline std::optional<std::uint32_t> ClampDrawIndices(std::span<std::byte> indice
|
||||
return highest;
|
||||
}
|
||||
|
||||
template <typename TIndex>
|
||||
std::optional<std::uint32_t> HighestDrawIndexOf(std::span<const std::byte> indices, bool skipRestart) {
|
||||
constexpr auto restartIndex = std::numeric_limits<TIndex>::max();
|
||||
const auto count = indices.size() / sizeof(TIndex);
|
||||
const auto* data = indices.data();
|
||||
TIndex highest = 0;
|
||||
if (!skipRestart) {
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
TIndex index;
|
||||
std::memcpy(&index, data + i * sizeof(TIndex), sizeof(TIndex));
|
||||
highest = std::max(highest, index);
|
||||
}
|
||||
if (count == 0) return std::nullopt;
|
||||
return highest;
|
||||
}
|
||||
TIndex kept = 0;
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
TIndex index;
|
||||
std::memcpy(&index, data + i * sizeof(TIndex), sizeof(TIndex));
|
||||
const TIndex valid = index == restartIndex ? TIndex{0} : TIndex(~TIndex{0});
|
||||
highest = std::max(highest, static_cast<TIndex>(index & valid));
|
||||
kept |= valid;
|
||||
}
|
||||
if (kept == 0) return std::nullopt;
|
||||
return highest;
|
||||
}
|
||||
|
||||
inline std::optional<std::uint32_t> HighestDrawIndex(std::span<const std::byte> indices, std::uint32_t indexSize, bool skipRestart) {
|
||||
Require(indexSize == 2 || indexSize == 4, "unsupported index size");
|
||||
const auto restartIndex = indexSize == 2 ? 0xffffu : 0xffffffffu;
|
||||
std::optional<std::uint32_t> highest;
|
||||
for (std::size_t offset = 0; offset + indexSize <= indices.size(); offset += indexSize) {
|
||||
std::uint32_t index = 0;
|
||||
if (indexSize == 2) {
|
||||
std::uint16_t value = 0;
|
||||
std::memcpy(&value, indices.data() + offset, sizeof(value));
|
||||
index = value;
|
||||
} else {
|
||||
std::memcpy(&index, indices.data() + offset, sizeof(index));
|
||||
}
|
||||
if (skipRestart && index == restartIndex) continue;
|
||||
highest = std::max(highest.value_or(0u), index);
|
||||
}
|
||||
return highest;
|
||||
return indexSize == 2 ? HighestDrawIndexOf<std::uint16_t>(indices, skipRestart) : HighestDrawIndexOf<std::uint32_t>(indices, skipRestart);
|
||||
}
|
||||
|
||||
inline std::vector<std::uint32_t> MeshRestartTable(std::span<const std::byte> indices, std::uint32_t indexSize) {
|
||||
|
||||
@@ -9,13 +9,29 @@ layout(push_constant) uniform Parameters {
|
||||
uint maxX;
|
||||
uint maxY;
|
||||
uint maxZ;
|
||||
uint threadDimensions;
|
||||
uint groupX;
|
||||
uint groupY;
|
||||
uint groupZ;
|
||||
uint unused;
|
||||
} parameters;
|
||||
|
||||
uint groups(uint threads, uint group) {
|
||||
return threads / group + (threads % group != 0u ? 1u : 0u);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint x = parameters.source.words[0];
|
||||
uint y = parameters.source.words[1];
|
||||
uint z = parameters.source.words[2];
|
||||
if (parameters.threadDimensions != 0u) {
|
||||
parameters.arguments.words[3] = x;
|
||||
parameters.arguments.words[4] = y;
|
||||
parameters.arguments.words[5] = z;
|
||||
x = groups(x, parameters.groupX);
|
||||
y = groups(y, parameters.groupY);
|
||||
z = groups(z, parameters.groupZ);
|
||||
}
|
||||
bool valid = x <= parameters.maxX && y <= parameters.maxY && z <= parameters.maxZ;
|
||||
parameters.arguments.words[0] = valid ? x : 0u;
|
||||
parameters.arguments.words[1] = valid ? y : 0u;
|
||||
|
||||
@@ -127,6 +127,7 @@ BdaResources::BdaResources(const Context& context) {
|
||||
Require(ShaderRecompiler::BdaAbi::FaultBufferBytes <= context.limits.maxStorageBufferRange, "BDA fault buffer exceeds storage buffer range limit");
|
||||
fault = std::make_unique<Buffer>(context, ShaderRecompiler::BdaAbi::FaultBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
std::memset(fault->Bytes().data(), 0, ShaderRecompiler::BdaAbi::WrittenListWord * sizeof(std::uint32_t));
|
||||
std::memset(fault->Bytes().data() + ShaderRecompiler::BdaAbi::SrtGuardWord * sizeof(std::uint32_t), 0, sizeof(std::uint32_t));
|
||||
}
|
||||
|
||||
BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memory) : BdaResources(context) {
|
||||
@@ -245,6 +246,7 @@ void BdaResources::CheckFault() const {
|
||||
std::memcpy(&report, fault->Bytes().data(), sizeof(report));
|
||||
if (report.state == ShaderRecompiler::BdaAbi::FaultState::Empty) {
|
||||
Require(static_cast<std::uint32_t>(report.reason) == 0 && report.address == 0 && report.bytes == 0 && report.stage == 0 && report.instruction == 0 && report.otherFaults == 0, "BDA fault record has data without publication");
|
||||
checkSrtGuard();
|
||||
return;
|
||||
}
|
||||
Require(report.state == ShaderRecompiler::BdaAbi::FaultState::Ready && report.otherFaults <= 1, "incomplete or invalid BDA fault record");
|
||||
@@ -309,6 +311,27 @@ void BdaResources::SetImageTables(std::vector<ImageTableFaults> tables, std::vec
|
||||
imageTableRanges = std::move(ranges);
|
||||
}
|
||||
|
||||
void BdaResources::SetSrtGuards(std::vector<SrtGuardFaults> guards) {
|
||||
srtGuards = std::move(guards);
|
||||
}
|
||||
|
||||
void BdaResources::checkSrtGuard() const {
|
||||
namespace Abi = ShaderRecompiler::BdaAbi;
|
||||
std::uint32_t guard = 0;
|
||||
std::memcpy(&guard, fault->Bytes().data() + Abi::SrtGuardWord * sizeof(std::uint32_t), sizeof(guard));
|
||||
if (guard == 0) return;
|
||||
const auto stage = guard >> Abi::SrtGuardStageShift;
|
||||
const auto record = guard & ((1u << Abi::SrtGuardStageShift) - 1u);
|
||||
const auto found = std::find_if(srtGuards.begin(), srtGuards.end(), [&](const SrtGuardFaults& candidate) { return candidate.stage == stage; });
|
||||
const auto* guards = found != srtGuards.end() ? &*found : srtGuards.size() == 1 ? &srtGuards.front() : nullptr;
|
||||
Require(guards != nullptr && record + 2u < guards->flattenedSrt.size(), "a guarded SRT read faulted without its poison record");
|
||||
const auto& words = guards->flattenedSrt;
|
||||
const auto address = static_cast<std::uint64_t>(words[record + 1u]) | (static_cast<std::uint64_t>(words[record + 2u]) << 32u);
|
||||
std::ostringstream message;
|
||||
message << "BDA access failed: address=0x" << std::hex << address << " instruction=0x" << words[record] << std::dec << " bytes=4 stage=" << stage << " reason=" << static_cast<std::uint32_t>(Abi::FaultReason::Unmapped);
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
|
||||
void BdaResources::markWrittenPages() const {
|
||||
namespace Abi = ShaderRecompiler::BdaAbi;
|
||||
auto* words = reinterpret_cast<std::uint32_t*>(fault->Bytes().data());
|
||||
|
||||
@@ -42,6 +42,10 @@ BufferPool::BufferPool(const Context& context) : maxSlots(std::max<std::size_t>(
|
||||
smallTier.budget = smallBudget;
|
||||
largeTier.budget = budget;
|
||||
deviceTier.budget = deviceBudget(context);
|
||||
memoryProperties = context.memory;
|
||||
physical = context.physical;
|
||||
memoryProperties2 = context.memoryProperties2;
|
||||
imageBudget = ImageBudget(deviceTier.budget);
|
||||
}
|
||||
|
||||
BufferPool::~BufferPool() {
|
||||
@@ -61,7 +65,7 @@ VkDeviceMemory BufferPool::AllocateMemory(const VkMemoryAllocateInfo& allocation
|
||||
unsigned attempts = 1;
|
||||
VkDeviceSize reclaimed = 0;
|
||||
VkDeviceSize released = 0;
|
||||
const auto exhausted = [](VkResult value) { return value == VK_ERROR_OUT_OF_DEVICE_MEMORY || value == VK_ERROR_OUT_OF_HOST_MEMORY; };
|
||||
const auto exhausted = [](VkResult value) { return value == VK_ERROR_OUT_OF_DEVICE_MEMORY || value == VK_ERROR_OUT_OF_HOST_MEMORY || value == VK_ERROR_TOO_MANY_OBJECTS; };
|
||||
if (exhausted(result)) {
|
||||
reclaimed = trim();
|
||||
if (reclaimed != 0) {
|
||||
@@ -73,6 +77,7 @@ VkDeviceMemory BufferPool::AllocateMemory(const VkMemoryAllocateInfo& allocation
|
||||
if (exhausted(result)) {
|
||||
if (cacheReclaim != nullptr) released = cacheReclaim(ReclaimTarget(allocation.allocationSize));
|
||||
if (released != 0) {
|
||||
reclaimed += trim();
|
||||
memory = VK_NULL_HANDLE;
|
||||
result = allocateMemory(device, &allocation, nullptr, &memory);
|
||||
++attempts;
|
||||
@@ -115,6 +120,102 @@ VkDeviceMemory BufferPool::AllocateMemory(const VkMemoryAllocateInfo& allocation
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkDeviceSize BufferPool::ImageBudget(VkDeviceSize deviceBudget) {
|
||||
static const char* value = std::getenv("APS5_IMAGE_POOL_MIB");
|
||||
if (value != nullptr) return static_cast<VkDeviceSize>(std::strtoull(value, nullptr, 10)) << 20u;
|
||||
return deviceBudget / 4u;
|
||||
}
|
||||
|
||||
bool BufferPool::imagePooled(VkDeviceSize bytes, std::uint32_t memoryType) const {
|
||||
return imageBudget != 0 && bytes != 0 && bytes <= imageBudget && bytes <= deviceTier.budget && memoryType < memoryProperties.memoryTypeCount && DeviceTiered(memoryProperties.memoryTypes[memoryType].propertyFlags);
|
||||
}
|
||||
|
||||
bool BufferPool::heapOverBudget() {
|
||||
if (memoryProperties2 == nullptr || physical == VK_NULL_HANDLE) return false;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (heapRead != std::chrono::steady_clock::time_point{} && now - heapRead < std::chrono::milliseconds(100)) return heapOver;
|
||||
VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceMemoryProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2};
|
||||
properties.pNext = &reported;
|
||||
memoryProperties2(physical, &properties);
|
||||
heapRead = now;
|
||||
heapOver = false;
|
||||
for (std::uint32_t heap = 0; heap < memoryProperties.memoryHeapCount && heap < VK_MAX_MEMORY_HEAPS; ++heap) {
|
||||
if ((memoryProperties.memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0 && reported.heapBudget[heap] != 0 && reported.heapUsage[heap] > reported.heapBudget[heap]) heapOver = true;
|
||||
}
|
||||
return heapOver;
|
||||
}
|
||||
|
||||
VkDeviceMemory BufferPool::TakeImageMemory(const VkMemoryAllocateInfo& allocation, const char* operation, std::optional<std::uint32_t> hostType, std::uint32_t* allocatedType) {
|
||||
const auto type = allocation.memoryTypeIndex;
|
||||
if (imagePooled(allocation.allocationSize, type)) {
|
||||
std::lock_guard lock(mutex);
|
||||
++clock;
|
||||
const auto found = deviceTier.free.find({static_cast<std::size_t>(allocation.allocationSize), 0, memoryProperties.memoryTypes[type].propertyFlags, type});
|
||||
if (found != deviceTier.free.end() && !found->second.empty()) {
|
||||
auto& slots = found->second;
|
||||
const auto memory = slots.back().allocation.memory;
|
||||
imageOrder.erase(slots.back().order);
|
||||
slots.pop_back();
|
||||
if (slots.empty()) deviceTier.free.erase(found);
|
||||
--deviceTier.slots;
|
||||
deviceTier.retainedBytes -= allocation.allocationSize;
|
||||
--imageSlots;
|
||||
imageBytes -= allocation.allocationSize;
|
||||
++imageHits;
|
||||
if (allocatedType != nullptr) *allocatedType = type;
|
||||
return memory;
|
||||
}
|
||||
++imageMisses;
|
||||
}
|
||||
return AllocateMemory(allocation, operation, hostType, allocatedType);
|
||||
}
|
||||
|
||||
void BufferPool::PutImageMemory(VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept {
|
||||
if (memory == VK_NULL_HANDLE) return;
|
||||
if (!imagePooled(bytes, memoryType)) {
|
||||
freeMemory(device, memory, nullptr);
|
||||
return;
|
||||
}
|
||||
BufferAllocation allocation{VK_NULL_HANDLE, memory, nullptr, 0, bytes, static_cast<std::size_t>(bytes), 0, memoryProperties.memoryTypes[memoryType].propertyFlags};
|
||||
allocation.memoryType = memoryType;
|
||||
std::vector<BufferAllocation> evicted;
|
||||
try {
|
||||
std::lock_guard lock(mutex);
|
||||
if (heapOverBudget()) {
|
||||
while (imageSlots != 0 && evictOldestImage(evicted)) {
|
||||
}
|
||||
evicted.push_back(allocation);
|
||||
} else {
|
||||
while (imageSlots != 0 && imageBytes + bytes > imageBudget && evictOldestImage(evicted)) {
|
||||
}
|
||||
while (deviceTier.slots != 0 && (deviceTier.retainedBytes + bytes > deviceTier.budget || deviceTier.slots >= maxSlots)) evictOldest(deviceTier, evicted);
|
||||
const SlotKey imageKey{allocation.bytes, 0, allocation.properties, memoryType};
|
||||
auto& slots = deviceTier.free[imageKey];
|
||||
slots.push_back({allocation, ++clock, {}});
|
||||
try {
|
||||
slots.back().order = imageOrder.insert(imageOrder.end(), &slots.back());
|
||||
} catch (...) {
|
||||
slots.pop_back();
|
||||
if (slots.empty()) deviceTier.free.erase(imageKey);
|
||||
throw;
|
||||
}
|
||||
++deviceTier.slots;
|
||||
deviceTier.retainedBytes += bytes;
|
||||
++imageSlots;
|
||||
imageBytes += bytes;
|
||||
}
|
||||
} catch (...) {
|
||||
destroy(allocation);
|
||||
}
|
||||
for (const auto& gone : evicted) destroy(gone);
|
||||
}
|
||||
|
||||
std::size_t BufferPool::RetainedImages() {
|
||||
std::lock_guard lock(mutex);
|
||||
return imageSlots;
|
||||
}
|
||||
|
||||
VkDeviceSize BufferPool::ReclaimTarget(VkDeviceSize bytes) {
|
||||
return bytes + std::max<VkDeviceSize>(bytes, VkDeviceSize{256} << 20u);
|
||||
}
|
||||
@@ -256,7 +357,7 @@ VkDeviceSize BufferPool::destroy(const BufferAllocation& allocation) noexcept {
|
||||
}
|
||||
// Device-local allocations (see DeviceBuffer) are never mapped.
|
||||
if (allocation.mapping != nullptr) unmap(device, allocation.memory);
|
||||
destroyBuffer(device, allocation.buffer, nullptr);
|
||||
if (allocation.buffer != VK_NULL_HANDLE) destroyBuffer(device, allocation.buffer, nullptr);
|
||||
freeMemory(device, allocation.memory, nullptr);
|
||||
return allocation.allocationBytes;
|
||||
}
|
||||
@@ -297,7 +398,7 @@ std::optional<BufferAllocation> BufferPool::Take(std::size_t bytes, VkBufferUsag
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now - lastReport > std::chrono::seconds(10)) {
|
||||
lastReport = now;
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[bufferpool] small: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB); large: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB)%s; device: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu)\n", static_cast<unsigned long long>(smallTier.hits), static_cast<unsigned long long>(smallTier.misses), static_cast<unsigned long long>(smallTier.evictions), smallTier.slots, smallTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(largeTier.hits), static_cast<unsigned long long>(largeTier.misses), static_cast<unsigned long long>(largeTier.evictions), largeTier.slots, largeTier.retainedBytes / 1048576.0, sharedTiers() ? " (shared)" : "", static_cast<unsigned long long>(deviceTier.hits), static_cast<unsigned long long>(deviceTier.misses), static_cast<unsigned long long>(deviceTier.evictions), deviceTier.slots, deviceTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(deviceTier.budget >> 20u));
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[bufferpool] small: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB); large: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB)%s; device: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu); images: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu)\n", static_cast<unsigned long long>(smallTier.hits), static_cast<unsigned long long>(smallTier.misses), static_cast<unsigned long long>(smallTier.evictions), smallTier.slots, smallTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(largeTier.hits), static_cast<unsigned long long>(largeTier.misses), static_cast<unsigned long long>(largeTier.evictions), largeTier.slots, largeTier.retainedBytes / 1048576.0, sharedTiers() ? " (shared)" : "", static_cast<unsigned long long>(deviceTier.hits), static_cast<unsigned long long>(deviceTier.misses), static_cast<unsigned long long>(deviceTier.evictions), deviceTier.slots, deviceTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(deviceTier.budget >> 20u), static_cast<unsigned long long>(imageHits), static_cast<unsigned long long>(imageMisses), static_cast<unsigned long long>(imageEvictions), imageSlots, imageBytes / 1048576.0, static_cast<unsigned long long>(imageBudget >> 20u));
|
||||
}
|
||||
}
|
||||
auto& tier = tierFor(capacity, properties);
|
||||
@@ -322,12 +423,37 @@ void BufferPool::evictOldest(Tier& tier, std::vector<BufferAllocation>& evicted)
|
||||
// First: a throw here leaves the slot retained and counted.
|
||||
evicted.push_back(oldest->second.front().allocation);
|
||||
tier.retainedBytes -= oldest->second.front().allocation.allocationBytes;
|
||||
if (oldest->second.front().allocation.buffer == VK_NULL_HANDLE) {
|
||||
imageOrder.erase(oldest->second.front().order);
|
||||
imageBytes -= oldest->second.front().allocation.allocationBytes;
|
||||
--imageSlots;
|
||||
++imageEvictions;
|
||||
}
|
||||
oldest->second.pop_front();
|
||||
if (oldest->second.empty()) tier.free.erase(oldest);
|
||||
--tier.slots;
|
||||
++tier.evictions;
|
||||
}
|
||||
|
||||
bool BufferPool::evictOldestImage(std::vector<BufferAllocation>& evicted) {
|
||||
if (imageOrder.empty()) return false;
|
||||
const Slot* oldest = imageOrder.front();
|
||||
const SlotKey key{oldest->allocation.bytes, 0, oldest->allocation.properties, oldest->allocation.memoryType};
|
||||
const auto found = deviceTier.free.find(key);
|
||||
evicted.push_back(oldest->allocation);
|
||||
imageOrder.pop_front();
|
||||
const auto bytes = oldest->allocation.allocationBytes;
|
||||
deviceTier.retainedBytes -= bytes;
|
||||
imageBytes -= bytes;
|
||||
found->second.pop_front();
|
||||
if (found->second.empty()) deviceTier.free.erase(found);
|
||||
--deviceTier.slots;
|
||||
--imageSlots;
|
||||
++deviceTier.evictions;
|
||||
++imageEvictions;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t BufferPool::MaxSlots() {
|
||||
// APS5_BUFFER_POOL_SLOTS=<n> bounds the retained allocations; 64 is the capacity the pool had
|
||||
// before least-recently-used retention, for A/B runs.
|
||||
@@ -351,6 +477,10 @@ VkDeviceSize BufferPool::trim() {
|
||||
tier->retainedBytes = 0;
|
||||
tier->slots = 0;
|
||||
}
|
||||
imageEvictions += imageSlots;
|
||||
imageBytes = 0;
|
||||
imageSlots = 0;
|
||||
imageOrder.clear();
|
||||
}
|
||||
VkDeviceSize bytes = 0;
|
||||
for (const auto* tier : {&retiredSmall, &retiredLarge, &retiredDevice}) {
|
||||
@@ -388,7 +518,7 @@ void BufferPool::Put(const BufferAllocation& allocation) noexcept {
|
||||
evicted.push_back(allocation);
|
||||
} else {
|
||||
while (tier.slots != 0 && (tier.retainedBytes + allocation.allocationBytes > tier.budget || tier.slots >= maxSlots)) evictOldest(tier, evicted);
|
||||
tier.free[{allocation.bytes, allocation.usage, allocation.properties}].push_back({allocation, ++clock});
|
||||
tier.free[{allocation.bytes, allocation.usage, allocation.properties}].push_back({allocation, ++clock, {}});
|
||||
++tier.slots;
|
||||
tier.retainedBytes += allocation.allocationBytes;
|
||||
}
|
||||
@@ -403,8 +533,17 @@ std::shared_ptr<BufferPool> GetBufferPool(const Context& context) {
|
||||
return context.bufferPool;
|
||||
}
|
||||
|
||||
VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation) {
|
||||
return GetBufferPool(context)->AllocateMemory(allocation, operation, context.RefusedDeviceMemoryFallback(mask, allocation.memoryTypeIndex));
|
||||
VkDeviceMemory AllocateImageMemory(const Context& context, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation, std::uint32_t* allocatedType) {
|
||||
return GetBufferPool(context)->TakeImageMemory(allocation, operation, context.RefusedDeviceMemoryFallback(mask, allocation.memoryTypeIndex), allocatedType);
|
||||
}
|
||||
|
||||
void ReleaseImageMemory(const Context& context, VkDeviceMemory memory, VkDeviceSize bytes, std::uint32_t memoryType) noexcept {
|
||||
if (memory == VK_NULL_HANDLE) return;
|
||||
if (context.bufferPool) {
|
||||
context.bufferPool->PutImageMemory(memory, bytes, memoryType);
|
||||
return;
|
||||
}
|
||||
context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ namespace AgcDriver::Graphics {
|
||||
namespace {
|
||||
|
||||
std::uint64_t depthWrites = 0;
|
||||
std::uint64_t mirrorEpoch = 0;
|
||||
|
||||
std::pair<std::uint32_t, std::uint32_t> DepthBlockExtent(std::uint32_t bytesPerTexel) {
|
||||
return {bytesPerTexel == 4 ? 128u : 256u, bytesPerTexel == 1 ? 256u : 128u};
|
||||
@@ -697,6 +698,43 @@ public:
|
||||
else Recorder::CountBarriers(Recorder::CommandClass::Copy, 3);
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> StoreDepthPlane(const GuestTextureResource& resource) {
|
||||
ApplyFastClear();
|
||||
if (mirrorPlane.storage == nullptr) {
|
||||
mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
++mirrorEpoch;
|
||||
}
|
||||
copyPlane(mirrorPlane, mirrorPlane.storage, false, true, true);
|
||||
return mirrorPlane.storage;
|
||||
}
|
||||
|
||||
bool SyncFromMemory(const GuestTextureResource& resource) {
|
||||
const auto bytes = static_cast<std::size_t>(DescribeSurface(resource).guestBytes);
|
||||
GuestMemory::CollectWrites(resource.baseAddress, bytes);
|
||||
if (syncedGeneration == 0 || (pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || !GuestMemory::WrittenSince(resource.baseAddress, bytes, syncedGeneration)) {
|
||||
syncedGeneration = GuestMemory::TrackerGeneration();
|
||||
return false;
|
||||
}
|
||||
memoryStale = false;
|
||||
if (mirrorPlane.storage == nullptr) {
|
||||
mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
++mirrorEpoch;
|
||||
}
|
||||
else static_cast<void>(mirrorPlane.storage->Refresh());
|
||||
syncedGeneration = GuestMemory::TrackerGeneration();
|
||||
copyPlane(mirrorPlane, mirrorPlane.storage, false, false, true);
|
||||
written = ++depthWrites;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DropMirror() {
|
||||
mirrorPlane = {};
|
||||
}
|
||||
|
||||
bool memoryStale = false;
|
||||
std::uint64_t scannedEpoch = 0;
|
||||
std::uint64_t syncedGeneration = 0;
|
||||
|
||||
const Context context;
|
||||
const DepthTarget target;
|
||||
const VkFormat hostFormat;
|
||||
@@ -893,6 +931,7 @@ private:
|
||||
std::uint64_t stencilBytes() const {
|
||||
return DepthSliceBytes(target.extent, 1u);
|
||||
}
|
||||
PlaneStorage mirrorPlane;
|
||||
|
||||
bool CoversPlane(const StorageTexture& storage, bool stencil) const {
|
||||
const auto& resource = storage.Descriptor();
|
||||
@@ -904,15 +943,17 @@ private:
|
||||
return ResolveTextureFormat(resource.format) == (stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT);
|
||||
}
|
||||
|
||||
void copyPlane(PlaneStorage& plane, const std::shared_ptr<StorageTexture>& storage, bool stencil, bool toStorage) {
|
||||
void copyPlane(PlaneStorage& plane, const std::shared_ptr<StorageTexture>& storage, bool stencil, bool toStorage, bool ownBatch = false) {
|
||||
auto* recorder = Recorder::Active();
|
||||
Require(recorder != nullptr, "depth surface storage copies need an active recorder");
|
||||
Require(recorder != nullptr || ownBatch, "depth surface storage copies need an active recorder");
|
||||
std::unique_ptr<CommandBatch> batch;
|
||||
if (recorder == nullptr) batch = std::make_unique<CommandBatch>(context);
|
||||
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
const auto bytes = static_cast<std::size_t>(target.extent.width) * target.extent.height * (stencil ? 1u : d16 ? 2u : 4u);
|
||||
if (plane.staging == nullptr) plane.staging = std::make_unique<DeviceBuffer>(context, bytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
const auto commands = recorder->Commands();
|
||||
recorder->Keep(storage);
|
||||
const auto timing = recorder->BeginGpuTiming(Recorder::CommandClass::Copy);
|
||||
const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle();
|
||||
if (recorder != nullptr) recorder->Keep(storage);
|
||||
const std::uint32_t timing = recorder != nullptr ? recorder->BeginGpuTiming(Recorder::CommandClass::Copy) : 0u;
|
||||
VkBufferImageCopy planeRegion{0, 0, 0, {stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}, {0, 0, 0}, {target.extent.width, target.extent.height, 1}};
|
||||
VkBufferImageCopy colorRegion = planeRegion;
|
||||
colorRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
@@ -923,6 +964,10 @@ private:
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, plane.staging->Handle(), destinationImage, VK_IMAGE_LAYOUT_GENERAL, 1, toStorage ? &colorRegion : &planeRegion);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
|
||||
if (batch) {
|
||||
batch->SubmitAndWait();
|
||||
return;
|
||||
}
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Copy, 3);
|
||||
recorder->EndGpuTiming(timing, bytes);
|
||||
}
|
||||
@@ -931,6 +976,7 @@ private:
|
||||
depthPlane = {};
|
||||
stencilPlane = {};
|
||||
releaseStencilCopy();
|
||||
mirrorPlane = {};
|
||||
textures.clear();
|
||||
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
|
||||
if (owned != nullptr) owned.reset();
|
||||
@@ -1223,8 +1269,8 @@ bool sameSurface(const DepthTarget& a, const DepthTarget& b) {
|
||||
return a.address == b.address && a.stencilAddress == b.stencilAddress && a.extent.width == b.extent.width && a.extent.height == b.extent.height && a.format == b.format && a.samples == b.samples && (a.samples == 1u || (a.htileAddress == b.htileAddress && a.htileStencil == b.htileStencil && a.depthSwizzle == b.depthSwizzle && a.pipeBankXor == b.pipeBankXor && a.firstSlice == b.firstSlice && a.lastSlice == b.lastSlice && a.iterate256 == b.iterate256 && a.htilePipeAligned == b.htilePipeAligned && a.htileVrsEncoding == b.htileVrsEncoding && a.htileDepthOnly == b.htileDepthOnly));
|
||||
}
|
||||
|
||||
std::mutex& surfacesMutex() {
|
||||
static std::mutex mutex;
|
||||
std::recursive_mutex& surfacesMutex() {
|
||||
static std::recursive_mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
@@ -1296,13 +1342,6 @@ DepthSurface* planeSurface(VkDevice device, std::uint64_t address) {
|
||||
return found == list.rend() ? nullptr : found->get();
|
||||
}
|
||||
|
||||
void retire(DepthSurface& surface) {
|
||||
surface.retired = true;
|
||||
for (auto* owners : {&depthOwners(), &stencilOwners()}) {
|
||||
std::erase_if(*owners, [&](const auto& entry) { return entry.second.surface == &surface; });
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::uint64_t, std::size_t>& clearedHtiles() {
|
||||
static auto* cleared = new std::map<std::uint64_t, std::size_t>();
|
||||
return *cleared;
|
||||
@@ -1470,6 +1509,84 @@ VkImageAspectFlags storeBackClearedHtile(const Context& context, const DepthTarg
|
||||
htileReads()[target.htileAddress] = generation;
|
||||
return cleared;
|
||||
}
|
||||
|
||||
struct StalePlanes {
|
||||
std::mutex mutex;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges;
|
||||
std::atomic<std::uint64_t> begin{std::numeric_limits<std::uint64_t>::max()};
|
||||
std::atomic<std::uint64_t> end{0};
|
||||
};
|
||||
|
||||
StalePlanes& stalePlanes() {
|
||||
static auto* planes = new StalePlanes();
|
||||
return *planes;
|
||||
}
|
||||
|
||||
void publishStaleHull(StalePlanes& planes) {
|
||||
auto begin = std::numeric_limits<std::uint64_t>::max();
|
||||
std::uint64_t end = 0;
|
||||
for (const auto& range : planes.ranges) {
|
||||
begin = std::min(begin, range.first);
|
||||
end = std::max(end, range.second);
|
||||
}
|
||||
planes.begin.store(begin, std::memory_order_release);
|
||||
planes.end.store(end, std::memory_order_release);
|
||||
}
|
||||
|
||||
void noteStalePlane(std::uint64_t begin, std::uint64_t end) {
|
||||
auto& planes = stalePlanes();
|
||||
std::lock_guard lock(planes.mutex);
|
||||
if (std::find(planes.ranges.begin(), planes.ranges.end(), std::pair{begin, end}) != planes.ranges.end()) return;
|
||||
planes.ranges.emplace_back(begin, end);
|
||||
publishStaleHull(planes);
|
||||
}
|
||||
|
||||
bool overlapsStalePlane(std::uint64_t begin, std::uint64_t end) {
|
||||
auto& planes = stalePlanes();
|
||||
if (begin >= planes.end.load(std::memory_order_acquire) || planes.begin.load(std::memory_order_acquire) >= end) return false;
|
||||
std::lock_guard lock(planes.mutex);
|
||||
return std::any_of(planes.ranges.begin(), planes.ranges.end(), [&](const auto& range) { return begin < range.second && range.first < end; });
|
||||
}
|
||||
|
||||
bool mirrorable(const DepthTarget& target) {
|
||||
return (target.format == VK_FORMAT_D32_SFLOAT || target.format == VK_FORMAT_D32_SFLOAT_S8_UINT) && target.samples == 1 && target.depthSwizzle == XorSwizzleMode(TextureTileMode::kZ64KBX);
|
||||
}
|
||||
|
||||
GuestTextureResource mirrorResource(const DepthTarget& target) {
|
||||
GuestTextureResource resource{};
|
||||
resource.baseAddress = target.address;
|
||||
resource.width = target.extent.width;
|
||||
resource.height = target.extent.height;
|
||||
resource.mipCount = 1;
|
||||
resource.allocatedMipCount = 1;
|
||||
resource.tileMode = TextureTileMode::kZ64KBX;
|
||||
resource.dimension = TextureDimension::k2D;
|
||||
resource.format = 22;
|
||||
resource.dstSelX = 4;
|
||||
resource.dstSelY = 5;
|
||||
resource.dstSelZ = 6;
|
||||
resource.dstSelW = 7;
|
||||
return resource;
|
||||
}
|
||||
|
||||
std::pair<std::uint64_t, std::uint64_t> stalePlaneRange(const DepthSurface& surface) {
|
||||
return {surface.target.address, surface.target.address + DescribeSurface(mirrorResource(surface.target)).guestBytes};
|
||||
}
|
||||
|
||||
void retire(DepthSurface& surface) {
|
||||
surface.retired = true;
|
||||
for (auto* owners : {&depthOwners(), &stencilOwners()}) {
|
||||
std::erase_if(*owners, [&](const auto& entry) { return entry.second.surface == &surface; });
|
||||
}
|
||||
if (!surface.memoryStale) return;
|
||||
surface.memoryStale = false;
|
||||
const auto plane = stalePlaneRange(surface);
|
||||
if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& other) { return other->memoryStale && stalePlaneRange(*other) == plane; })) return;
|
||||
auto& planes = stalePlanes();
|
||||
std::lock_guard lock(planes.mutex);
|
||||
std::erase(planes.ranges, plane);
|
||||
publishStaleHull(planes);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t DepthSurfacesSerial() {
|
||||
@@ -1574,7 +1691,7 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const
|
||||
return constant * 256.0f;
|
||||
}
|
||||
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target) {
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten) {
|
||||
const auto htileCleared = RoutesToNativeDepthOwner(target) ? VkImageAspectFlags{0} : storeBackClearedHtile(context, target);
|
||||
std::lock_guard gpu(GuestMemory::GpuMutex());
|
||||
std::unique_lock lock(surfacesMutex());
|
||||
@@ -1609,6 +1726,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
|
||||
releaseRetired(context);
|
||||
surfacesSerial.fetch_add(1, std::memory_order_release);
|
||||
surfaces().push_back(std::make_unique<DepthSurface>(context, target, std::move(sampling)));
|
||||
++mirrorEpoch;
|
||||
auto& fresh = *surfaces().back();
|
||||
if (target.samples != 1u && fresh.sampleable && fresh.sampling.refusal.empty()) {
|
||||
std::unique_lock guardLock(depthGuardMutex());
|
||||
@@ -1631,14 +1749,82 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target)
|
||||
if (!created) surface->NoteHtileClear(VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
}
|
||||
if (!created) surface->NoteHtileClear(htileCleared);
|
||||
if (mirrorable(target)) static_cast<void>(surface->SyncFromMemory(mirrorResource(target)));
|
||||
surface->ApplyFastClear();
|
||||
if (drawnDepth != nullptr && drawnDepth != surface) drawnDepth->written = ++depthWrites;
|
||||
surface->written = ++depthWrites;
|
||||
surface->NoteMemory();
|
||||
if (mirrorable(target) && surface->scannedEpoch != mirrorEpoch) {
|
||||
surface->scannedEpoch = mirrorEpoch;
|
||||
const auto plane = stalePlaneRange(*surface);
|
||||
for (const auto& other : surfaces()) {
|
||||
if (other.get() == surface || other->context.device != context.device || other->written >= surface->written || !mirrorable(other->target)) continue;
|
||||
const auto otherPlane = stalePlaneRange(*other);
|
||||
if (otherPlane == plane || otherPlane.first >= plane.second || plane.first >= otherPlane.second) continue;
|
||||
other->memoryStale = false;
|
||||
other->DropMirror();
|
||||
}
|
||||
}
|
||||
if ((depthWritten || cleared) && mirrorable(target)) {
|
||||
surface->memoryStale = true;
|
||||
const auto plane = target.address;
|
||||
noteStalePlane(plane, plane + DescribeSurface(mirrorResource(target)).guestBytes);
|
||||
}
|
||||
drawnDepth = surface;
|
||||
return surface->view;
|
||||
}
|
||||
|
||||
bool DepthPlanesStale() {
|
||||
auto& planes = stalePlanes();
|
||||
return planes.begin.load(std::memory_order_acquire) < planes.end.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint64_t address, std::size_t bytes) {
|
||||
static thread_local bool flushing = false;
|
||||
const auto end = bytes > std::numeric_limits<std::uint64_t>::max() - address ? std::numeric_limits<std::uint64_t>::max() : address + bytes;
|
||||
if (flushing || !overlapsStalePlane(address, end)) return {};
|
||||
flushing = true;
|
||||
struct Reset {
|
||||
~Reset() { flushing = false; }
|
||||
} reset;
|
||||
std::vector<std::pair<DepthSurface*, std::shared_ptr<StorageTexture>>> stored;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges;
|
||||
{
|
||||
std::lock_guard gpu(GuestMemory::GpuMutex());
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> remaining;
|
||||
for (const auto& surface : surfaces()) {
|
||||
if (!surface->memoryStale) continue;
|
||||
const auto resource = mirrorResource(surface->target);
|
||||
const auto plane = surface->target.address;
|
||||
const auto planeBytes = DescribeSurface(resource).guestBytes;
|
||||
if (plane >= end || address >= plane + planeBytes) {
|
||||
remaining.emplace_back(plane, plane + planeBytes);
|
||||
continue;
|
||||
}
|
||||
surface->memoryStale = false;
|
||||
if (surface->SyncFromMemory(resource)) continue;
|
||||
stored.emplace_back(surface.get(), surface->StoreDepthPlane(resource));
|
||||
ranges.emplace_back(plane, planeBytes);
|
||||
}
|
||||
auto& planes = stalePlanes();
|
||||
std::lock_guard staleLock(planes.mutex);
|
||||
planes.ranges = std::move(remaining);
|
||||
publishStaleHull(planes);
|
||||
}
|
||||
for (const auto& entry : stored) entry.second->MarkWholeWritten();
|
||||
for (const auto& [plane, planeBytes] : ranges) StorageTexture::FlushPending(plane, static_cast<std::size_t>(planeBytes), nullptr, "depth plane store");
|
||||
const auto generation = GuestMemory::TrackerGeneration();
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
for (const auto& entry : stored) {
|
||||
if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& candidate) { return candidate.get() == entry.first; })) {
|
||||
entry.first->syncedGeneration = generation;
|
||||
entry.first->NoteMemory();
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
std::uint64_t HtileDepthClearAddress(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const std::array<std::uint32_t, 3>& numThreads) {
|
||||
static constexpr std::array<std::uint32_t, 17> htileMaskKernel{0xd7460000u, 0x04010c06u, 0x34000084u, 0xdc388000u, 0x04020000u, 0xbf8c3f70u, 0xd7710008u, 0x00120805u, 0xd7710009u, 0x00120a05u, 0xd771000au, 0x00120c05u, 0xd771000bu, 0x00120e05u, 0xdc788000u, 0x00020800u, 0xbf810000u};
|
||||
if (userData.size() < 6 || numThreads[0] != 64 || numThreads[1] != 1 || numThreads[2] != 1) return 0;
|
||||
|
||||
@@ -2211,6 +2211,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
}
|
||||
APS5_LOG_OUT_DEBUG("Creating color target %zu address=0x%llx bytes=%llu extent=%ux%u", index, static_cast<unsigned long long>(color.address), static_cast<unsigned long long>(color.bytes), color.extent.width, color.extent.height);
|
||||
const ColorTargetLayout colorLayout(color.extent.width, color.extent.height, color.tileMode, color.elementBytes);
|
||||
static_cast<void>(FlushDepthPlanes(color.address, colorLayout.Bytes()));
|
||||
RetireDepthSurfaces(context.device, color.address, colorLayout.Bytes());
|
||||
constexpr VkBufferUsageFlags copies = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
timer.phase(PhaseSetup);
|
||||
@@ -2287,7 +2288,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
binding.target = std::make_unique<RenderTarget>(context, color, state.blends.at(color.exportIndex).blendEnable != 0);
|
||||
targetViews.push_back(binding.target->View());
|
||||
}
|
||||
if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth));
|
||||
if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite));
|
||||
timer.phase(PhasePrepare);
|
||||
const bool recordDraws = RecordDraws();
|
||||
auto* recorder = Recorder::Active();
|
||||
@@ -2842,7 +2843,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con
|
||||
}
|
||||
if (recipe.hasDepth) {
|
||||
timer.phase(PhaseSetup);
|
||||
const auto depthView = DepthSurfaceView(context, *state.depth);
|
||||
const auto depthView = DepthSurfaceView(context, *state.depth, state.depthWrite);
|
||||
timer.phase(PhaseReadTarget);
|
||||
if (recipe.targetViews.back() != depthView) return miss(DrawRecipeMiss::TargetGone);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,7 @@ GpuColorTransfer::GpuColorTransfer(const Context& context) : context(context) {
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &push;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout color transfer");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(COLOR_TRANSFER_SPV);
|
||||
moduleInfo.pCode = COLOR_TRANSFER_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule color transfer");
|
||||
module = CreateShaderModule(context, COLOR_TRANSFER_SPV, sizeof(COLOR_TRANSFER_SPV), "vkCreateShaderModule color transfer");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "ThreadOwned.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
@@ -527,7 +528,7 @@ VkResult createImportBuffer(const Context& context, VkExternalMemoryHandleTypeFl
|
||||
const char* bindImport(const Context& context, HostImport& entry, VkExternalMemoryHandleTypeFlagBits handleType, const void* import, std::uint32_t importTypes, bool& allocated, VkResult& failure) {
|
||||
const auto failed = [&](const char* step, VkResult result) -> const char* {
|
||||
if (entry.buffer) context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, entry.buffer, nullptr);
|
||||
if (entry.memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, entry.memory, nullptr);
|
||||
if (allocated && entry.memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, entry.memory, nullptr);
|
||||
entry.buffer = VK_NULL_HANDLE;
|
||||
entry.memory = VK_NULL_HANDLE;
|
||||
failure = result;
|
||||
@@ -544,7 +545,9 @@ const char* bindImport(const Context& context, HostImport& entry, VkExternalMemo
|
||||
ImportFamilies families;
|
||||
if (handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) shareDmaBufImport(context, info, families, false);
|
||||
#endif
|
||||
if (const auto result = context.Function<PFN_vkCreateBuffer>("vkCreateBuffer")(context.device, &info, nullptr, &entry.buffer); result != VK_SUCCESS) return failed("vkCreateBuffer", result);
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
if (const auto result = context.Function<PFN_vkCreateBuffer>("vkCreateBuffer")(context.device, &info, nullptr, &buffer); result != VK_SUCCESS) return failed("vkCreateBuffer", result);
|
||||
entry.buffer = buffer;
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetBufferMemoryRequirements>("vkGetBufferMemoryRequirements")(context.device, entry.buffer, &requirements);
|
||||
const auto types = requirements.memoryTypeBits & importTypes;
|
||||
@@ -4552,6 +4555,7 @@ void GuestBufferMemory::UploadFinish(bool addressable) {
|
||||
for (const auto& region : regions) CompletePendingCpuWrites(context, region.begin, static_cast<std::size_t>(region.end - region.begin));
|
||||
if (space != nullptr) completeSpaceWrites();
|
||||
uploaded = true;
|
||||
for (const auto& region : regions) static_cast<void>(FlushDepthPlanes(region.begin, static_cast<std::size_t>(region.end - region.begin)));
|
||||
for (const auto& [begin, end] : writes) StorageTexture::FlushPending(begin, static_cast<std::size_t>(end - begin), nullptr, "written buffer range");
|
||||
if (context.hostImportAlignment != 0) {
|
||||
auto& state = Imports();
|
||||
|
||||
@@ -46,10 +46,8 @@ VkPipeline TextureDetiler::multisampleReadbackPipeline(std::uint32_t elementByte
|
||||
}
|
||||
auto& module = readbackModules[depth ? 1u : 0u];
|
||||
if (module == VK_NULL_HANDLE) {
|
||||
VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
info.codeSize = depth ? sizeof(MULTISAMPLE_READBACK_DEPTH_SPV) : sizeof(MULTISAMPLE_READBACK_UINT_SPV);
|
||||
info.pCode = depth ? MULTISAMPLE_READBACK_DEPTH_SPV : MULTISAMPLE_READBACK_UINT_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &info, nullptr, &module), "vkCreateShaderModule multisample readback");
|
||||
module = depth ? CreateShaderModule(context, MULTISAMPLE_READBACK_DEPTH_SPV, sizeof(MULTISAMPLE_READBACK_DEPTH_SPV), "vkCreateShaderModule multisample readback")
|
||||
: CreateShaderModule(context, MULTISAMPLE_READBACK_UINT_SPV, sizeof(MULTISAMPLE_READBACK_UINT_SPV), "vkCreateShaderModule multisample readback");
|
||||
}
|
||||
const VkSpecializationMapEntry entry{0u, 0u, sizeof(elementBytes)};
|
||||
const VkSpecializationInfo specialization{1u, &entry, sizeof(elementBytes), &elementBytes};
|
||||
|
||||
@@ -359,10 +359,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
||||
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT meshSubgroup{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, nullptr, 32u};
|
||||
for (std::uint32_t i = 0; i < shaders.size(); ++i) {
|
||||
const auto& shader = *shaders[i].program;
|
||||
VkShaderModuleCreateInfo module{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
module.codeSize = shader.spirv.size() * sizeof(std::uint32_t);
|
||||
module.pCode = shader.spirv.data();
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &module, nullptr, &_modules[i]), "vkCreateShaderModule graphics");
|
||||
_modules[i] = CreateShaderModule(context, shader.spirv.data(), shader.spirv.size() * sizeof(std::uint32_t), "vkCreateShaderModule graphics");
|
||||
const auto stage = VulkanStage(shaders[i].stage);
|
||||
stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stages[i].stage = stage;
|
||||
|
||||
@@ -44,7 +44,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel
|
||||
constexpr std::uint32_t FileMagic = 0x4c4a5041u;
|
||||
// Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the
|
||||
// layout guards above do not catch (a field renumbered or reinterpreted at the same size).
|
||||
constexpr std::uint32_t FileFormat = 4u;
|
||||
constexpr std::uint32_t FileFormat = 5u;
|
||||
constexpr std::uint32_t ModuleRecord = 1u;
|
||||
constexpr std::uint32_t GraphicsRecord = 2u;
|
||||
constexpr std::uint32_t ComputeRecord = 3u;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libkernel/Time/include/SleepWake.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
@@ -1083,6 +1084,60 @@ void FlushForAccess(std::uint64_t address, std::size_t bytes) {
|
||||
|
||||
}
|
||||
|
||||
struct Recorder::ProgressWatcher {
|
||||
std::mutex mutex;
|
||||
std::condition_variable wake;
|
||||
std::uint64_t submitted = 0;
|
||||
bool stop = false;
|
||||
std::thread thread;
|
||||
|
||||
ProgressWatcher(VkDevice device, VkSemaphore timeline, PFN_vkWaitSemaphoresKHR waitSemaphores, PFN_vkGetSemaphoreCounterValueKHR counterValue) {
|
||||
thread = std::thread([this, device, timeline, waitSemaphores, counterValue] {
|
||||
CpuTopology::PinHelperThread("GPU progress watcher");
|
||||
std::uint64_t seen = 0;
|
||||
for (;;) {
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
wake.wait(lock, [&] { return stop || submitted > seen; });
|
||||
if (stop) return;
|
||||
}
|
||||
const std::uint64_t target = seen + 1;
|
||||
VkSemaphoreWaitInfoKHR info{VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR};
|
||||
info.semaphoreCount = 1;
|
||||
info.pSemaphores = &timeline;
|
||||
info.pValues = ⌖
|
||||
const auto result = waitSemaphores(device, &info, 50'000'000ull);
|
||||
if (result == VK_TIMEOUT) continue;
|
||||
if (result != VK_SUCCESS) {
|
||||
std::fprintf(stderr, "[gpu] progress watcher: vkWaitSemaphoresKHR failed (%d), watcher stops\n", static_cast<int>(result));
|
||||
return;
|
||||
}
|
||||
std::uint64_t value = target;
|
||||
if (counterValue(device, timeline, &value) != VK_SUCCESS) value = target;
|
||||
seen = std::max(target, value);
|
||||
KernelWakeSleepers_nid_no_patch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Submitted(std::uint64_t serial) {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
submitted = serial;
|
||||
}
|
||||
wake.notify_one();
|
||||
}
|
||||
|
||||
~ProgressWatcher() {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
stop = true;
|
||||
}
|
||||
wake.notify_one();
|
||||
thread.join();
|
||||
}
|
||||
};
|
||||
|
||||
Recorder::Recorder(const Context& context, bool timelineSemaphores) : context(context), id(nextRecorderId.fetch_add(1)) {
|
||||
{
|
||||
std::lock_guard lock(liveRecordersMutex);
|
||||
@@ -1129,6 +1184,7 @@ Recorder::~Recorder() {
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "[gpu] recorder teardown: %s\n", error.what());
|
||||
}
|
||||
progressWatcher.reset();
|
||||
holdKept.clear();
|
||||
if (activeRecorder == this) {
|
||||
activeRecorder = nullptr;
|
||||
@@ -1676,6 +1732,18 @@ bool Recorder::closeStoreRun(bool atSubmit) {
|
||||
std::vector<QueuedWrite> writes;
|
||||
writes.reserve(stores.size());
|
||||
for (const auto& store : stores) writes.push_back({store.address, store.End(), store.dumpTarget != 0});
|
||||
if (const auto order = QueuedWriteOrder(writes); !std::is_sorted(order.begin(), order.end())) {
|
||||
std::vector<Batch::StoreRun::Queued> ordered;
|
||||
std::vector<QueuedWrite> orderedWrites;
|
||||
ordered.reserve(stores.size());
|
||||
orderedWrites.reserve(writes.size());
|
||||
for (const auto index : order) {
|
||||
ordered.push_back(std::move(stores[index]));
|
||||
orderedWrites.push_back(writes[index]);
|
||||
}
|
||||
stores = std::move(ordered);
|
||||
writes = std::move(orderedWrites);
|
||||
}
|
||||
const bool dumps = std::any_of(writes.begin(), writes.end(), [](const QueuedWrite& write) { return write.computed; });
|
||||
if (dumps) recordQueuedDumps(commands, stores);
|
||||
std::size_t first = 0;
|
||||
@@ -1981,8 +2049,8 @@ void Recorder::EndGpuTiming(std::uint32_t index, std::uint64_t bytes) {
|
||||
context.Function<PFN_vkCmdWriteTimestamp>("vkCmdWriteTimestamp")(open->commands, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, open->queries, index * 2 + 1);
|
||||
}
|
||||
|
||||
bool Recorder::RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments) {
|
||||
constexpr std::uint32_t PushBytes = 32;
|
||||
bool Recorder::RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress source, VkDeviceAddress arguments, const std::array<std::uint32_t, 3>* threadGroup) {
|
||||
constexpr std::uint32_t PushBytes = 48;
|
||||
if (dispatchArgumentState == 0) {
|
||||
dispatchArgumentState = -1;
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
@@ -1992,10 +2060,7 @@ bool Recorder::RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &push;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &dispatchArgumentLayout), "vkCreatePipelineLayout dispatch arguments");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(DISPATCH_ARGUMENTS_SPV);
|
||||
moduleInfo.pCode = DISPATCH_ARGUMENTS_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule dispatch arguments");
|
||||
module = CreateShaderModule(context, DISPATCH_ARGUMENTS_SPV, sizeof(DISPATCH_ARGUMENTS_SPV), "vkCreateShaderModule dispatch arguments");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
@@ -2018,11 +2083,15 @@ bool Recorder::RecordDispatchArguments(VkCommandBuffer commands, VkDeviceAddress
|
||||
VkDeviceAddress source;
|
||||
VkDeviceAddress arguments;
|
||||
std::array<std::uint32_t, 4> limits;
|
||||
} parameters{source, arguments, {limit[0], limit[1], limit[2], 0u}};
|
||||
std::array<std::uint32_t, 4> group;
|
||||
} parameters{source, arguments, {limit[0], limit[1], limit[2], threadGroup != nullptr ? 1u : 0u}, {1u, 1u, 1u, 0u}};
|
||||
if (threadGroup != nullptr) std::copy(threadGroup->begin(), threadGroup->end(), parameters.group.begin());
|
||||
static_assert(sizeof(parameters) == PushBytes);
|
||||
function(cmdPushConstants, "vkCmdPushConstants")(commands, dispatchArgumentLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters);
|
||||
function(cmdDispatch, "vkCmdDispatch")(commands, 1, 1, 1);
|
||||
recordBarrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT);
|
||||
const VkPipelineStageFlags readers = VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | (threadGroup != nullptr ? VK_PIPELINE_STAGE_TRANSFER_BIT : 0u);
|
||||
const VkAccessFlags reads = VK_ACCESS_INDIRECT_COMMAND_READ_BIT | (threadGroup != nullptr ? VK_ACCESS_TRANSFER_READ_BIT : 0u);
|
||||
recordBarrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, readers, VK_ACCESS_SHADER_WRITE_BIT, reads);
|
||||
CountBarriers(CommandClass::DispatchLeading, 2);
|
||||
return true;
|
||||
}
|
||||
@@ -2075,10 +2144,7 @@ bool Recorder::gpuSampleCounter() {
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &push;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &sampleLayout), "vkCreatePipelineLayout sample counter");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(SAMPLE_COUNTER_SPV);
|
||||
moduleInfo.pCode = SAMPLE_COUNTER_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule sample counter");
|
||||
module = CreateShaderModule(context, SAMPLE_COUNTER_SPV, sizeof(SAMPLE_COUNTER_SPV), "vkCreateShaderModule sample counter");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
@@ -2094,9 +2160,7 @@ bool Recorder::gpuSampleCounter() {
|
||||
const VkPushConstantRange dumpPush{VK_SHADER_STAGE_COMPUTE_BIT, 0, SampleDumpPushBytes};
|
||||
layoutInfo.pPushConstantRanges = &dumpPush;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &dumpLayout), "vkCreatePipelineLayout sample dumps");
|
||||
moduleInfo.codeSize = sizeof(SAMPLE_DUMPS_SPV);
|
||||
moduleInfo.pCode = SAMPLE_DUMPS_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule sample dumps");
|
||||
module = CreateShaderModule(context, SAMPLE_DUMPS_SPV, sizeof(SAMPLE_DUMPS_SPV), "vkCreateShaderModule sample dumps");
|
||||
pipelineInfo.stage.module = module;
|
||||
pipelineInfo.layout = dumpLayout;
|
||||
{
|
||||
@@ -2125,6 +2189,56 @@ std::uint64_t Recorder::Batch::StoreRun::Queued::End() const {
|
||||
return address + (dumpTarget != 0 ? SampleDumpBytes : bytes.size());
|
||||
}
|
||||
|
||||
std::vector<std::size_t> Recorder::QueuedWriteOrder(std::span<const QueuedWrite> writes) {
|
||||
std::vector<std::size_t> order(writes.size());
|
||||
for (std::size_t i = 0; i < order.size(); ++i) order[i] = i;
|
||||
std::vector<bool> coveredLater(writes.size()), overLater(writes.size());
|
||||
std::map<std::uint64_t, std::uint64_t> later;
|
||||
for (auto i = writes.size(); i-- > 0;) {
|
||||
const auto& write = writes[i];
|
||||
if (!write.computed) {
|
||||
const auto after = later.upper_bound(write.begin);
|
||||
coveredLater[i] = after != later.begin() && std::prev(after)->second >= write.end;
|
||||
const auto last = later.lower_bound(write.end);
|
||||
overLater[i] = last != later.begin() && std::prev(last)->second > write.begin;
|
||||
continue;
|
||||
}
|
||||
auto begin = write.begin, end = write.end;
|
||||
auto at = later.lower_bound(begin);
|
||||
if (at != later.begin() && std::prev(at)->second >= begin) --at;
|
||||
while (at != later.end() && at->first <= end) {
|
||||
begin = std::min(begin, at->first);
|
||||
end = std::max(end, at->second);
|
||||
at = later.erase(at);
|
||||
}
|
||||
later.emplace(begin, end);
|
||||
}
|
||||
std::vector<std::size_t> before, dumps, after;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> earlier, delayed;
|
||||
for (std::size_t i = 0; i < writes.size(); ++i) {
|
||||
const auto& write = writes[i];
|
||||
const auto over = [&](const std::vector<std::pair<std::uint64_t, std::uint64_t>>& ranges) {
|
||||
return std::any_of(ranges.begin(), ranges.end(), [&](const auto& range) { return write.begin < range.second && range.first < write.end; });
|
||||
};
|
||||
if (write.computed) {
|
||||
dumps.push_back(i);
|
||||
earlier.emplace_back(write.begin, write.end);
|
||||
} else if (dumps.empty()) {
|
||||
before.push_back(i);
|
||||
} else if (overLater[i]) {
|
||||
if (over(delayed) || (!coveredLater[i] && over(earlier))) return order;
|
||||
before.push_back(i);
|
||||
} else {
|
||||
after.push_back(i);
|
||||
delayed.emplace_back(write.begin, write.end);
|
||||
}
|
||||
}
|
||||
order = std::move(before);
|
||||
order.insert(order.end(), dumps.begin(), dumps.end());
|
||||
order.insert(order.end(), after.begin(), after.end());
|
||||
return order;
|
||||
}
|
||||
|
||||
std::vector<std::size_t> Recorder::QueuedWriteGroups(std::span<const QueuedWrite> writes) {
|
||||
std::vector<std::size_t> ends;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> computed;
|
||||
@@ -2391,10 +2505,7 @@ bool Recorder::RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress rec
|
||||
layoutInfo.pushConstantRangeCount = 1;
|
||||
layoutInfo.pPushConstantRanges = &push;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &meshArgumentLayout), "vkCreatePipelineLayout mesh arguments");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(MESH_ARGUMENTS_SPV);
|
||||
moduleInfo.pCode = MESH_ARGUMENTS_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule mesh arguments");
|
||||
module = CreateShaderModule(context, MESH_ARGUMENTS_SPV, sizeof(MESH_ARGUMENTS_SPV), "vkCreateShaderModule mesh arguments");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
@@ -3494,6 +3605,13 @@ void Recorder::Submit() {
|
||||
newestSubmitted = batch->serial;
|
||||
newestSubmittedAt = batch->submittedAt;
|
||||
}
|
||||
if (timeline != VK_NULL_HANDLE) {
|
||||
if (!progressWatcher) {
|
||||
progressWatcher = std::make_unique<ProgressWatcher>(context.device, timeline, context.Function<PFN_vkWaitSemaphoresKHR>("vkWaitSemaphoresKHR"),
|
||||
context.Function<PFN_vkGetSemaphoreCounterValueKHR>("vkGetSemaphoreCounterValueKHR"));
|
||||
}
|
||||
progressWatcher->Submitted(batch->serial);
|
||||
}
|
||||
inFlight.push_back(std::move(batch));
|
||||
inFlightKeptBytes += inFlight.back()->keptBytes;
|
||||
if (activeRecorder == this) pendingLabelSince.store(NoPendingLabel, std::memory_order_release);
|
||||
@@ -3989,6 +4107,7 @@ void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source, bool
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!batch->completions.empty()) KernelWakeSleepers_nid_no_patch();
|
||||
if (profile) holdCounters.completions += batch->completions.size();
|
||||
const auto keptStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
// Deferred only from inside a hold: the unlock that releases the list is this thread's own, and
|
||||
|
||||
@@ -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
|
||||
@@ -723,6 +724,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
auto& counters = TextureCounts();
|
||||
const auto address = resource.baseAddress;
|
||||
const auto bytes = static_cast<std::size_t>(guestBytes);
|
||||
static_cast<void>(FlushDepthPlanes(address, bytes));
|
||||
// A storage image whose results for this surface (or for a mip chain containing it) are still on
|
||||
// the GPU supplies the texture by a view of it; anything else needs those results in guest
|
||||
// memory first.
|
||||
@@ -864,18 +866,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 +902,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 +937,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);
|
||||
@@ -1117,6 +1123,7 @@ std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std
|
||||
reportGuestAllocation(viewed, viewedBytes);
|
||||
}
|
||||
}
|
||||
if (DepthPlanesStale() || written) static_cast<void>(FlushDepthPlanes(viewed.baseAddress, static_cast<std::size_t>(viewedBytes)));
|
||||
if (written && DepthSurfaceOverlaps(context.device, viewed.baseAddress, viewedBytes)) {
|
||||
const auto storage = WrittenStorageBytes(viewed, mip);
|
||||
RetireCoveredDepthSurfaces(context.device, storage.bytes, storage.extents);
|
||||
@@ -1885,6 +1892,11 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
|
||||
for (const auto& shader : shaders) {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.program->runtimeAbiVersion);
|
||||
if (shader.program->poisonedSrtReads != 0) {
|
||||
const auto& programBindings = shader.program->bindings;
|
||||
const auto flat = std::find_if(programBindings.begin(), programBindings.end(), [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::FlattenedSrt; });
|
||||
if (flat != programBindings.end()) srtGuardFaults.push_back({IrStage(shader.stage), flat->guestDescriptor});
|
||||
}
|
||||
const VkShaderStageFlags flags = VulkanStage(shader.stage);
|
||||
std::uint64_t stageStorageBuffers = 0;
|
||||
std::uint64_t stageResources = shader.stage == ShaderRecompiler::ShaderStage::Fragment ? colorAttachments : 0;
|
||||
@@ -2284,6 +2296,7 @@ void ShaderResources::buildComplete() {
|
||||
timing.uploadMs += phase(BuildPhase::Upload);
|
||||
if (usesBda) bda = std::make_unique<BdaResources>(context, guestMemory);
|
||||
else if (usesFaultBuffer) bda = std::make_unique<BdaResources>(context);
|
||||
if (bda) bda->SetSrtGuards(std::move(srtGuardFaults));
|
||||
finishImageTables();
|
||||
phase(BuildPhase::Bda);
|
||||
if (_set != VK_NULL_HANDLE || fragment.set != VK_NULL_HANDLE) {
|
||||
@@ -2720,7 +2733,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"};
|
||||
|
||||
@@ -2858,6 +2871,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
|
||||
@@ -2936,8 +2962,10 @@ 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);
|
||||
if (!FlushDepthPlanes(address, bytes).empty()) return fail(FastFail::Changed);
|
||||
surface.collected = GuestMemory::CollectWrites(address, bytes);
|
||||
if (surface.collected == 0) return fail(FastFail::Collect);
|
||||
const auto* source = surface.source;
|
||||
@@ -3062,8 +3090,10 @@ 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);
|
||||
if (!FlushDepthPlanes(address, bytes).empty()) return false;
|
||||
surface.collected = GuestMemory::CollectWrites(address, bytes);
|
||||
if (surface.collected == 0) return false;
|
||||
if (PendingStorageOverlaps(address, bytes, surface.source)) return false;
|
||||
@@ -3237,7 +3267,10 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
|
||||
if (!reusable || shaders.empty()) return false;
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
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& region : directRegions) {
|
||||
static_cast<void>(FlushDepthPlanes(region.begin, static_cast<std::size_t>(region.end - region.begin)));
|
||||
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));
|
||||
@@ -3262,6 +3295,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;
|
||||
@@ -3367,6 +3401,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);
|
||||
@@ -3374,7 +3413,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;
|
||||
@@ -4224,7 +4263,7 @@ std::size_t ShaderResources::addDataBuffer(std::span<const std::uint32_t> words)
|
||||
const auto size = words.size() * sizeof(std::uint32_t);
|
||||
Require(size <= context.limits.maxStorageBufferRange, "shader data buffer exceeds descriptor range limit");
|
||||
const bool refreshable = TemplateDataRefresh() && size <= MaxRefreshBytes;
|
||||
auto buffer = std::make_unique<Buffer>(context, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | (refreshable ? VK_BUFFER_USAGE_TRANSFER_DST_BIT : 0u));
|
||||
auto buffer = std::make_unique<Buffer>(context, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
std::memcpy(buffer->Bytes().data(), words.data(), size);
|
||||
Allocation allocation{0, size, false, std::move(buffer)};
|
||||
if (refreshable) allocation.dataWords.assign(words.begin(), words.end());
|
||||
@@ -4300,6 +4339,31 @@ bool ShaderResources::refreshData(VkCommandBuffer commands, const CompiledShader
|
||||
return recorded;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> ShaderResources::shaderDataAllocation(const CompiledShader& shader) const {
|
||||
Require(shader.program != nullptr, "missing compiled shader");
|
||||
const auto& program = *shader.program;
|
||||
if (program.bindings.size() != bindings.size()) return std::nullopt;
|
||||
for (std::size_t index = 0; index < program.bindings.size(); ++index) {
|
||||
if (program.bindings[index].role != ShaderRecompiler::DescriptorRole::ShaderData || bindings[index].allocations.size() != 1) continue;
|
||||
const auto allocation = bindings[index].allocations.front();
|
||||
if (allocations[allocation].guest || allocations[allocation].buffer == nullptr) return std::nullopt;
|
||||
return allocation;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VkBuffer ShaderResources::shaderDataBuffer(const CompiledShader& shader) const {
|
||||
const auto allocation = shaderDataAllocation(shader);
|
||||
return allocation.has_value() ? allocations[*allocation].buffer->Handle() : VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void ShaderResources::forgetShaderDataWords(const CompiledShader& shader) {
|
||||
if (const auto allocation = shaderDataAllocation(shader)) {
|
||||
allocations[*allocation].dataWords.clear();
|
||||
rehashDataWords();
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderResources::writeDataWords(VkCommandBuffer commands, std::size_t allocation, std::span<const std::uint32_t> words) const {
|
||||
const auto& buffer = *allocations[allocation].buffer;
|
||||
const auto size = words.size() * sizeof(std::uint32_t);
|
||||
@@ -4499,7 +4563,7 @@ std::shared_ptr<Texture> ShaderResources::fastTexture(const ImageRecord& record)
|
||||
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 (DepthSurfaceAt(address) || !FlushDepthPlanes(address, bytes).empty()) return nullptr;
|
||||
if (GuestMemory::CollectWrites(address, bytes) == 0) return nullptr;
|
||||
if (PendingStorageOverlaps(address, bytes, record.source.get())) return nullptr;
|
||||
auto keys = record.keys;
|
||||
@@ -4571,14 +4635,15 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
||||
const auto resource = record != nullptr && record->decoded ? record->resource : DecodeTextureResource(words);
|
||||
const bool forcedSrgb = ForcesDegammaOn(resource.format, binding.imageSamplers[element], shaderSamplers);
|
||||
RequireNonSeamlessCube(resource.dimension == TextureDimension::kCube, binding.imageSamplers[element], shaderSamplers);
|
||||
const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DArray;
|
||||
if (!firstLayer && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast<int>(*binding.imageShape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
|
||||
const bool depthCompare = !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element);
|
||||
const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && (resource.dimension == TextureDimension::k2DArray || (depthCompare && resource.dimension == TextureDimension::k1DArray));
|
||||
const bool depth1D = depthCompare && ((binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k1D) || (binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2DArray && resource.dimension == TextureDimension::k1DArray));
|
||||
if (!firstLayer && !depth1D && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast<int>(*binding.imageShape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
|
||||
const VkComponentMapping components = ViewComponents(resource);
|
||||
const auto guestBytes = record != nullptr && record->decoded ? record->guestBytes : DescribeSurface(resource).guestBytes;
|
||||
CompletePendingCpuWrites(context, resource.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
if (resource.dccAddress != 0) CompletePendingCpuWrites(context, resource.dccAddress, DccKeyBytes(guestBytes));
|
||||
std::shared_ptr<Texture> texture;
|
||||
const bool depthCompare = !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element);
|
||||
const VkImageViewType multisampleViewType = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2DMultisampleArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
|
||||
if (previousSampled.texture != nullptr && previousSampled.depthCompare == depthCompare && previousSampled.multisampleViewType == multisampleViewType && words.size() == 8 && std::equal(words.begin(), words.end(), previousSampled.words.begin())) texture = previousSampled.texture;
|
||||
if (texture == nullptr && record != nullptr && record->texture != nullptr) {
|
||||
@@ -4916,8 +4981,10 @@ void ShaderResources::resolveImageTables() {
|
||||
try {
|
||||
resource = DecodeTextureResource(words);
|
||||
shape = binding.imageShapes.at(element);
|
||||
firstLayer = shape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DArray;
|
||||
if (!firstLayer && !MatchesGuestDimension(shape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the table entry's image shape (shape " + std::to_string(static_cast<int>(shape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
|
||||
const bool depthCompare = element < binding.imageDepthCompare.size() && binding.imageDepthCompare[element];
|
||||
firstLayer = shape == ShaderRecompiler::DescriptorImageShape::Image2D && (resource.dimension == TextureDimension::k2DArray || (depthCompare && resource.dimension == TextureDimension::k1DArray));
|
||||
const bool depth1D = depthCompare && ((shape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k1D) || (shape == ShaderRecompiler::DescriptorImageShape::Image2DArray && resource.dimension == TextureDimension::k1DArray));
|
||||
if (!firstLayer && !depth1D && !MatchesGuestDimension(shape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the table entry's image shape (shape " + std::to_string(static_cast<int>(shape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
|
||||
guestBytes = DescribeSurface(resource).guestBytes;
|
||||
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(resource.baseAddress), static_cast<std::size_t>(guestBytes)) && GuestMemory::CommittedRanges(resource.baseAddress, static_cast<std::size_t>(guestBytes), false).empty()) throw std::runtime_error("AGC graphics: the table entry's surface at 0x" + HexText(resource.baseAddress) + " (0x" + HexText(guestBytes) + " bytes) is not mapped");
|
||||
static_cast<void>(ResolveTextureFormat(resource.format));
|
||||
|
||||
@@ -1978,7 +1978,9 @@ std::string NullPixelProgramRejection(const QueueState& queue) {
|
||||
const auto targetMask = find(queue.context, 0x8e);
|
||||
const auto shaderMask = find(queue.context, 0x8f);
|
||||
if (targetMask == queue.context.end() || shaderMask == queue.context.end()) return "AGC graphics: a draw without a pixel program needs CB_TARGET_MASK and CB_SHADER_MASK";
|
||||
if (ColorWriteMask(queue.context) != 0) return "AGC graphics: a draw without a pixel program writes color";
|
||||
const auto colorControl = find(queue.context, 0x202);
|
||||
const bool colorDisabled = colorControl != queue.context.end() && ((colorControl->second >> 4u) & 7u) == 0u;
|
||||
if (!colorDisabled && ColorWriteMask(queue.context) != 0) return "AGC graphics: a draw without a pixel program writes color";
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/SlimMutex.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
@@ -40,6 +41,17 @@
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
OwnedImage::~OwnedImage() {
|
||||
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
|
||||
ReleaseImageMemory(context, memory, memoryBytes, memoryType);
|
||||
}
|
||||
|
||||
void OwnedImage::Allocate(const Context& device, const VkMemoryAllocateInfo& allocation, std::uint32_t mask, const char* operation) {
|
||||
memory = AllocateImageMemory(device, allocation, mask, operation, &memoryType);
|
||||
memoryBytes = allocation.allocationSize;
|
||||
context.bufferPool = device.bufferPool;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// APS5_PROFILE_DRAW: accumulate texture setup phases and report every 200 textures.
|
||||
@@ -90,7 +102,7 @@ thread_local const char* uploadReason = "first";
|
||||
// (DiscardPendingInside) and units an alias's own results superseded (markLayersPending); relaxed,
|
||||
// reported only.
|
||||
std::atomic<std::uint64_t> singlePassMoves{0}, slabRetiles{0};
|
||||
std::atomic<std::uint64_t> partialUploads{0}, partialUploadBytes{0}, partialWriteBacks{0}, partialWriteBackBytes{0}, emptyWriteBacks{0}, coalescedWriteBacks{0}, unitsDropped{0}, unitsSuperseded{0};
|
||||
std::atomic<std::uint64_t> partialUploads{0}, partialUploadBytes{0}, partialWriteBacks{0}, partialWriteBackBytes{0}, emptyWriteBacks{0}, coalescedWriteBacks{0}, unitsDropped{0}, unitsSuperseded{0}, clearHandOvers{0}, handedOverSuperseded{0};
|
||||
// FlushPending listings skipped because no pending unit of the image lay inside the range, images
|
||||
// whose selected pending units were dropped because their memory is no longer registered (see
|
||||
// writeBackLayers), and pending units a DCC clear -> uncompressed key flip kept as the texels
|
||||
@@ -143,7 +155,7 @@ void reportStorageTraffic(StorageTraffic& traffic) {
|
||||
const auto take = [](std::atomic<std::uint64_t>& counter) { return static_cast<unsigned long long>(counter.exchange(0, std::memory_order_relaxed)); };
|
||||
const auto partialUploadCount = take(partialUploads), partialWriteBackCount = take(partialWriteBacks);
|
||||
const auto uploadMiB = take(partialUploadBytes) / 1048576.0, writeBackMiB = take(partialWriteBackBytes) / 1048576.0;
|
||||
AgcDriver::ProfilePrint_nid_no_patch( "[storage] uploads by path (count/MiB, 10 s):%s; %llu write-backs GPU-direct, %llu stored nothing; block units: partial uploads %llu/%.1f, partial write-backs %llu/%.1f, %llu widened to every pending unit, units dropped %llu, superseded %llu, dropped unregistered %llu, kept over a key flip %llu%s, pretest skipped %llu, held for a queued draw read %llu, evicted stale %llu\n", line.c_str(), static_cast<unsigned long long>(traffic.directWriteBacks), take(emptyWriteBacks), partialUploadCount, uploadMiB, partialWriteBackCount, writeBackMiB, take(coalescedWriteBacks), take(unitsDropped), take(unitsSuperseded), take(unregisteredDropped), take(keyFlipKept), ShadowReport().c_str(), take(pretestSkipped), take(queuedReadHeld), take(staleEvicted));
|
||||
AgcDriver::ProfilePrint_nid_no_patch( "[storage] uploads by path (count/MiB, 10 s):%s; %llu write-backs GPU-direct, %llu stored nothing; block units: partial uploads %llu/%.1f, partial write-backs %llu/%.1f, %llu widened to every pending unit, units dropped %llu, superseded %llu, handed over at %llu clear-key refreshes (%llu units superseded), dropped unregistered %llu, kept over a key flip %llu%s, pretest skipped %llu, held for a queued draw read %llu, evicted stale %llu\n", line.c_str(), static_cast<unsigned long long>(traffic.directWriteBacks), take(emptyWriteBacks), partialUploadCount, uploadMiB, partialWriteBackCount, writeBackMiB, take(coalescedWriteBacks), take(unitsDropped), take(unitsSuperseded), take(clearHandOvers), take(handedOverSuperseded), take(unregisteredDropped), take(keyFlipKept), ShadowReport().c_str(), take(pretestSkipped), take(queuedReadHeld), take(staleEvicted));
|
||||
traffic.writeBacks.clear();
|
||||
traffic.uploadReasons.clear();
|
||||
traffic.directWriteBacks = 0;
|
||||
@@ -268,7 +280,7 @@ ExpandImage CreateExpandImage(const Context& context, const GuestTextureResource
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "texture");
|
||||
result.owned->memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory sRGB copy");
|
||||
result.owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory sRGB copy");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, result.image, result.owned->memory, 0), "vkBindImageMemory sRGB copy");
|
||||
result.bytes = requirements.size;
|
||||
return result;
|
||||
@@ -329,11 +341,12 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
||||
Require(arrayLayers == 0 || sliceLinearBytes <= UINT64_MAX / arrayLayers, "detiled texture buffer size overflows");
|
||||
const auto linearBytes = sliceLinearBytes * arrayLayers;
|
||||
|
||||
const bool depth1D = depthCompare && (descriptor.dimension == TextureDimension::k1D || descriptor.dimension == TextureDimension::k1DArray);
|
||||
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
imageInfo.flags = (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u) | (SrgbTwinFormat(context, vkFormat) != VK_FORMAT_UNDEFINED ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u);
|
||||
imageInfo.imageType = ImageTypeFor(descriptor.dimension);
|
||||
imageInfo.imageType = depth1D ? VK_IMAGE_TYPE_2D : ImageTypeFor(descriptor.dimension);
|
||||
imageInfo.format = vkFormat;
|
||||
imageInfo.extent = {descriptor.width, descriptor.height, geometry.imageDepth};
|
||||
imageInfo.extent = {descriptor.width, depth1D ? 1u : descriptor.height, geometry.imageDepth};
|
||||
imageInfo.mipLevels = descriptor.mipCount;
|
||||
Require(geometry.imageLayers <= context.limits.maxImageArrayLayers, "a texture array with more layers than maxImageArrayLayers is not implemented");
|
||||
imageInfo.arrayLayers = geometry.imageLayers;
|
||||
@@ -351,7 +364,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocationBytes = requirements.size;
|
||||
allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "texture");
|
||||
owned->memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory texture");
|
||||
owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory texture");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory");
|
||||
|
||||
const VkImage uploadImage = image;
|
||||
@@ -531,7 +544,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
|
||||
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = image;
|
||||
viewInfo.viewType = ViewTypeFor(descriptor.dimension, viewLayerCount);
|
||||
viewInfo.viewType = depth1D ? (descriptor.dimension == TextureDimension::k1DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D) : ViewTypeFor(descriptor.dimension, viewLayerCount);
|
||||
viewInfo.format = srgbExpand ? VK_FORMAT_R8G8B8A8_SRGB : vkFormat;
|
||||
viewFormat = viewInfo.format;
|
||||
viewInfo.components = depthCompare ? VkComponentMapping{} : components;
|
||||
@@ -627,7 +640,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocationBytes = requirements.size;
|
||||
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
owned->memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory repacked copy");
|
||||
owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory repacked copy");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory repacked copy");
|
||||
|
||||
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
|
||||
@@ -685,7 +698,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocationBytes = requirements.size;
|
||||
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
owned->memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory block copy");
|
||||
owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory block copy");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory block copy");
|
||||
|
||||
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
|
||||
@@ -722,11 +735,12 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
||||
copyElementBytes = BytesPerElement(descriptor.format);
|
||||
AGC_LOG_DEFERRED(stdout, "Texture address=0x%llx %ux%u mips=%u layers=%u depth copy of storage image (vk=%d -> %d)", static_cast<unsigned long long>(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, copyLayers, static_cast<int>(colorFormat), static_cast<int>(depthFormat));
|
||||
|
||||
const bool depth1D = descriptor.dimension == TextureDimension::k1D || descriptor.dimension == TextureDimension::k1DArray;
|
||||
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
imageInfo.flags = descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u;
|
||||
imageInfo.imageType = ImageTypeFor(descriptor.dimension);
|
||||
imageInfo.imageType = depth1D ? VK_IMAGE_TYPE_2D : ImageTypeFor(descriptor.dimension);
|
||||
imageInfo.format = depthFormat;
|
||||
imageInfo.extent = {descriptor.width, descriptor.height, 1u};
|
||||
imageInfo.extent = {descriptor.width, depth1D ? 1u : descriptor.height, 1u};
|
||||
imageInfo.mipLevels = copyLevels;
|
||||
imageInfo.arrayLayers = copyLayers;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
@@ -742,7 +756,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocationBytes = requirements.size;
|
||||
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
owned->memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth copy");
|
||||
owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth copy");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory depth copy");
|
||||
|
||||
const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
|
||||
@@ -752,14 +766,16 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
|
||||
}
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = image;
|
||||
viewInfo.viewType = ViewTypeFor(descriptor.dimension, viewLayerCount);
|
||||
viewInfo.viewType = depth1D ? (descriptor.dimension == TextureDimension::k1DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D) : ViewTypeFor(descriptor.dimension, viewLayerCount);
|
||||
viewInfo.format = depthFormat;
|
||||
viewInfo.components = VkComponentMapping{};
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, descriptor.baseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount};
|
||||
VkImageViewMinLodCreateInfoEXT minLod{VK_STRUCTURE_TYPE_IMAGE_VIEW_MIN_LOD_CREATE_INFO_EXT};
|
||||
ChainMinLod(context, descriptor, viewInfo, minLod);
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth copy");
|
||||
viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount};
|
||||
createFirstLayerView(descriptor, viewInfo);
|
||||
prepareBaseLevelView(descriptor, viewInfo);
|
||||
static_cast<void>(components);
|
||||
depthCopyTextures.fetch_add(1, std::memory_order_relaxed);
|
||||
} catch (...) {
|
||||
@@ -1067,7 +1083,7 @@ Texture::~Texture() {
|
||||
}
|
||||
|
||||
void Texture::createFirstLayerView(const GuestTextureResource& descriptor, VkImageViewCreateInfo viewInfo) {
|
||||
if (descriptor.dimension != TextureDimension::k2DArray) return;
|
||||
if (descriptor.dimension != TextureDimension::k2DArray && (viewInfo.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY || descriptor.dimension != TextureDimension::k1DArray)) return;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &firstLayerView), "vkCreateImageView first layer");
|
||||
@@ -1076,9 +1092,10 @@ void Texture::createFirstLayerView(const GuestTextureResource& descriptor, VkIma
|
||||
|
||||
void Texture::prepareBaseLevelView(const GuestTextureResource& descriptor, VkImageViewCreateInfo viewInfo) {
|
||||
if (viewInfo.subresourceRange.levelCount == 1u) return;
|
||||
if (descriptor.dimension != TextureDimension::k1D && descriptor.dimension != TextureDimension::k2D && descriptor.dimension != TextureDimension::k2DArray) return;
|
||||
const bool depth1D = (viewInfo.subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) && (descriptor.dimension == TextureDimension::k1D || descriptor.dimension == TextureDimension::k1DArray);
|
||||
if (!depth1D && descriptor.dimension != TextureDimension::k1D && descriptor.dimension != TextureDimension::k2D && descriptor.dimension != TextureDimension::k2DArray) return;
|
||||
viewInfo.pNext = nullptr;
|
||||
viewInfo.viewType = descriptor.dimension == TextureDimension::k1D ? VK_IMAGE_VIEW_TYPE_1D : VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.viewType = (descriptor.dimension == TextureDimension::k1D && !depth1D) ? VK_IMAGE_VIEW_TYPE_1D : VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.subresourceRange.levelCount = 1;
|
||||
viewInfo.subresourceRange.layerCount = 1;
|
||||
baseLevelInfo = viewInfo;
|
||||
@@ -1411,7 +1428,7 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler,
|
||||
allocation.allocationSize = requirements.size;
|
||||
memoryBytes = requirements.size;
|
||||
allocation.memoryTypeIndex = context.DeviceMemoryType(requirements.memoryTypeBits, requirements.size, "storage texture");
|
||||
memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory storage texture");
|
||||
memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory storage texture", &memoryType);
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory storage");
|
||||
} catch (...) {
|
||||
release();
|
||||
@@ -2056,7 +2073,10 @@ bool StorageTexture::Refresh() {
|
||||
}
|
||||
}
|
||||
// No publish: the upload below reads the stored units from the unit shadow itself.
|
||||
const bool flushed = FlushPending(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), alias.get(), "storage refresh", PublishScope::None);
|
||||
handedOver.clear();
|
||||
handedUnits.clear();
|
||||
const auto handOverKeys = alias == nullptr ? clearHandOver() : std::nullopt;
|
||||
const bool flushed = !handOverKeys.has_value() && FlushPending(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), alias.get(), "storage refresh", PublishScope::None);
|
||||
if (profile && flushed) start = LookupOutcomes::Add(LookupOutcomes::PendingFlush, start);
|
||||
// `original` holds the guest bytes the image was last uploaded from or written back as; while the
|
||||
// guest memory and the DCC keys still match, the image content is current. Pages nobody wrote
|
||||
@@ -2101,7 +2121,7 @@ bool StorageTexture::Refresh() {
|
||||
}
|
||||
};
|
||||
const auto keysStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
keys = ProvedKeys();
|
||||
keys = handOverKeys.has_value() ? *handOverKeys : ProvedKeys();
|
||||
if (profile && descriptor.dccAddress != 0) LookupOutcomes::Add(LookupOutcomes::DccScan, keysStart);
|
||||
if (keys != uploadedKeys) {
|
||||
changed.assign(trackedLayers, true);
|
||||
@@ -3543,6 +3563,7 @@ void StorageTexture::refreshGeneration() {
|
||||
|
||||
void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count) {
|
||||
CaptureTrace::Log("image-write image=%llx first=%u count=%u generation=%llu", static_cast<unsigned long long>(descriptor.baseAddress), first, count, static_cast<unsigned long long>(generation));
|
||||
++writes;
|
||||
static const bool eager = std::getenv("APS5_EAGER_WRITEBACK") != nullptr || std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
|
||||
std::vector<bool> unsaved(trackedLayers, false);
|
||||
bool anyUnsaved = false;
|
||||
@@ -3567,6 +3588,7 @@ void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count)
|
||||
}
|
||||
if (superseded) source->reconcilePending();
|
||||
}
|
||||
if (!handedOver.empty() || !handedUnits.empty()) supersedeHandedOver(first, count);
|
||||
if (eager) {
|
||||
WriteBack();
|
||||
return;
|
||||
@@ -3646,6 +3668,7 @@ bool StorageTexture::pendingUnitInside(std::uint64_t address, std::size_t bytes)
|
||||
}
|
||||
|
||||
bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published) {
|
||||
static_cast<void>(FlushDepthPlanes(address, bytes));
|
||||
if (published != nullptr) *published = false;
|
||||
if (AgcDriver::DriverDetail::DrawPipeline::HeldReads(address, bytes)) {
|
||||
queuedReadHeld.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -4763,6 +4786,11 @@ bool StorageTexture::CopyFrom(StorageTexture& source, const char*& refusal) {
|
||||
copied.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &copied);
|
||||
recorder->EndGpuTiming(timing, guestBytes);
|
||||
MarkWholeWritten();
|
||||
return true;
|
||||
}
|
||||
|
||||
void StorageTexture::MarkWholeWritten() {
|
||||
// As after a clear: the image holds the surface, guest memory follows at the write-back (which
|
||||
// keeps the 64 KiB blocks the CPU writes from now on; the collect is uncached for the reason
|
||||
// given in FillClear).
|
||||
@@ -4777,7 +4805,6 @@ bool StorageTexture::CopyFrom(StorageTexture& source, const char*& refusal) {
|
||||
refreshGeneration();
|
||||
forgetBorrowed(0, trackedLayers);
|
||||
MarkDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
// See AdjacentGenerationEnabled above.
|
||||
@@ -4849,6 +4876,104 @@ void StorageTexture::blockGenerations(std::vector<std::uint64_t>& generations) c
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic<bool>& ClearHandOverOn() {
|
||||
static std::atomic<bool> on{std::getenv("APS5_NO_CLEAR_HAND_OVER") == nullptr};
|
||||
return on;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StorageTexture::SetClearHandOver(bool enabled) {
|
||||
ClearHandOverOn().store(enabled, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::uint64_t StorageTexture::ClearHandOvers() {
|
||||
return clearHandOvers.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::uint64_t StorageTexture::HandedOverSuperseded() {
|
||||
return handedOverSuperseded.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::optional<DccKeys> StorageTexture::clearHandOver() {
|
||||
if (!ClearHandOverOn().load(std::memory_order_relaxed) || !blockUnits || descriptor.dccAddress == 0 || anyLayerPending()) return std::nullopt;
|
||||
if (!GuestMemory::Watched(descriptor.baseAddress, static_cast<std::size_t>(guestBytes))) return std::nullopt;
|
||||
const auto others = overlappingPending(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
if (others.empty()) return std::nullopt;
|
||||
const auto begin = descriptor.baseAddress;
|
||||
const auto end = descriptor.baseAddress + guestBytes;
|
||||
const auto keyBytes = DccKeyBytes(guestBytes);
|
||||
for (const auto& other : others) {
|
||||
if (!other->blockUnits || (other->descriptor.dccAddress != 0 && other->uploadedKeys != DccKeys::Uncompressed)) return std::nullopt;
|
||||
if (other->descriptor.dccAddress != 0 && other->descriptor.dccAddress < descriptor.dccAddress + keyBytes && descriptor.dccAddress < other->descriptor.dccAddress + DccKeyBytes(other->guestBytes)) return std::nullopt;
|
||||
for (std::uint32_t unit = 0; unit < other->trackedLayers; ++unit) {
|
||||
if (!other->layerPending[unit]) continue;
|
||||
const auto from = other->layerBegin(unit);
|
||||
const auto to = from + other->layerBytes(unit);
|
||||
if (to <= begin || from >= end) continue;
|
||||
if (from < begin || to > end || other->layerBytes(unit) != trackedLayerBytes) return std::nullopt;
|
||||
}
|
||||
}
|
||||
const auto keys = ProvedKeys();
|
||||
VkClearColorValue clear{};
|
||||
if (!IsDccClear(keys) || !ClearColorFor(storageFormat, keys, clear)) return std::nullopt;
|
||||
if (handedUnits.size() != trackedLayers) handedUnits.assign(trackedLayers, false);
|
||||
for (const auto& other : others) {
|
||||
for (std::uint32_t unit = 0; unit < other->trackedLayers; ++unit) {
|
||||
const auto from = other->layerBegin(unit);
|
||||
if (other->layerPending[unit] && from >= begin && from < end) handedUnits[static_cast<std::size_t>((from - begin) / trackedLayerBytes)] = true;
|
||||
}
|
||||
const auto same = std::find_if(handedOver.begin(), handedOver.end(), [&](const auto& entry) { return entry.first.lock() == other; });
|
||||
if (same != handedOver.end()) same->second = other->writes;
|
||||
else handedOver.emplace_back(other, other->writes);
|
||||
}
|
||||
clearHandOvers.fetch_add(1, std::memory_order_relaxed);
|
||||
return keys;
|
||||
}
|
||||
|
||||
void StorageTexture::supersedeHandedOver(std::uint32_t first, std::uint32_t count) {
|
||||
const auto begin = layerBegin(first);
|
||||
const auto end = first + count >= trackedLayers ? descriptor.baseAddress + guestBytes : layerBegin(first + count);
|
||||
for (auto entry = handedOver.begin(); entry != handedOver.end();) {
|
||||
const auto source = entry->first.lock();
|
||||
if (source == nullptr || source->writes != entry->second) {
|
||||
entry = handedOver.erase(entry);
|
||||
continue;
|
||||
}
|
||||
bool superseded = false;
|
||||
for (std::uint32_t unit = 0; unit < source->trackedLayers; ++unit) {
|
||||
const auto from = source->layerBegin(unit);
|
||||
if (!source->layerPending[unit] || from < begin || from + source->layerBytes(unit) > end) continue;
|
||||
source->layerPending[unit] = false;
|
||||
handedOverSuperseded.fetch_add(1, std::memory_order_relaxed);
|
||||
superseded = true;
|
||||
}
|
||||
if (superseded) source->reconcilePending();
|
||||
++entry;
|
||||
}
|
||||
if (first == 0 && count >= trackedLayers) handedOver.clear();
|
||||
if (std::any_of(handedUnits.begin() + first, handedUnits.begin() + std::min<std::size_t>(first + count, handedUnits.size()), [](bool handed) { return handed; })) {
|
||||
const auto current = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
std::vector<std::uint64_t> generations;
|
||||
blockGenerations(generations);
|
||||
std::vector<std::uint8_t> stamped(generations.size(), 0);
|
||||
std::vector<std::uint8_t> cpu(generations.size(), 0);
|
||||
const bool tracked = GuestMemory::ChangedBlocks(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), generations, stamped, cpu);
|
||||
bool moved = false;
|
||||
for (std::uint32_t unit = first; unit < first + count && unit < handedUnits.size(); ++unit) {
|
||||
if (!handedUnits[unit]) continue;
|
||||
handedUnits[unit] = false;
|
||||
if (!tracked || unit >= cpu.size() || cpu[unit] != 0) continue;
|
||||
layerGeneration[unit] = current;
|
||||
moved = true;
|
||||
}
|
||||
if (moved) refreshGeneration();
|
||||
}
|
||||
if (std::none_of(handedUnits.begin(), handedUnits.end(), [](bool handed) { return handed; })) handedUnits.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> StorageTexture::pendingAlias() const {
|
||||
static const bool disabled = std::getenv("APS5_NO_ALIAS_BORROW") != nullptr;
|
||||
if (disabled || !blockUnits) return nullptr;
|
||||
@@ -5481,7 +5606,7 @@ void StorageTexture::release() noexcept {
|
||||
if (proxyMemory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, proxyMemory, nullptr);
|
||||
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
|
||||
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
|
||||
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
|
||||
ReleaseImageMemory(context, memory, memoryBytes, memoryType);
|
||||
}
|
||||
|
||||
std::uint64_t StorageTexture::GuestBytes() const {
|
||||
|
||||
@@ -89,10 +89,7 @@ TextureDetiler::TextureDetiler(const Context& context) : context(context) {
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = &pushRange;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &pipelineLayoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(TEXTURE_DETILE_SPV);
|
||||
moduleInfo.pCode = TEXTURE_DETILE_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule");
|
||||
module = CreateShaderModule(context, TEXTURE_DETILE_SPV, sizeof(TEXTURE_DETILE_SPV), "vkCreateShaderModule");
|
||||
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &layoutInfo, nullptr, &imageDescriptorLayout), "vkCreateDescriptorSetLayout");
|
||||
pipelineLayoutInfo.pSetLayouts = &imageDescriptorLayout;
|
||||
@@ -168,10 +165,7 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem
|
||||
std::copy(equation->bits.begin(), equation->bits.end(), values.begin() + 4);
|
||||
}
|
||||
if (image && imageModule == VK_NULL_HANDLE) {
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(TEXTURE_DETILE_IMAGE_SPV);
|
||||
moduleInfo.pCode = TEXTURE_DETILE_IMAGE_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &imageModule), "vkCreateShaderModule");
|
||||
imageModule = CreateShaderModule(context, TEXTURE_DETILE_IMAGE_SPV, sizeof(TEXTURE_DETILE_IMAGE_SPV), "vkCreateShaderModule");
|
||||
}
|
||||
std::array<VkSpecializationMapEntry, 22> entries{};
|
||||
for (std::uint32_t index = 0; index < entries.size(); ++index) entries[index] = {index, index * 4u, 4};
|
||||
@@ -273,10 +267,7 @@ void TextureDetiler::DispatchRepack(VkCommandBuffer commands, VkBuffer source, s
|
||||
Require(commands != VK_NULL_HANDLE && source != VK_NULL_HANDLE && destination != VK_NULL_HANDLE, "a texel repack needs a command buffer and its buffers");
|
||||
Require(bytes != 0 && bytes % 4u == 0 && sourceOffset % 4u == 0 && destinationOffset % 4u == 0, "a texel repack needs whole aligned words");
|
||||
if (repackPipeline == VK_NULL_HANDLE) {
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(TEXTURE_REPACK_SPV);
|
||||
moduleInfo.pCode = TEXTURE_REPACK_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &repackModule), "vkCreateShaderModule repack");
|
||||
repackModule = CreateShaderModule(context, TEXTURE_REPACK_SPV, sizeof(TEXTURE_REPACK_SPV), "vkCreateShaderModule repack");
|
||||
VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
stage.module = repackModule;
|
||||
@@ -347,11 +338,7 @@ void TextureDetiler::DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = &pushRange;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &pipelineLayoutInfo, nullptr, &cmaskPipelineLayout), "vkCreatePipelineLayout CMASK clear");
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = sizeof(CMASK_CLEAR_SPV);
|
||||
moduleInfo.pCode = CMASK_CLEAR_SPV;
|
||||
VkShaderModule cmaskModule = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &cmaskModule), "vkCreateShaderModule CMASK clear");
|
||||
VkShaderModule cmaskModule = CreateShaderModule(context, CMASK_CLEAR_SPV, sizeof(CMASK_CLEAR_SPV), "vkCreateShaderModule CMASK clear");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#include "BdaShader.hpp"
|
||||
#include "ColorTransferTests.hpp"
|
||||
#include "HostImportFailure.hpp"
|
||||
#include <fstream>
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include <SDL_loadso.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -23,7 +26,7 @@ using namespace AgcDriver::Graphics;
|
||||
|
||||
class Device {
|
||||
public:
|
||||
Device() {
|
||||
explicit Device(bool importHostMemory = false) {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#else
|
||||
@@ -86,11 +89,13 @@ public:
|
||||
VkPhysicalDeviceFeatures enabled{};
|
||||
enabled.shaderInt64 = VK_TRUE;
|
||||
address.pNext = &bytes;
|
||||
const std::array<const char*, 2> extensionsEnabled{VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, VK_KHR_8BIT_STORAGE_EXTENSION_NAME};
|
||||
std::vector<const char*> extensionsEnabled{VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, VK_KHR_8BIT_STORAGE_EXTENSION_NAME};
|
||||
const bool externalHost = importHostMemory && std::any_of(available.begin(), available.end(), [](const auto& item) { return std::strcmp(item.extensionName, VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) == 0; });
|
||||
if (externalHost) extensionsEnabled.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
|
||||
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &address};
|
||||
device.queueCreateInfoCount = 1;
|
||||
device.pQueueCreateInfos = &queue;
|
||||
device.enabledExtensionCount = 2;
|
||||
device.enabledExtensionCount = static_cast<std::uint32_t>(extensionsEnabled.size());
|
||||
device.ppEnabledExtensionNames = extensionsEnabled.data();
|
||||
device.pEnabledFeatures = &enabled;
|
||||
Check(function<PFN_vkCreateDevice>("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice");
|
||||
@@ -99,6 +104,13 @@ public:
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
|
||||
context.limits = properties.limits;
|
||||
if (externalHost) {
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT host{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 extended{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &host};
|
||||
function<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(context.physical, &extended);
|
||||
context.hostImportAlignment = host.minImportedHostPointerAlignment;
|
||||
}
|
||||
if (importHostMemory) std::printf("host import device %s vendor=0x%04x device=0x%04x driver=0x%08x alignment=%llu\n", properties.deviceName, properties.vendorID, properties.deviceID, properties.driverVersion, static_cast<unsigned long long>(context.hostImportAlignment));
|
||||
context.bufferDeviceAddress = true;
|
||||
context.Function<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(context.device, family, 0, &context.queue);
|
||||
VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
@@ -143,7 +155,8 @@ private:
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
RunBdaContractTests();
|
||||
if (argc == 2) {
|
||||
const bool importFailures = argc == 2 && std::strcmp(argv[1], "--host-import-failures") == 0;
|
||||
if (argc == 2 && !importFailures) {
|
||||
const auto shader = MakeBdaTestShader(0x7fff12340000ULL, 32);
|
||||
std::ofstream file(argv[1], std::ios::binary);
|
||||
file.write(reinterpret_cast<const char*>(shader.data()), static_cast<std::streamsize>(shader.size() * sizeof(std::uint32_t)));
|
||||
@@ -152,12 +165,20 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
std::unique_ptr<Device> device;
|
||||
try {
|
||||
device = std::make_unique<Device>();
|
||||
device = std::make_unique<Device>(importFailures);
|
||||
} catch (const std::exception& error) {
|
||||
if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw;
|
||||
std::cout << "skipped, no usable Vulkan device: " << error.what() << '\n';
|
||||
return 77;
|
||||
}
|
||||
if (importFailures) {
|
||||
if (device->GetContext().hostImportAlignment == 0) {
|
||||
std::cout << "skipped, no external host memory import support\n";
|
||||
return 77;
|
||||
}
|
||||
AgcDriver::Tests::RunHostImportFailureTests(device->GetContext(), true);
|
||||
return 0;
|
||||
}
|
||||
Buffer buffer(device->GetContext(), 256, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
Require(buffer.DeviceAddress() != 0 && buffer.Bytes().size() == 256, "invalid real BDA buffer");
|
||||
buffer.Bytes()[255] = std::byte{0x5a};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "AllocationFailure.hpp"
|
||||
#include "SlabReturn.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -16,6 +17,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
@@ -895,6 +897,110 @@ void DeviceBindingFailures() {
|
||||
|
||||
}
|
||||
|
||||
VkDeviceMemory reclaimImage = VK_NULL_HANDLE;
|
||||
const Context* reclaimImageContext = nullptr;
|
||||
|
||||
VkDeviceSize mockImageReclaim(VkDeviceSize) {
|
||||
if (reclaimImage == VK_NULL_HANDLE) return 0;
|
||||
ReleaseImageMemory(*reclaimImageContext, reclaimImage, 10 * MiB, 0);
|
||||
reclaimImage = VK_NULL_HANDLE;
|
||||
return 10 * MiB;
|
||||
}
|
||||
|
||||
void ImageMemoryReclaim() {
|
||||
mock = MockDevice{};
|
||||
auto context = mockContext();
|
||||
context.cacheReclaim = mockImageReclaim;
|
||||
reclaimImageContext = &context;
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = 10 * MiB;
|
||||
allocation.memoryTypeIndex = 0;
|
||||
std::uint32_t type = 0;
|
||||
reclaimImage = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
mock.limit = 12 * MiB;
|
||||
auto request = allocation;
|
||||
request.allocationSize = 4 * MiB;
|
||||
const auto before = mock.attempts;
|
||||
try {
|
||||
const auto recovered = AllocateImageMemory(context, request, 1u, "test image", &type);
|
||||
Expect(recovered != VK_NULL_HANDLE && mock.attempts == before + 2 && mock.liveBytes == 4 * MiB && context.bufferPool->RetainedImages() == 0, "the image memory a cache release returns to the pool is freed before the retry");
|
||||
ReleaseImageMemory(context, recovered, 4 * MiB, 0);
|
||||
} catch (const std::exception& error) {
|
||||
Expect(false, std::string("the cache release of an image did not permit recovery: ") + error.what());
|
||||
}
|
||||
reclaimImageContext = nullptr;
|
||||
}
|
||||
|
||||
void ImageMemoryOverBudget() {
|
||||
mock = MockDevice{};
|
||||
auto context = budgetContext();
|
||||
reportedBudget = 1000 * MiB;
|
||||
reportedUsage = 500 * MiB;
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = 4 * MiB;
|
||||
allocation.memoryTypeIndex = 0;
|
||||
std::uint32_t type = 0;
|
||||
const auto first = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
ReleaseImageMemory(context, first, 4 * MiB, 0);
|
||||
Expect(mock.frees == 0 && context.bufferPool->RetainedImages() == 1, "image memory is retained while the heap is under its reported budget");
|
||||
reportedUsage = 1100 * MiB;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
const auto second = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
Expect(second == first && mock.allocations == 1, "a retained allocation is still handed out over the budget");
|
||||
ReleaseImageMemory(context, second, 4 * MiB, 0);
|
||||
Expect(mock.frees == 1 && context.bufferPool->RetainedImages() == 0, "image memory released while the heap is over its reported budget is freed");
|
||||
const auto third = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
ReleaseImageMemory(context, third, 4 * MiB, 0);
|
||||
reportedUsage = 500 * MiB;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
const auto fourth = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
ReleaseImageMemory(context, fourth, 4 * MiB, 0);
|
||||
Expect(mock.frees == 2 && context.bufferPool->RetainedImages() == 1, "the pool retains image memory again once the heap is under its budget");
|
||||
}
|
||||
|
||||
void ImageMemoryReuse() {
|
||||
mock = MockDevice{};
|
||||
auto context = mockContext();
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = 4 * MiB;
|
||||
allocation.memoryTypeIndex = 0;
|
||||
std::uint32_t type = 99;
|
||||
const auto first = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
Expect(type == 0 && mock.allocations == 1, "the first image allocates device memory of its type");
|
||||
ReleaseImageMemory(context, first, 4 * MiB, 0);
|
||||
Expect(mock.frees == 0 && context.bufferPool->RetainedImages() == 1, "a released device-local image allocation is retained");
|
||||
const auto again = AllocateImageMemory(context, allocation, 1u, "test image", &type);
|
||||
Expect(again == first && type == 0 && mock.allocations == 1 && context.bufferPool->RetainedImages() == 0, "the next image of the same type and size takes the retained allocation");
|
||||
auto smaller = allocation;
|
||||
smaller.allocationSize = 2 * MiB;
|
||||
const auto other = AllocateImageMemory(context, smaller, 1u, "test image", &type);
|
||||
Expect(other != again && mock.allocations == 2, "an image of another size allocates");
|
||||
ReleaseImageMemory(context, other, 2 * MiB, 0);
|
||||
ReleaseImageMemory(context, again, 4 * MiB, 0);
|
||||
auto host = allocation;
|
||||
host.memoryTypeIndex = 1;
|
||||
const auto placed = AllocateImageMemory(context, host, 2u, "test image", &type);
|
||||
Expect(type == 1 && mock.allocations == 3, "a host-visible image allocation is made");
|
||||
ReleaseImageMemory(context, placed, 4 * MiB, 1);
|
||||
Expect(mock.frees == 1 && context.bufferPool->RetainedImages() == 2, "a host-visible image allocation is freed when released");
|
||||
const auto unsized = AllocateImageMemory(context, smaller, 1u, "test image", &type);
|
||||
Expect(unsized == other && mock.allocations == 3 && context.bufferPool->RetainedImages() == 1, "an image of a retained size takes that allocation");
|
||||
ReleaseImageMemory(context, unsized, 0, 0);
|
||||
Expect(mock.frees == 2 && context.bufferPool->RetainedImages() == 1, "an allocation of unknown size is freed");
|
||||
std::vector<VkDeviceMemory> many;
|
||||
for (int i = 0; i < 33; ++i) many.push_back(AllocateImageMemory(context, allocation, 1u, "test image", &type));
|
||||
Expect(many.front() == again && mock.allocations == 35, "a burst takes the retained allocation first and allocates the rest");
|
||||
for (const auto memory : many) ReleaseImageMemory(context, memory, 4 * MiB, 0);
|
||||
Expect(context.bufferPool->RetainedImages() == 32 && mock.frees == 3 && !mock.memoryBytes.contains(many.front()) && mock.memoryBytes.contains(many.back()), "retained image memory is evicted least recently used under its budget");
|
||||
mock.results.push_back(VK_ERROR_OUT_OF_DEVICE_MEMORY);
|
||||
auto third = allocation;
|
||||
third.allocationSize = 3 * MiB;
|
||||
const auto recovered = AllocateImageMemory(context, third, 1u, "test image", &type);
|
||||
Expect(recovered != VK_NULL_HANDLE && mock.allocations == 36 && mock.frees == 35 && context.bufferPool->RetainedImages() == 0, "an allocation failure frees the retained image memory");
|
||||
ReleaseImageMemory(context, recovered, 3 * MiB, 0);
|
||||
Expect(mock.frees == 35 && context.bufferPool->RetainedImages() == 1, "the pool retains image memory again after a trim");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--small-device-retention") {
|
||||
@@ -937,6 +1043,9 @@ int main(int argc, char** argv) {
|
||||
PartialSlabDoesNotRetry();
|
||||
CacheReclaimRecovery();
|
||||
ImageCacheReclaim();
|
||||
ImageMemoryReuse();
|
||||
ImageMemoryReclaim();
|
||||
ImageMemoryOverBudget();
|
||||
HostPlacement();
|
||||
RefusedDevicePlacement();
|
||||
AttachmentReclaim();
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "Recompiler.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace ShaderRecompiler;
|
||||
|
||||
void require(bool condition, const std::string& message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint32_t, 4> CvtF16F32Store{0x7e021500u, 0xe0700000u, 0x80000100u, 0xbf810000u};
|
||||
|
||||
std::vector<std::uint32_t> recompile(bool preserve, bool flush, bool flushCapability = true) {
|
||||
const std::array<std::uint32_t, 4> capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityDenormPreserve, spv::CapabilityDenormFlushToZero};
|
||||
const std::array<std::uint32_t, 4> userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu};
|
||||
const std::array<std::string_view, 2> extensions{"SPV_KHR_storage_buffer_storage_class", "SPV_KHR_float_controls"};
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Compute, 0x20000u, CvtF16F32Store, 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 = std::span<const std::uint32_t>(capabilities).first(flushCapability ? capabilities.size() : capabilities.size() - 1);
|
||||
request.target.supportedExtensions = extensions;
|
||||
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
|
||||
request.target.maxWorkgroupInvocations = 1024;
|
||||
request.target.maxWorkgroupSharedMemoryBytes = 49152;
|
||||
request.target.float32DenormPreserve = preserve;
|
||||
request.target.float32FlushToZero = flush;
|
||||
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();
|
||||
}
|
||||
|
||||
struct Modes {
|
||||
bool preserve32 = false;
|
||||
bool flush32 = false;
|
||||
bool preserveCapability = false;
|
||||
};
|
||||
|
||||
Modes scan(std::span<const std::uint32_t> words) {
|
||||
Modes modes;
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> 16u;
|
||||
const auto op = static_cast<spv::Op>(words[cursor] & 0xffffu);
|
||||
require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction");
|
||||
if (op == spv::OpCapability && words[cursor + 1] == spv::CapabilityDenormPreserve) modes.preserveCapability = true;
|
||||
if (op == spv::OpExecutionMode && count == 4 && words[cursor + 3] == 32u) {
|
||||
if (words[cursor + 2] == spv::ExecutionModeDenormPreserve) modes.preserve32 = true;
|
||||
if (words[cursor + 2] == spv::ExecutionModeDenormFlushToZero) modes.flush32 = true;
|
||||
}
|
||||
cursor += count;
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
|
||||
void verify() {
|
||||
const auto preserved = scan(recompile(true, false));
|
||||
require(preserved.preserve32 && preserved.preserveCapability && !preserved.flush32, "float32DenormPreserve did not emit the 32-bit DenormPreserve execution mode and capability");
|
||||
const auto plain = scan(recompile(false, false));
|
||||
require(!plain.preserve32 && !plain.flush32, "a target without float32DenormPreserve or float32FlushToZero set a 32-bit denormal execution mode");
|
||||
const auto flushed = scan(recompile(true, true));
|
||||
require(flushed.flush32 && !flushed.preserve32, "the hardware 32-bit flush did not take precedence over the preserve flag");
|
||||
const auto nativeFlush = scan(recompile(true, true, false));
|
||||
require(!nativeFlush.preserve32 && !nativeFlush.flush32, "a hardware flush without the DenormFlushToZero capability declared a 32-bit denormal execution mode");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
verify();
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "F32DenormPreserve: %s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
std::puts("F32DenormPreserve: the 32-bit DenormPreserve execution mode follows the target flag and yields to the hardware flush");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp"
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Case {
|
||||
VkDriverId driver;
|
||||
VkBool32 flushToZero;
|
||||
VkShaderFloatControlsIndependence independence;
|
||||
bool probe;
|
||||
bool declared;
|
||||
};
|
||||
|
||||
constexpr Case Cases[]{
|
||||
{VK_DRIVER_ID_MOLTENVK, VK_FALSE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE, true, false},
|
||||
{VK_DRIVER_ID_MOLTENVK, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, true, true},
|
||||
{VK_DRIVER_ID_MESA_RADV, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, true, true},
|
||||
{VK_DRIVER_ID_MESA_RADV, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY, true, true},
|
||||
{VK_DRIVER_ID_MESA_RADV, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE, false, false},
|
||||
{VK_DRIVER_ID_NVIDIA_PROPRIETARY, VK_FALSE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL, false, false},
|
||||
{VK_DRIVER_ID_AMD_PROPRIETARY, VK_TRUE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY, true, true},
|
||||
{static_cast<VkDriverId>(0), VK_FALSE, VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE, false, false},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int failures = 0;
|
||||
for (const auto& item : Cases) {
|
||||
const auto control = AgcDriver::Float32FlushControlFor(item.driver, item.flushToZero, item.independence);
|
||||
if (control.probe != item.probe || control.declared != item.declared) {
|
||||
std::fprintf(stderr, "driver %d flush %u independence %d: expected probe %d declared %d, got %d %d\n", static_cast<int>(item.driver), static_cast<unsigned>(item.flushToZero), static_cast<int>(item.independence), item.probe ? 1 : 0, item.declared ? 1 : 0, control.probe ? 1 : 0, control.declared ? 1 : 0);
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -521,6 +521,12 @@ void stateTests() {
|
||||
queue.shader[0x008] = 0;
|
||||
Require(AgcDriver::Graphics::PixelProgramSkipped(queue), "a zero pixel program address was not read as unset");
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, true).find("writes color") != std::string::npos, "a draw without a pixel program that writes color was accepted");
|
||||
auto colorDisabled = queue;
|
||||
colorDisabled.context[0x202] = colorDisabled.context.at(0x202) & ~0x70u;
|
||||
Require(AgcDriver::Graphics::DrawRejection(colorDisabled, true).empty(), "a draw without a pixel program was rejected while CB_COLOR_CONTROL disables color");
|
||||
Require(!AgcDriver::Graphics::DecodeState(colorDisabled).hasColorTarget, "a draw with CB_COLOR_CONTROL disabling color decoded a color target");
|
||||
colorDisabled.context.erase(0x202);
|
||||
Require(AgcDriver::Graphics::DrawRejection(colorDisabled, true).find("writes color") != std::string::npos, "a draw without a pixel program and an unwritten CB_COLOR_CONTROL was accepted");
|
||||
queue.shader.erase(0x008);
|
||||
queue.shader.erase(0x009);
|
||||
Require(AgcDriver::Graphics::PixelProgramSkipped(queue), "an unwritten pixel program address was not read as unset");
|
||||
@@ -8393,6 +8399,25 @@ void LineStripTests() {
|
||||
Require(rejection.empty(), "the draw precheck rejected an indexed line strip with primitive restart: " + rejection);
|
||||
}
|
||||
|
||||
std::uint32_t keyedGenerator = 0;
|
||||
std::size_t keyedWords = 0;
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL keyedCreateShaderModule(VkDevice, const VkShaderModuleCreateInfo* info, const VkAllocationCallbacks*, VkShaderModule* module) {
|
||||
keyedWords = info->codeSize / sizeof(std::uint32_t);
|
||||
keyedGenerator = info->pCode[2];
|
||||
*module = makeHandle<VkShaderModule>();
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
void ShaderModuleKeyTests() {
|
||||
const std::array<std::uint32_t, 5> code{0x07230203u, 0x00010300u, 0x00080001u, 8u, 0u};
|
||||
static_cast<void>(AgcDriver::Graphics::CreateShaderModule(keyedCreateShaderModule, VK_NULL_HANDLE, true, code.data(), sizeof(code), "keyed module"));
|
||||
Require(keyedWords == code.size() && keyedGenerator == 0x00080001u, "a shader module with graphics pipeline libraries changed its generator word");
|
||||
static_cast<void>(AgcDriver::Graphics::CreateShaderModule(keyedCreateShaderModule, VK_NULL_HANDLE, false, code.data(), sizeof(code), "keyed module"));
|
||||
Require(keyedWords == code.size() && keyedGenerator == (0x00080001u ^ 0x8000u), "a shader module without graphics pipeline libraries kept the generator word of the other mode");
|
||||
Require(code[2] == 0x00080001u, "keying a shader module changed its source words");
|
||||
}
|
||||
|
||||
void ProvokingVertexTests() {
|
||||
using namespace AgcDriver::Graphics;
|
||||
constexpr std::uint32_t lastVertex = 1u << 19u;
|
||||
@@ -9506,6 +9531,24 @@ void highestDrawIndexTests() {
|
||||
Require(HighestDrawIndex(bytesOf(restartOnly), 2, false) == 0xffff, "a 16-bit all-ones index without restart was skipped");
|
||||
Require(HighestDrawIndex(bytesOf(narrow).first(4), 2, true) == 7 && HighestDrawIndex(bytesOf(narrow).first(2), 2, true) == 0, "the scan read past its index range");
|
||||
expectFailure([&] { HighestDrawIndex(bytesOf(narrow), 1, false); }, "unsupported index size");
|
||||
Require(!HighestDrawIndex({}, 2, false).has_value() && !HighestDrawIndex({}, 4, true).has_value(), "an empty index range reached a vertex");
|
||||
std::vector<std::uint16_t> long16(1027, 0xffff);
|
||||
long16[1026] = 0;
|
||||
Require(HighestDrawIndex(std::as_bytes(std::span(long16)), 2, true) == 0, "index 0 after many restart indices was not reached");
|
||||
long16[513] = 4000;
|
||||
Require(HighestDrawIndex(std::as_bytes(std::span(long16)), 2, true) == 4000, "a 16-bit index inside a long range was missed");
|
||||
std::vector<std::uint32_t> long32(1029);
|
||||
for (std::size_t i = 0; i < long32.size(); ++i) long32[i] = static_cast<std::uint32_t>(i * 7u % 1000u);
|
||||
long32[1028] = 0x80000001u;
|
||||
Require(HighestDrawIndex(std::as_bytes(std::span(long32)), 4, true) == 0x80000001u, "a 32-bit index above 2^31 in the tail was missed");
|
||||
long32[1028] = 0xffffffffu;
|
||||
Require(HighestDrawIndex(std::as_bytes(std::span(long32)), 4, true) == 999u && HighestDrawIndex(std::as_bytes(std::span(long32)), 4, false) == 0xffffffffu, "a 32-bit restart index at the end of a long range");
|
||||
std::vector<std::byte> shifted(2 * 1000 + 3);
|
||||
for (std::size_t i = 0; i < 1000; ++i) {
|
||||
const auto value = static_cast<std::uint16_t>(i == 777 ? 0x8001u : i % 300u);
|
||||
std::memcpy(shifted.data() + 1 + 2 * i, &value, sizeof(value));
|
||||
}
|
||||
Require(HighestDrawIndex(std::span<const std::byte>(shifted).subspan(1, 2 * 1000 + 1), 2, false) == 0x8001u, "an unaligned 16-bit range with a trailing byte");
|
||||
}
|
||||
|
||||
void storeAtFlipTests() {
|
||||
@@ -10391,6 +10434,7 @@ int main(int argc, char** argv) {
|
||||
stencilReferenceUnusedTests();
|
||||
aliasedBufferViewTests();
|
||||
LineStripTests();
|
||||
ShaderModuleKeyTests();
|
||||
ProvokingVertexTests();
|
||||
ProvokingVertexRenderPassTests();
|
||||
ConservativeRasterizationTests();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
@@ -125,6 +126,29 @@ void hostState() {
|
||||
Expect(!AgcDriver::CurrentHostHdr().available, "a display switched to SDR leaves HDR output available");
|
||||
}
|
||||
|
||||
void forceSdr() {
|
||||
resetHost();
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_FORCE_SDR", "1");
|
||||
#else
|
||||
setenv("APS5_FORCE_SDR", "1", 1);
|
||||
#endif
|
||||
AgcDriverSetSurfaceHdr10_nid_postfix(true);
|
||||
AgcDriverSetHostDisplayHdr_nid_postfix(AgcDriver::DisplayHdrFromOutput(true, 800.0f, 0.01f, 400.0f));
|
||||
const auto forced = AgcDriver::CurrentHostHdr();
|
||||
Expect(!forced.available && forced.display.hdr && forced.surfaceHdr10, "forcing SDR does not suppress HDR availability while preserving host capabilities");
|
||||
Expect(AgcDriver::ToneMapPeakNits(forced) == 100.0f, "forcing SDR does not report SDR reference white");
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_FORCE_SDR", "");
|
||||
#else
|
||||
unsetenv("APS5_FORCE_SDR");
|
||||
#endif
|
||||
AgcDriverSetSurfaceHdr10_nid_postfix(true);
|
||||
const auto restored = AgcDriver::CurrentHostHdr();
|
||||
Expect(restored.available && restored.generation != forced.generation, "removing the SDR override does not restore HDR availability");
|
||||
resetHost();
|
||||
}
|
||||
|
||||
void reportedWhiteLevel() {
|
||||
resetHost();
|
||||
constexpr std::uint64_t rgbPq = 0x8100070422000000ull;
|
||||
@@ -158,6 +182,7 @@ int main() {
|
||||
whiteLevels();
|
||||
hostState();
|
||||
reportedWhiteLevel();
|
||||
forceSdr();
|
||||
if (failures != 0) return 1;
|
||||
std::puts("host HDR tests passed");
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "HostImportFailure.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
enum class Step { None, Create, Allocate, Bind };
|
||||
|
||||
struct Probe {
|
||||
PFN_vkGetDeviceProcAddr backend = nullptr;
|
||||
Step fail = Step::None;
|
||||
VkResult result = VK_SUCCESS;
|
||||
unsigned injected = 0;
|
||||
unsigned invalid = 0;
|
||||
std::set<VkBuffer> buffers;
|
||||
std::set<VkDeviceMemory> memories;
|
||||
};
|
||||
|
||||
Probe probe;
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL CreateBuffer(VkDevice device, const VkBufferCreateInfo* info, const VkAllocationCallbacks* allocator, VkBuffer* buffer) {
|
||||
if (probe.fail == Step::Create) {
|
||||
probe.fail = Step::None;
|
||||
++probe.injected;
|
||||
*buffer = reinterpret_cast<VkBuffer>(std::uintptr_t{0xdeadbeef});
|
||||
return probe.result;
|
||||
}
|
||||
const auto result = reinterpret_cast<PFN_vkCreateBuffer>(probe.backend(device, "vkCreateBuffer"))(device, info, allocator, buffer);
|
||||
if (result == VK_SUCCESS) probe.buffers.insert(*buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL AllocateMemory(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) {
|
||||
if (probe.fail == Step::Allocate) {
|
||||
probe.fail = Step::None;
|
||||
++probe.injected;
|
||||
*memory = reinterpret_cast<VkDeviceMemory>(std::uintptr_t{0xdeadbeef});
|
||||
return probe.result;
|
||||
}
|
||||
const auto result = reinterpret_cast<PFN_vkAllocateMemory>(probe.backend(device, "vkAllocateMemory"))(device, info, allocator, memory);
|
||||
if (result == VK_SUCCESS) probe.memories.insert(*memory);
|
||||
return result;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL BindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize offset) {
|
||||
if (probe.fail == Step::Bind) {
|
||||
probe.fail = Step::None;
|
||||
++probe.injected;
|
||||
return probe.result;
|
||||
}
|
||||
return reinterpret_cast<PFN_vkBindBufferMemory>(probe.backend(device, "vkBindBufferMemory"))(device, buffer, memory, offset);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL DestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* allocator) {
|
||||
if (probe.buffers.erase(buffer) != 1) {
|
||||
++probe.invalid;
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<PFN_vkDestroyBuffer>(probe.backend(device, "vkDestroyBuffer"))(device, buffer, allocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL FreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* allocator) {
|
||||
if (probe.memories.erase(memory) != 1) {
|
||||
++probe.invalid;
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<PFN_vkFreeMemory>(probe.backend(device, "vkFreeMemory"))(device, memory, allocator);
|
||||
}
|
||||
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL DeviceProc(VkDevice device, const char* name) {
|
||||
if (std::strcmp(name, "vkCreateBuffer") == 0) return reinterpret_cast<PFN_vkVoidFunction>(CreateBuffer);
|
||||
if (std::strcmp(name, "vkAllocateMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(AllocateMemory);
|
||||
if (std::strcmp(name, "vkBindBufferMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(BindBufferMemory);
|
||||
if (std::strcmp(name, "vkDestroyBuffer") == 0) return reinterpret_cast<PFN_vkVoidFunction>(DestroyBuffer);
|
||||
if (std::strcmp(name, "vkFreeMemory") == 0) return reinterpret_cast<PFN_vkVoidFunction>(FreeMemory);
|
||||
return probe.backend(device, name);
|
||||
}
|
||||
|
||||
constexpr std::size_t Alignment = 65536;
|
||||
constexpr std::size_t Bytes = 4096;
|
||||
|
||||
class Heap {
|
||||
public:
|
||||
Heap() {
|
||||
#ifdef _WIN32
|
||||
data = static_cast<std::byte*>(VirtualAlloc(nullptr, Alignment, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
data = static_cast<std::byte*>(std::aligned_alloc(Alignment, Alignment));
|
||||
#endif
|
||||
Require(data != nullptr, "cannot allocate the host import failure heap");
|
||||
std::memset(data, 0x37, Alignment);
|
||||
GuestAllocations::Mutation().Add(data, Alignment, true, true, true);
|
||||
}
|
||||
|
||||
~Heap() {
|
||||
GuestAllocations::Mutation().Remove(data);
|
||||
#ifdef _WIN32
|
||||
VirtualFree(data, 0, MEM_RELEASE);
|
||||
#else
|
||||
std::free(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::byte* Data() const { return data; }
|
||||
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
|
||||
|
||||
private:
|
||||
std::byte* data = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void AgcDriver::Tests::RunHostImportFailureTests(const Graphics::Context& original, bool native) {
|
||||
unsigned failures = 0;
|
||||
for (const auto step : {Step::Create, Step::Allocate, Step::Bind}) {
|
||||
for (const auto result : {VK_ERROR_OUT_OF_HOST_MEMORY, VK_ERROR_OUT_OF_DEVICE_MEMORY}) {
|
||||
Heap heap;
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
ClearHostImports(original.device);
|
||||
probe = {};
|
||||
probe.backend = original.deviceProc;
|
||||
probe.fail = step;
|
||||
probe.result = result;
|
||||
auto context = original;
|
||||
context.deviceProc = DeviceProc;
|
||||
struct Cleanup {
|
||||
VkDevice device;
|
||||
~Cleanup() { ClearHostImports(device); }
|
||||
} cleanup{context.device};
|
||||
SetImportWatch(context, ImportWatch::Unwatch);
|
||||
const HostImport* failed = nullptr;
|
||||
bool threw = false;
|
||||
try {
|
||||
failed = HostImportFor(context, heap.Address(), Bytes);
|
||||
} catch (const std::runtime_error&) {
|
||||
threw = true;
|
||||
}
|
||||
const bool rejected = failed == nullptr && probe.injected == 1 && ExistingHostImportFor(context, heap.Address(), Bytes) == nullptr && HostImportLiveBytes() == 0;
|
||||
const bool released = probe.buffers.empty() && probe.memories.empty();
|
||||
const auto invalid = probe.invalid;
|
||||
ClearHostImports(context.device);
|
||||
probe.fail = Step::None;
|
||||
const auto* recovered = HostImportFor(context, heap.Address(), Bytes);
|
||||
Require(recovered != nullptr && recovered->buffer != VK_NULL_HANDLE && recovered->memory != VK_NULL_HANDLE && recovered->address != 0, "host import retry failed");
|
||||
if (native) {
|
||||
Buffer readback(original, Bytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
std::memset(readback.Bytes().data(), 0, Bytes);
|
||||
CommandBatch batch(original);
|
||||
RecordMemoryBarrier(original, batch.Handle(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
CopyBuffer(original, batch.Handle(), recovered->buffer, heap.Address() - recovered->base, readback.Handle(), 0, Bytes);
|
||||
RecordMemoryBarrier(original, batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
batch.SubmitAndWait();
|
||||
readback.Invalidate();
|
||||
Require(std::memcmp(heap.Data(), readback.Bytes().data(), Bytes) == 0, "host import retry returned incorrect GPU readback data");
|
||||
}
|
||||
ClearHostImports(context.device);
|
||||
const bool clean = probe.buffers.empty() && probe.memories.empty() && HostImportLiveBytes() == 0;
|
||||
std::printf("host import step=%u result=%d injected=%u rejected=%u threw=%u invalid releases=%u failure cleaned=%u retry cleaned=%u readbacks=%u\n", static_cast<unsigned>(step), static_cast<int>(result), probe.injected, rejected, threw, invalid, released, clean, native ? 1u : 0u);
|
||||
if (!rejected || !released || !clean || invalid != 0 || probe.invalid != invalid) ++failures;
|
||||
}
|
||||
}
|
||||
Require(failures == 0, "failed host import creation released an undefined handle or leaked a resource");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_HOSTIMPORTFAILURE_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_HOSTIMPORTFAILURE_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
|
||||
namespace AgcDriver::Tests {
|
||||
|
||||
void RunHostImportFailureTests(const Graphics::Context& context, bool native);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "HostImportFailure.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
@@ -372,6 +373,7 @@ int main() {
|
||||
WindowPromotionOutlivesTheHold();
|
||||
IdleRetireOutlivesTheHold();
|
||||
BusyRetireOutlivesALaterBatch();
|
||||
AgcDriver::Tests::RunHostImportFailureTests(mockContext(), false);
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
return 1;
|
||||
|
||||
@@ -117,6 +117,54 @@ void ReleaseWatched(void* block, std::size_t bytes) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool partialWatchRestorationTests() {
|
||||
#ifdef _WIN32
|
||||
constexpr std::size_t bytes = 131072;
|
||||
constexpr std::size_t page = 4096;
|
||||
constexpr std::size_t restoredOffset = 4 * page;
|
||||
constexpr std::size_t restoredBytes = 4 * page;
|
||||
if (!AgcDriver::GuestMemory::WriteWatched()) return false;
|
||||
auto* memory = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536));
|
||||
if (memory == nullptr) return false;
|
||||
GuestArena::GuestArenaCommit_nid_postfix(memory, bytes, PAGE_READWRITE, restoredBytes);
|
||||
struct Cleanup {
|
||||
std::uint8_t* memory;
|
||||
~Cleanup() { ReleaseWatched(memory, bytes); }
|
||||
} cleanup{memory};
|
||||
const auto address = reinterpret_cast<std::uint64_t>(memory);
|
||||
AgcDriver::GuestMemory::Unwatch(address, 65536);
|
||||
Require(!AgcDriver::GuestMemory::Watched(address, page), "the partial remap fixture was not excluded");
|
||||
memory[page] = 0x22;
|
||||
AgcDriver::GuestMemory::MarkWritten(address + page, 1);
|
||||
Require(AgcDriver::GuestMemory::UntrackedHint(address + page, 1) != 0, "the excluded remap fixture has no driver hint");
|
||||
GuestArena::GuestArenaReset_nid_postfix(memory + restoredOffset, restoredBytes);
|
||||
GuestArena::GuestArenaCommit_nid_postfix(memory + restoredOffset, restoredBytes, PAGE_READWRITE, restoredBytes);
|
||||
Require(AgcDriver::GuestMemory::Watched(address + restoredOffset, restoredBytes), "a partial private remap did not restore its write watch");
|
||||
Require(!AgcDriver::GuestMemory::Watched(address, restoredOffset), "a partial remap restored an excluded prefix");
|
||||
Require(!AgcDriver::GuestMemory::Watched(address + restoredOffset + restoredBytes, page), "a partial remap restored an excluded suffix");
|
||||
Require(AgcDriver::GuestMemory::UntrackedHint(address + page, 1) == 0, "a partial remap retained a stale driver hint");
|
||||
std::memset(memory + restoredOffset, 0x33, restoredBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
const auto generation = AgcDriver::GuestMemory::CollectWrites(address + restoredOffset, restoredBytes);
|
||||
Require(generation != 0, "the private remap has no collected CPU write history");
|
||||
memory[restoredOffset + page] = 0x44;
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Require(AgcDriver::GuestMemory::CollectWrites(address + restoredOffset, restoredBytes) != 0, "the private remap stopped collecting CPU writes");
|
||||
Require(!AgcDriver::GuestMemory::UnchangedSince(address + restoredOffset, restoredBytes, generation), "the private remap hid a genuine CPU write");
|
||||
GuestArena::GuestArenaReset_nid_postfix(memory, restoredBytes);
|
||||
GuestArena::GuestArenaCommit_nid_postfix(memory, restoredBytes, PAGE_READWRITE, restoredBytes);
|
||||
Require(AgcDriver::GuestMemory::Watched(address, restoredBytes), "the first private pages did not regain coverage");
|
||||
Require(!AgcDriver::GuestMemory::Watched(address + 12 * page, page), "restoring the first pages restored an excluded tail");
|
||||
memory[12 * page] = 0x55;
|
||||
AgcDriver::GuestMemory::MarkWritten(address + 12 * page, 1);
|
||||
AgcDriver::GuestMemory::Unwatch(address + 12 * page, page);
|
||||
Require(AgcDriver::GuestMemory::UntrackedHint(address + 12 * page, 1) == 0, "excluding a partially watched block invented a write-watch hint");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::size_t> selectDevice(std::span<const VkPhysicalDeviceProperties> devices, std::string_view request) {
|
||||
const auto rankDeviceType = [](VkPhysicalDeviceType type) {
|
||||
switch (type) {
|
||||
@@ -6630,7 +6678,32 @@ void queuedWriteGroupTests() {
|
||||
}
|
||||
return memory;
|
||||
};
|
||||
const auto recorded = [](const std::vector<Write>& writes) {
|
||||
const auto order = Recorder::QueuedWriteOrder(writes);
|
||||
std::vector<Write> ordered;
|
||||
for (const auto index : order) ordered.push_back(writes[index]);
|
||||
const auto groups = Recorder::QueuedWriteGroups(ordered);
|
||||
std::vector<std::size_t> sequence;
|
||||
std::size_t first = 0;
|
||||
for (const auto end : groups) {
|
||||
for (const bool computed : {false, true}) {
|
||||
for (auto i = first; i < end; ++i) {
|
||||
if (ordered[i].computed == computed) sequence.push_back(order[i]);
|
||||
}
|
||||
}
|
||||
first = end;
|
||||
}
|
||||
return std::pair{sequence, groups.size()};
|
||||
};
|
||||
Require(Recorder::QueuedWriteGroups({}).empty(), "no queued writes made a group");
|
||||
const std::vector<Write> flagged{{0x100, 0x200, false}, {0x100, 0x1f8, true}, {0x108, 0x200, true}, {0x200, 0x208, false}, {0x400, 0x500, false}, {0x400, 0x4f8, true}, {0x408, 0x500, true}, {0x500, 0x508, false}, {0x800, 0x804, false}};
|
||||
Require(recorded(flagged) == std::pair{std::vector<std::size_t>{0, 4, 1, 2, 5, 6, 3, 7, 8}, std::size_t{2}}, "the dumps of a run with a flag after each query slot were not folded once, with the clears before them and the flags after them");
|
||||
const std::vector<Write> open{{0x50, 0x58, false}, {0x100, 0x200, false}, {0x100, 0x1f8, true}, {0x108, 0x200, true}, {0x200, 0x208, false}, {0x400, 0x500, false}, {0x400, 0x4f8, true}};
|
||||
Require(recorded(open) == std::pair{std::vector<std::size_t>{0, 1, 5, 2, 3, 6, 4}, std::size_t{2}}, "the clear of a slot whose end dump the run does not hold was not moved ahead of the dumps");
|
||||
const std::vector<Write> overDump{{0x100, 0x200, false}, {0x100, 0x1f8, true}, {0x108, 0x200, true}, {0x1f0, 0x410, false}, {0x400, 0x4f8, true}};
|
||||
Require(Recorder::QueuedWriteOrder(overDump) == std::vector<std::size_t>{0, 1, 2, 3, 4}, "a store over an earlier dump that the later dumps cover only in part was moved ahead of that dump");
|
||||
const std::vector<Write> overFlag{{0x100, 0x200, false}, {0x100, 0x1f8, true}, {0x108, 0x200, true}, {0x500, 0x508, false}, {0x400, 0x508, false}, {0x400, 0x4f8, true}, {0x408, 0x500, true}};
|
||||
Require(Recorder::QueuedWriteOrder(overFlag) == std::vector<std::size_t>{0, 1, 2, 3, 4, 5, 6}, "a clear over an earlier store after a dump was moved ahead of that store");
|
||||
const std::vector<Write> query{{0x100, 0x200, false}, {0x100, 0x1f8, true}, {0x108, 0x200, true}, {0x200, 0x300, false}, {0x200, 0x2f8, true}, {0x208, 0x300, true}};
|
||||
Require(Recorder::QueuedWriteGroups(query) == Groups{3, 6}, "the clear of the next query slot did not wait for the dumps before it");
|
||||
const std::vector<Write> reused{{0x100, 0x1f8, true}, {0x100, 0x200, false}, {0x100, 0x1f8, true}};
|
||||
@@ -6660,6 +6733,26 @@ void queuedWriteGroupTests() {
|
||||
const auto groups = Recorder::QueuedWriteGroups(writes);
|
||||
Require(!groups.empty() && groups.back() == writes.size() && std::is_sorted(groups.begin(), groups.end()), "queued write groups do not end at the last write in order");
|
||||
Require(paint(writes, groups, bytes) == programOrder(writes, bytes), "recording each group's stores before its computed writes changed the final bytes of the program order");
|
||||
const auto sequence = recorded(writes).first;
|
||||
std::vector<std::size_t> position(writes.size(), writes.size());
|
||||
for (std::size_t i = 0; i < sequence.size(); ++i) position[sequence[i]] = i;
|
||||
Require(sequence.size() == writes.size() && std::find(position.begin(), position.end(), writes.size()) == position.end(), "the recorded order is not a permutation of the queued writes");
|
||||
std::vector<std::uint8_t> memory(bytes, 0);
|
||||
for (const auto index : sequence) {
|
||||
for (auto at = writes[index].begin; at < writes[index].end; ++at) memory[at] = static_cast<std::uint8_t>(index + 1);
|
||||
}
|
||||
Require(memory == programOrder(writes, bytes), "the recorded order changed the final bytes of the program order");
|
||||
for (std::size_t j = 0; j < writes.size(); ++j) {
|
||||
if (writes[j].computed) continue;
|
||||
std::vector<bool> covered(bytes);
|
||||
for (auto k = j + 1; k < writes.size(); ++k) {
|
||||
if (writes[k].computed) std::fill(covered.begin() + writes[k].begin, covered.begin() + writes[k].end, true);
|
||||
}
|
||||
if (std::any_of(covered.begin() + writes[j].begin, covered.begin() + writes[j].end, [](bool value) { return value; })) continue;
|
||||
for (std::size_t i = 0; i < j; ++i) {
|
||||
Require(position[i] < position[j], writes[i].computed ? "a store no later dump overlaps was recorded before a dump ahead of it" : "a store no later dump overlaps was recorded before a store ahead of it");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9279,6 +9372,216 @@ void integerClearTests(const Device& device) {
|
||||
std::cout << "integer storage clears: " << tested << " formats ok\n";
|
||||
}
|
||||
|
||||
bool clearHandOverTests(const Device& device, Recorder& recorder, bool watched) {
|
||||
const auto& base = device.GetContext();
|
||||
const std::string what = watched ? "clear-key hand-over (watched)" : "clear-key hand-over";
|
||||
if (base.hostImportAlignment == 0) {
|
||||
std::cout << "host imports unavailable: " << what << " not tested\n";
|
||||
return false;
|
||||
}
|
||||
constexpr std::size_t surfaceA = 256 * 256 * 4;
|
||||
constexpr std::size_t surfaceB = 128 * 128 * 8;
|
||||
constexpr std::size_t keyCount = surfaceB / 256;
|
||||
constexpr std::size_t bytes = surfaceA + 65536;
|
||||
static_assert(surfaceB <= surfaceA && keyCount <= bytes - surfaceA, "B lies over the first units of A, and B's DCC keys follow A inside the block");
|
||||
const auto decided = PrepareImportWatch(base);
|
||||
#ifndef _WIN32
|
||||
if (watched && decided == ImportWatch::Unwatch) {
|
||||
std::cout << "host imports are compared on this device: " << what << " not tested\n";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
void* block = nullptr;
|
||||
if (watched) {
|
||||
block = AllocateWatched(bytes, 65536);
|
||||
if (block == nullptr) {
|
||||
std::cout << "no write watching: " << what << " not tested\n";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
#else
|
||||
block = std::aligned_alloc(65536, bytes);
|
||||
#endif
|
||||
}
|
||||
Require(block != nullptr && reinterpret_cast<std::uint64_t>(block) % 65536 == 0, "cannot allocate the clear-key hand-over block");
|
||||
if (watched) SetImportWatch(base, ImportWatch::Watch);
|
||||
struct RestoreWatch {
|
||||
const Context& context;
|
||||
ImportWatch decided;
|
||||
~RestoreWatch() { SetImportWatch(context, decided); }
|
||||
} restoreWatch{base, decided};
|
||||
auto* memory = static_cast<std::uint8_t*>(block);
|
||||
const auto address = reinterpret_cast<std::uint64_t>(block);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(block, bytes, true, true, true);
|
||||
}
|
||||
struct Unregister {
|
||||
const Context& context;
|
||||
void* block;
|
||||
std::uint64_t address;
|
||||
bool watched;
|
||||
~Unregister() {
|
||||
StorageTexture::SetClearHandOver(true);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(block);
|
||||
}
|
||||
HostImportFor(context, address, bytes);
|
||||
if (watched) ReleaseWatched(block, bytes);
|
||||
}
|
||||
} unregister{base, block, address, watched};
|
||||
if (HostImportFor(base, address, bytes) == nullptr) {
|
||||
std::cout << "host import of the clear-key hand-over block refused: " << what << " not tested\n";
|
||||
return false;
|
||||
}
|
||||
if (watched && !AgcDriver::GuestMemory::Watched(address, bytes)) {
|
||||
std::cout << "the imported block left the write watch: " << what << " not tested\n";
|
||||
return false;
|
||||
}
|
||||
TextureDetiler detiler(base);
|
||||
auto context = base;
|
||||
context.detiler = &detiler;
|
||||
const auto surface = [&](std::uint32_t width, std::uint32_t height, std::uint32_t format, std::uint64_t dcc) {
|
||||
GuestTextureResource resource{};
|
||||
resource.baseAddress = address;
|
||||
resource.width = width;
|
||||
resource.height = height;
|
||||
resource.mipCount = 1;
|
||||
resource.tileMode = TextureTileMode::kR64KBX;
|
||||
resource.dimension = TextureDimension::k2D;
|
||||
resource.format = format;
|
||||
resource.dstSelX = 4;
|
||||
resource.dstSelY = 5;
|
||||
resource.dstSelZ = 6;
|
||||
resource.dstSelW = 7;
|
||||
resource.dccAddress = dcc;
|
||||
return resource;
|
||||
};
|
||||
const auto resourceA = surface(256, 256, 56, 0);
|
||||
const auto resourceB = surface(128, 128, 71, address + surfaceA);
|
||||
Require(DescribeSurface(resourceA).guestBytes == surfaceA && DescribeSurface(resourceB).guestBytes == surfaceB, "the clear-key hand-over surfaces have unexpected sizes");
|
||||
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
const auto draw = [&](const std::shared_ptr<StorageTexture>& image, VkClearColorValue value) {
|
||||
const auto commands = recorder.Commands();
|
||||
recorder.Keep(image);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
context.Function<PFN_vkCmdClearColorImage>("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1, &range);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
image->MarkDirty();
|
||||
};
|
||||
const auto readImage = [&](const std::shared_ptr<StorageTexture>& image, std::uint32_t width, std::uint32_t height, std::size_t texelBytes) {
|
||||
Buffer readback(context, static_cast<std::size_t>(width) * height * texelBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
const auto commands = recorder.Commands();
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
VkBufferImageCopy copy{};
|
||||
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
copy.imageExtent = {width, height, 1};
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, readback.Handle(), 1, ©);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
recorder.Submit();
|
||||
device.WaitQueue();
|
||||
recorder.Sync();
|
||||
const auto pixels = readback.Bytes();
|
||||
return std::vector<std::byte>(pixels.begin(), pixels.end());
|
||||
};
|
||||
const auto readMemory = [&](std::uint64_t from, std::size_t length) {
|
||||
std::vector<std::byte> read(length);
|
||||
AgcDriver::GuestMemory::Read(from, read);
|
||||
recorder.Sync();
|
||||
if (!watched) std::memcpy(read.data(), memory + (from - address), length);
|
||||
return read;
|
||||
};
|
||||
const auto filled = [](std::span<const std::byte> read, std::span<const std::uint8_t> texel) {
|
||||
for (std::size_t i = 0; i < read.size(); ++i) {
|
||||
if (std::to_integer<std::uint8_t>(read[i]) != texel[i % texel.size()]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
constexpr std::array<std::uint8_t, 4> red{255, 0, 0, 255};
|
||||
constexpr std::array<std::uint8_t, 4> blue{0, 0, 255, 255};
|
||||
constexpr std::array<std::uint8_t, 8> green{0, 0, 0x00, 0x3c, 0, 0, 0x00, 0x3c};
|
||||
constexpr std::array<std::uint8_t, 8> cleared{};
|
||||
enum class Order { Superseded, ReadFirst, Rewritten, CpuStore };
|
||||
struct Outcome {
|
||||
std::vector<std::byte> bytes;
|
||||
std::vector<std::byte> imageB;
|
||||
std::uint64_t handOvers = 0;
|
||||
std::uint64_t superseded = 0;
|
||||
};
|
||||
const auto run = [&](bool on, Order order) {
|
||||
const std::string where = what + (order == Order::Superseded ? ", A never read again" : order == Order::ReadFirst ? ", A read before B wrote" : order == Order::Rewritten ? ", A written again after the hand-over" : ", a CPU store before B wrote") + (on ? ", on" : ", off");
|
||||
StorageTexture::SetClearHandOver(on);
|
||||
recorder.Sync();
|
||||
std::memset(memory, 0x55, surfaceA);
|
||||
std::memset(memory + surfaceA, 0x00, keyCount);
|
||||
Outcome outcome{};
|
||||
const auto handOvers = StorageTexture::ClearHandOvers();
|
||||
const auto superseded = StorageTexture::HandedOverSuperseded();
|
||||
{
|
||||
auto imageA = StorageTexture::Create(context, detiler, resourceA, 0);
|
||||
auto imageB = StorageTexture::Create(context, detiler, resourceB, 0);
|
||||
draw(imageA, {{1.0f, 0.0f, 0.0f, 1.0f}});
|
||||
imageB->Refresh();
|
||||
if (on && watched) {
|
||||
Require(StorageTexture::ClearHandOvers() == handOvers + 1, where + ": B's refresh under clear keys did not hand A's results over");
|
||||
Require(StorageTexture::FindPending(address, surfaceA) == imageA, where + ": A's results did not stay pending");
|
||||
Require(std::all_of(memory, memory + 2 * 65536, [](std::uint8_t value) { return value == 0x55; }), where + ": B's refresh stored A's results over B");
|
||||
} else {
|
||||
Require(StorageTexture::ClearHandOvers() == handOvers, where + ": the hand-over ran while off");
|
||||
}
|
||||
outcome.imageB = readImage(imageB, 128, 128, 8);
|
||||
Require(filled(outcome.imageB, cleared), where + ": B does not hold its keys' clear");
|
||||
if (order == Order::ReadFirst) {
|
||||
Require(filled(readMemory(address, 65536), red), where + ": a read of A's memory before B wrote it did not see A's results");
|
||||
imageA->Refresh();
|
||||
Require(filled(readImage(imageA, 256, 256, 4), red), where + ": A read again as A lost its results");
|
||||
}
|
||||
if (order == Order::Rewritten) {
|
||||
draw(imageA, {{0.0f, 0.0f, 1.0f, 1.0f}});
|
||||
Require(filled(readMemory(address, 65536), blue), where + ": a read of A's memory did not see A's newer results");
|
||||
imageB->Refresh();
|
||||
Require(filled(readImage(imageB, 128, 128, 8), cleared), where + ": B refreshed again does not hold its keys' clear");
|
||||
}
|
||||
if (order == Order::CpuStore) {
|
||||
std::memset(memory + 65536, 0x77, 65536);
|
||||
}
|
||||
draw(imageB, {{0.0f, 1.0f, 0.0f, 1.0f}});
|
||||
Require(StorageTexture::FlushPending(address, bytes, nullptr, "clear-key hand-over test", PublishScope::Whole), where + ": nothing was pending at the end");
|
||||
recorder.Submit();
|
||||
device.WaitQueue();
|
||||
recorder.Sync();
|
||||
outcome.bytes = readMemory(address, surfaceA);
|
||||
Require(filled(readImage(imageB, 128, 128, 8), green), where + ": B lost its results");
|
||||
}
|
||||
outcome.handOvers = StorageTexture::ClearHandOvers() - handOvers;
|
||||
outcome.superseded = StorageTexture::HandedOverSuperseded() - superseded;
|
||||
recorder.Sync();
|
||||
return outcome;
|
||||
};
|
||||
for (const auto order : {Order::Superseded, Order::ReadFirst, Order::Rewritten, Order::CpuStore}) {
|
||||
const std::string name = what + (order == Order::Superseded ? ", A never read again" : order == Order::ReadFirst ? ", A read before B wrote" : order == Order::Rewritten ? ", A written again after the hand-over" : ", a CPU store before B wrote");
|
||||
const auto off = run(false, order);
|
||||
const auto on = run(true, order);
|
||||
const auto& lastA = order == Order::Rewritten ? blue : red;
|
||||
if (order != Order::CpuStore) Require(filled(std::span(off.bytes).first(2 * 65536), green), name + ": B's results did not reach guest memory with the hand-over off");
|
||||
Require(filled(std::span(off.bytes).subspan(2 * 65536), lastA), name + ": A's results did not reach guest memory with the hand-over off");
|
||||
Require(on.imageB == off.imageB, name + ": B held other texels with the hand-over on than off");
|
||||
Require(on.bytes == off.bytes, name + ": the hand-over left other guest bytes than the old order");
|
||||
Require(off.handOvers == 0 && off.superseded == 0, name + ": the hand-over ran while off");
|
||||
if (!watched) {
|
||||
Require(on.handOvers == 0, name + ": results were handed over in memory the write tracker does not watch");
|
||||
continue;
|
||||
}
|
||||
if (order == Order::Superseded) Require(on.superseded == 2, name + ": B's results did not drop A's two units under B (dropped " + std::to_string(on.superseded) + ")");
|
||||
if (order == Order::ReadFirst) Require(on.superseded <= 1, name + ": a unit stored by the read was dropped again");
|
||||
}
|
||||
std::cout << what << ": ok\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
void denormalClearTests(const Device& device, Recorder& recorder) {
|
||||
const auto& base = device.GetContext();
|
||||
if (base.hostImportAlignment == 0) {
|
||||
@@ -9641,6 +9944,11 @@ void Run(Context context) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--partial-watch-restoration-only") {
|
||||
if (!partialWatchRestorationTests()) return 77;
|
||||
std::cout << "partial private write-watch restoration tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--device-selection-only") {
|
||||
deviceSelectionTests();
|
||||
std::cout << "recorder device selection tests passed\n";
|
||||
@@ -9791,6 +10099,14 @@ int main(int argc, char** argv) {
|
||||
keptUnitSettleTests(device, recorder);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--clear-hand-over-only") {
|
||||
clearHandOverTests(device, recorder, false);
|
||||
if (!clearHandOverTests(device, recorder, true)) {
|
||||
std::cout << "skipped, the device imports no watched host memory for the clear-key hand-over\n";
|
||||
return 77;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--integer-clear-only") {
|
||||
integerClearTests(device);
|
||||
return 0;
|
||||
@@ -10026,6 +10342,8 @@ int main(int argc, char** argv) {
|
||||
movedMetadataTests(device, recorder);
|
||||
viewPastLastMipTests(device, recorder);
|
||||
keysFillTests(device, recorder);
|
||||
clearHandOverTests(device, recorder, false);
|
||||
clearHandOverTests(device, recorder, true);
|
||||
integerClearTests(device);
|
||||
denormalClearTests(device, recorder);
|
||||
unitShadowTests(device, recorder);
|
||||
|
||||
@@ -169,6 +169,7 @@ void requireSameResult(const RecompileResult& left, const RecompileResult& right
|
||||
require(left.shaderDataUserWords == right.shaderDataUserWords, prefix + "shader-data user words differ");
|
||||
require(left.imageTableRanges == right.imageTableRanges && left.imageTableShader == right.imageTableShader && left.imageTableFaults == right.imageTableFaults && left.imageTablePoison.size() == right.imageTablePoison.size(), prefix + "image table invocation data differs");
|
||||
require(left.imageTableReadRanges == right.imageTableReadRanges, prefix + "image table read ranges differ");
|
||||
require(left.userDataRegisterReads == right.userDataRegisterReads, prefix + "user data register read mask differs");
|
||||
for (std::size_t i = 0; i < left.imageTablePoison.size(); ++i) {
|
||||
const auto& a = left.imageTablePoison[i];
|
||||
const auto& b = right.imageTablePoison[i];
|
||||
@@ -269,6 +270,7 @@ RecompileResult sampleResult() {
|
||||
result.imageTableReadRanges = {{{0x1000u, 32u}, {0x1040u, 32u}}, {}, {{0x5000000000ull, 16u}}};
|
||||
result.imageTableShader = 0x80003aa700ull;
|
||||
result.imageTableFaults = 3u;
|
||||
result.userDataRegisterReads = (std::uint64_t{1} << 63u) | (std::uint64_t{1} << 32u) | 0x25u;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -321,6 +323,7 @@ CompiledVariant sampleVariant() {
|
||||
image.flatLineCompatible = false;
|
||||
image.flatVolumeLineCompatible = false;
|
||||
image.foldTexelOffsetsOnly = true;
|
||||
image.remappedDepth1D = true;
|
||||
image.byElements = 4;
|
||||
image.byComponents = 1;
|
||||
image.emulatedCompare = 0x1a07u;
|
||||
@@ -633,6 +636,43 @@ void verifyPrebuiltAcrossProcesses(const char* self, const std::filesystem::path
|
||||
require(std::system(command.c_str()) == 0, "the prebuilt loading process failed");
|
||||
}
|
||||
|
||||
void verifyPrebuiltDiscovery(std::string_view layout, bool seed) {
|
||||
using namespace ShaderRecompiler::ShaderDiskCache;
|
||||
require(layout == "pack" || layout == "loose" || layout == "versioned" || layout == "file" || layout == "empty" || layout == "missing", "unknown prebuilt discovery layout");
|
||||
Shutdown();
|
||||
SampleRequest sample = prebuiltSample();
|
||||
const auto key = sample.Key();
|
||||
const auto variant = sampleVariant();
|
||||
if (seed) {
|
||||
const auto directory = ShaderRecompiler::ShaderCacheDirectory() / "prebuilt";
|
||||
const auto entry = EncodeEntry(key, variant);
|
||||
if (layout == "pack") {
|
||||
auto otherKey = key;
|
||||
otherKey.back() ^= std::byte{1};
|
||||
require(WriteFileAtomically(directory / "a.pack", packBytes({{key, entry}})), "cannot seed the matching prebuilt pack");
|
||||
require(WriteFileAtomically(directory / "z.pack", packBytes({{otherKey, EncodeEntry(otherKey, variant)}})), "cannot seed the other prebuilt pack");
|
||||
} else if (layout == "loose") {
|
||||
require(WriteFileAtomically(directory / EntryName(key), entry), "cannot seed the loose prebuilt entry");
|
||||
} else if (layout == "versioned") {
|
||||
require(WriteFileAtomically(directory / EntryDirectory().filename() / EntryName(key), entry), "cannot seed the versioned prebuilt entry");
|
||||
} else if (layout == "file") {
|
||||
require(WriteFileAtomically(directory, packBytes({{key, entry}})), "cannot seed the direct prebuilt pack");
|
||||
} else if (layout == "empty") {
|
||||
require(WriteFileAtomically(directory / "ignored.txt", entry), "cannot seed the ignored prebuilt file");
|
||||
}
|
||||
std::cout << "prebuilt discovery seeded " << layout << '\n';
|
||||
return;
|
||||
}
|
||||
CompiledVariant loaded;
|
||||
const bool expected = layout != "empty" && layout != "missing";
|
||||
require(Load(key, loaded) == expected, "prebuilt discovery returned the wrong result for " + std::string(layout));
|
||||
if (expected) requireSameVariant(variant, loaded, "prebuilt discovery");
|
||||
const auto totals = Totals();
|
||||
require(totals.prebuiltHits == (expected ? 1u : 0u) && totals.hits == 0 && totals.writes == 0 && Writers() == 0,
|
||||
"prebuilt discovery did not remain a read-only prebuilt lookup");
|
||||
std::cout << "prebuilt discovery verified " << layout << '\n';
|
||||
}
|
||||
|
||||
void verifyArtifactStorageIsolation() {
|
||||
SampleRequest sample;
|
||||
const auto key = sample.Key();
|
||||
@@ -704,6 +744,7 @@ void verifyKeySensitivity() {
|
||||
changes("the narrow subgroup clock", [](SampleRequest& sample) { sample.request.target.narrowSubgroupClock = true; });
|
||||
changes("f64 denormal preservation", [](SampleRequest& sample) { sample.request.target.float64DenormPreserve = true; });
|
||||
changes("f16 denormal preservation", [](SampleRequest& sample) { sample.request.target.float16DenormPreserve = true; });
|
||||
changes("f32 denormal preservation", [](SampleRequest& sample) { sample.request.target.float32DenormPreserve = true; });
|
||||
changes("spanned waves", [](SampleRequest& sample) { sample.request.target.spannedWaves = true; });
|
||||
changes("robust buffer access 2", [](SampleRequest& sample) { sample.request.target.robustBufferAccess2 = true; });
|
||||
changes("wave LDS memory barriers", [](SampleRequest& sample) { sample.request.target.waveLdsMemoryBarriers = true; });
|
||||
@@ -2434,6 +2475,10 @@ int main(int argc, char** argv) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (argc == 3 && (std::string_view(argv[1]) == "--seed-prebuilt-discovery" || std::string_view(argv[1]) == "--verify-prebuilt-discovery")) {
|
||||
verifyPrebuiltDiscovery(argv[2], std::string_view(argv[1]) == "--seed-prebuilt-discovery");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--large-file") return runLargeFileWrite();
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--load") return runLoadingProcess();
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--load-prebuilt") return runPrebuiltLoadingProcess();
|
||||
|
||||
@@ -4848,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) == "NVNQQScAAAA=", "new requests did not use serialization version 39");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQSkAAAA=", "new requests did not use serialization version 41");
|
||||
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
|
||||
auto withFallbacks = minimal;
|
||||
withFallbacks.context.linkedCallFallbacks = fallbacks;
|
||||
@@ -4966,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=", "NVNQQSgAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSoAAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
static constexpr std::string_view version18Request{
|
||||
@@ -5059,6 +5059,7 @@ void verifyVertexInfoVersion19() {
|
||||
auto version20Vertex = flagged;
|
||||
version20Vertex.pop_back();
|
||||
version20Vertex.pop_back();
|
||||
version20Vertex.pop_back();
|
||||
version20Vertex[4] = 20u;
|
||||
const auto replay20Vertex = serializer.Deserialize(encodeBase64(version20Vertex));
|
||||
require(replay20Vertex.request.context.vertex.has_value() && replay20Vertex.request.context.vertex->paClVsOutCntl == 0x89abcdefu, "a main version20 request lost PA_CL_VS_OUT_CNTL");
|
||||
@@ -5069,6 +5070,7 @@ void verifyVertexInfoVersion19() {
|
||||
auto legacy = plain;
|
||||
legacy.pop_back();
|
||||
legacy.pop_back();
|
||||
legacy.pop_back();
|
||||
legacy.erase(legacy.begin() + offset, legacy.begin() + offset + 4u);
|
||||
for (const auto version : {16u, 17u, 18u, 19u}) {
|
||||
legacy[4] = static_cast<std::uint8_t>(version);
|
||||
@@ -5120,6 +5122,7 @@ void verifyVertexInfoVersion19() {
|
||||
version30.pop_back();
|
||||
version30.pop_back();
|
||||
version30.pop_back();
|
||||
version30.pop_back();
|
||||
version30.erase(version30.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u), version30.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
|
||||
version30[4] = 30u;
|
||||
const auto replay30 = serializer.Deserialize(encodeBase64(version30));
|
||||
@@ -5127,6 +5130,7 @@ void verifyVertexInfoVersion19() {
|
||||
auto spanned = sampledPixel;
|
||||
spanned.target.spannedWaves = true;
|
||||
auto version32 = decodeBase64(serializer.Serialize(spanned));
|
||||
version32.pop_back();
|
||||
require(version32.size() > 2u && version32.back() == 0u && version32[version32.size() - 2u] == 0u && version32[version32.size() - 3u] == 1u, "the spanned-waves flag is not the byte before the depth remap flag");
|
||||
version32.pop_back();
|
||||
version32.pop_back();
|
||||
@@ -5138,8 +5142,10 @@ void verifyVertexInfoVersion19() {
|
||||
version29.pop_back();
|
||||
version29.pop_back();
|
||||
version29.pop_back();
|
||||
version29.pop_back();
|
||||
version29.erase(version29.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes), version29.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
|
||||
auto version36 = sampledBytes;
|
||||
version36.pop_back();
|
||||
version36.erase(version36.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes - 1u));
|
||||
version36[4] = 36u;
|
||||
const auto replay36 = serializer.Deserialize(encodeBase64(version36));
|
||||
@@ -5151,6 +5157,7 @@ void verifyVertexInfoVersion19() {
|
||||
version28.pop_back();
|
||||
version28.pop_back();
|
||||
version28.pop_back();
|
||||
version28.pop_back();
|
||||
version28.erase(version28.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u), version28.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
|
||||
version28[4] = 28u;
|
||||
const auto replay28 = serializer.Deserialize(encodeBase64(version28));
|
||||
@@ -5159,6 +5166,7 @@ void verifyVertexInfoVersion19() {
|
||||
version27.pop_back();
|
||||
version27.pop_back();
|
||||
version27.pop_back();
|
||||
version27.pop_back();
|
||||
version27.erase(version27.begin() + static_cast<std::ptrdiff_t>(maskOffset), version27.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
|
||||
version27[4] = 27u;
|
||||
const auto replay27 = serializer.Deserialize(encodeBase64(version27));
|
||||
@@ -5167,6 +5175,7 @@ void verifyVertexInfoVersion19() {
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.erase(legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset), legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset + 2u + sampleMaskBytes + 4u + iterationBytes));
|
||||
legacyPixel[4] = 26u;
|
||||
const auto replay26 = serializer.Deserialize(encodeBase64(legacyPixel));
|
||||
@@ -5216,9 +5225,10 @@ void verifyDepthRemapRequests() {
|
||||
require(serializer.Serialize(replay.request) == remapped, "a version 35 depth remap request changed on re-serialization");
|
||||
const auto plainBytes = decodeBase64(plain);
|
||||
const auto remappedBytes = decodeBase64(remapped);
|
||||
require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 6, remappedBytes.begin()), "the depth remap flag and mode are not the last 6 bytes before the f32 flush flag of a vertex request");
|
||||
require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 7, remappedBytes.begin()), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags of a vertex request");
|
||||
for (const std::uint8_t legacyVersion : {32u, 34u}) {
|
||||
auto legacyBytes = remappedBytes;
|
||||
legacyBytes.pop_back();
|
||||
legacyBytes[4] = legacyVersion;
|
||||
legacyBytes.resize(legacyBytes.size() - 6u);
|
||||
const auto legacy = serializer.Deserialize(encodeBase64(legacyBytes));
|
||||
@@ -5226,6 +5236,7 @@ void verifyDepthRemapRequests() {
|
||||
require(serializer.Serialize(legacy.request) == plain, ("a version " + std::to_string(legacyVersion) + " vertex request misread the fields before the depth remap").c_str());
|
||||
}
|
||||
auto version35 = remappedBytes;
|
||||
version35.pop_back();
|
||||
version35[4] = 35u;
|
||||
version35.pop_back();
|
||||
const auto replay35 = serializer.Deserialize(encodeBase64(version35));
|
||||
@@ -5272,6 +5283,7 @@ void verifyMergedHullConfiguration() {
|
||||
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);
|
||||
legacy.pop_back();
|
||||
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}) {
|
||||
@@ -6883,6 +6895,10 @@ void verifySrtReadPatching() {
|
||||
const auto* replacement = repeatedUse.Argument(0);
|
||||
require(replacement->Opcode() == IrOpcode::ReadConst, "SRT read patching: the uses do not reference a flat read");
|
||||
require(repeatedUse.Argument(1) == replacement && secondUser.Argument(0) == replacement, "SRT read patching: not every operand uses the same replacement");
|
||||
const std::vector<const IrValue*> order(block.Instructions().begin(), block.Instructions().end());
|
||||
const auto readAt = std::find(order.begin(), order.end(), &read);
|
||||
require(readAt != order.end() && readAt - order.begin() >= 2 && *(readAt - 1) == replacement && *(readAt - 2) == replacement->Argument(0) && (*(readAt - 2))->Opcode() == IrOpcode::GetSrtResource, "SRT read patching: the flat read is not right before the original read");
|
||||
require(order.back()->Opcode() == IrOpcode::ReferenceU32 && order.back()->Argument(0) == &read, "SRT read patching: the planning reference is not at the end of the block");
|
||||
const auto& remainingUses = read.OperandUses();
|
||||
require(remainingUses.size() == 1u && remainingUses.front().user->Opcode() == IrOpcode::ReferenceU32, "SRT read patching: the original read retains non-planning uses");
|
||||
}
|
||||
@@ -6981,6 +6997,28 @@ void verifyGuardedNullPointers() {
|
||||
const auto samePipeline = [](const RecompileResult& left, const RecompileResult& right) {
|
||||
return left.variantId == right.variantId && left.specializationId == right.specializationId && left.PipelineVariantId() == right.PipelineVariantId() && left.spirv == right.spirv;
|
||||
};
|
||||
const auto guardSites = [](const RecompileResult& result) {
|
||||
const auto words = result.spirv.Words();
|
||||
std::map<std::uint32_t, std::string> names;
|
||||
std::map<std::uint32_t, std::uint32_t> constants;
|
||||
std::vector<std::uint32_t> guardWords;
|
||||
std::string function;
|
||||
std::size_t calls = 0;
|
||||
std::size_t stores = 0;
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> 16u;
|
||||
const auto opcode = words[cursor] & 0xffffu;
|
||||
require(count != 0 && count <= words.size() - cursor, "guarded pointer: truncated SPIR-V instruction");
|
||||
if (opcode == spv::OpName) names[words[cursor + 1]] = reinterpret_cast<const char*>(&words[cursor + 2]);
|
||||
if (opcode == spv::OpConstant && count == 4u) constants[words[cursor + 2]] = words[cursor + 3];
|
||||
if (opcode == spv::OpFunction) function = names[words[cursor + 2]];
|
||||
if (opcode == spv::OpAccessChain && count == 6u && names[words[cursor + 3]] == "fault_buffer" && constants.contains(words[cursor + 5]) && constants[words[cursor + 5]] == BdaAbi::SrtGuardWord) guardWords.push_back(words[cursor + 2]);
|
||||
if (function == "main" && opcode == spv::OpFunctionCall && names[words[cursor + 3]] == "record_bda_fault") ++calls;
|
||||
if (function == "main" && opcode == spv::OpAtomicStore && std::find(guardWords.begin(), guardWords.end(), words[cursor + 1]) != guardWords.end()) ++stores;
|
||||
cursor += count;
|
||||
}
|
||||
return std::pair{calls, stores};
|
||||
};
|
||||
|
||||
NestedRequest branch(branchCode, root.data());
|
||||
point(payloadAddress);
|
||||
@@ -6992,6 +7030,8 @@ void verifyGuardedNullPointers() {
|
||||
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");
|
||||
const auto [branchCalls, branchStores] = guardSites(*validResult);
|
||||
require(branchCalls == 0u && branchStores == 1u, ("guarded pointer: a guarded read calls record_bda_fault " + std::to_string(branchCalls) + " times in main and stores its record to the fault buffer's guard word " + std::to_string(branchStores) + " times, expected one store and no call").c_str());
|
||||
|
||||
for (const auto pointer : {std::uint64_t{0}, unmapped}) {
|
||||
for (const auto condition : {1u, 0u}) {
|
||||
@@ -7050,6 +7090,8 @@ void verifyGuardedNullPointers() {
|
||||
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");
|
||||
const auto [chainCalls, chainStores] = guardSites(*mappedChainResult);
|
||||
require(chainCalls == 0u && chainStores == mappedChain->plan->guardedSrtSlots.size(), ("guarded pointer: a chain's guarded reads call record_bda_fault " + std::to_string(chainCalls) + " times in main and store to the guard word " + std::to_string(chainStores) + " times, expected one store per guard and no call").c_str());
|
||||
require(samePipeline(*chainResult, *mappedChainResult) && samePipeline(*nullChainResult, *mappedChainResult), "guarded pointer: a chain's poison changed the variant, the specialization or the module");
|
||||
|
||||
const auto poisonedWords = [](const ResourceCapture& capture, std::uint32_t pc, std::uint64_t base) {
|
||||
@@ -7620,6 +7662,13 @@ int main(int argc, char** argv) {
|
||||
std::vector<std::uint64_t> float32FlushKey;
|
||||
RecompileCacheKey::Build(float32Flush, float32FlushKey);
|
||||
require(plainKey != float32FlushKey, "f32 hardware flush did not change the cache key");
|
||||
auto float32Preserve = uncached;
|
||||
float32Preserve.target.float32DenormPreserve = true;
|
||||
require(RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(float32Preserve)).request.target.float32DenormPreserve, "f32 denormal preservation was lost in serialization");
|
||||
require(!RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(uncached)).request.target.float32DenormPreserve, "f32 denormal preservation appeared from nowhere in serialization");
|
||||
std::vector<std::uint64_t> float32PreserveKey;
|
||||
RecompileCacheKey::Build(float32Preserve, float32PreserveKey);
|
||||
require(plainKey != float32PreserveKey && float32FlushKey != float32PreserveKey, "f32 denormal preservation did not change the cache key");
|
||||
auto changedLayout = request;
|
||||
changedLayout.layout.pushConstantSizeBytes = 64;
|
||||
require(!Recompile(changedLayout).cacheHit, "binding layout change reused an incompatible variant");
|
||||
|
||||
@@ -102,11 +102,12 @@ struct Outcome {
|
||||
};
|
||||
|
||||
const ShaderTessellationInputInfo Tessellation{.inputControlPoints = InputControlPoints, .outputControlPoints = InputControlPoints, .patchesPerGroup = 1u};
|
||||
const ShaderTessellationInputInfo GroupedTessellation{.inputControlPoints = InputControlPoints, .outputControlPoints = InputControlPoints, .patchesPerGroup = 1u, .primitiveGroupSize = 1u};
|
||||
|
||||
Outcome Lower(IrProgram& program) {
|
||||
Outcome Lower(IrProgram& program, const ShaderTessellationInputInfo& tessellation = Tessellation) {
|
||||
Outcome outcome;
|
||||
try {
|
||||
TessellationLowering{}.Lower(program, Tessellation);
|
||||
TessellationLowering{}.Lower(program, tessellation);
|
||||
} catch (const std::exception& error) {
|
||||
outcome.reason = error.what();
|
||||
return outcome;
|
||||
@@ -193,5 +194,17 @@ int main() {
|
||||
return RefusedBecause(hull.program, "not a whole number of control points");
|
||||
});
|
||||
|
||||
passed &= run("a hull read at a computed control-point index stays in its patch when the relative patch is also data", [] {
|
||||
Hull hull;
|
||||
auto& address = hull.inPatch(hull.computed(2u, LocalStride));
|
||||
hull.read(address);
|
||||
static_cast<void>(hull.builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&hull.relativePatch()}));
|
||||
const Outcome outcome = Lower(hull.program, GroupedTessellation);
|
||||
if (!outcome.lowered) std::fprintf(stderr, "refused: %s\n", outcome.reason.c_str());
|
||||
if (!outcome.lowered || outcome.emitted->Opcode() != IrOpcode::ISub32 || outcome.emitted->Argument(0)->Resolve() != address.Resolve()) return false;
|
||||
const IrValue* patch = outcome.emitted->Argument(1)->Resolve();
|
||||
return patch->Opcode() == IrOpcode::IMul32 && patch->Argument(1)->Resolve()->ImmediateU32() == PatchStride;
|
||||
});
|
||||
|
||||
return passed ? 0 : 1;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
|
||||
#include "Translation/InstructionTranslator.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@@ -21,6 +22,7 @@ using namespace ShaderRecompiler;
|
||||
constexpr std::uint32_t MoveM0Two = 0xbefc0382u;
|
||||
constexpr std::uint32_t MoveM0GroupX = 0xbefc0304u;
|
||||
constexpr std::uint32_t MoveV4One = 0x7e0802f2u;
|
||||
constexpr std::uint32_t MoveV4Two = 0x7e0802f4u;
|
||||
constexpr std::uint32_t MoveV5Two = 0x7e0a02f4u;
|
||||
constexpr std::uint32_t MoveV6Four = 0x7e0c02f6u;
|
||||
constexpr std::uint32_t MovrelsV1V4 = 0x7e028704u;
|
||||
@@ -117,6 +119,24 @@ Shape Scan(std::span<const std::uint32_t> words) {
|
||||
return shape;
|
||||
}
|
||||
|
||||
std::size_t WindowStores(std::span<const std::uint32_t> words) {
|
||||
std::vector<std::uint32_t> functionPointers;
|
||||
std::vector<std::uint32_t> pointers;
|
||||
std::size_t stores = 0;
|
||||
const auto contains = [](const std::vector<std::uint32_t>& ids, std::uint32_t id) { return std::find(ids.begin(), ids.end(), id) != ids.end(); };
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> spv::WordCountShift;
|
||||
const auto op = static_cast<spv::Op>(words[cursor] & spv::OpCodeMask);
|
||||
Require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction");
|
||||
if (op == spv::OpTypePointer && words[cursor + 2] == spv::StorageClassFunction) functionPointers.push_back(words[cursor + 1]);
|
||||
if (op == spv::OpVariable && words[cursor + 3] == spv::StorageClassFunction && contains(functionPointers, words[cursor + 1])) pointers.push_back(words[cursor + 2]);
|
||||
if (op == spv::OpAccessChain && contains(pointers, words[cursor + 3])) pointers.push_back(words[cursor + 2]);
|
||||
if (op == spv::OpStore && contains(pointers, words[cursor + 1])) ++stores;
|
||||
cursor += count;
|
||||
}
|
||||
return stores;
|
||||
}
|
||||
|
||||
IrProgram TranslateStage(ShaderStageKind stage, std::uint32_t waveSize, std::uint32_t base = 0u, std::uint32_t fetchStart = 0u, std::uint32_t fetchCount = 0u) {
|
||||
ShaderComputeInputInfo compute{};
|
||||
compute.threadIdsNum = 3;
|
||||
@@ -293,6 +313,15 @@ int main(int argc, char** argv) {
|
||||
return shape.functionArrays == 1;
|
||||
});
|
||||
|
||||
passed &= run("a window register written twice before a relative read keeps only its last window store", [] {
|
||||
const std::array<std::uint32_t, 9> overwritten{MoveM0GroupX, MoveV4One, MoveV4Two, MoveV5Two, MoveV6Four, MovrelsV1V4, 0xe0700000u, 0x80000100u, EndProgram};
|
||||
const std::array<std::uint32_t, 8> once{MoveM0GroupX, MoveV4Two, MoveV5Two, MoveV6Four, MovrelsV1V4, 0xe0700000u, 0x80000100u, EndProgram};
|
||||
const auto twice = WindowStores(RecompileProgram(overwritten));
|
||||
const auto single = WindowStores(RecompileProgram(once));
|
||||
if (twice != single || single < 3u) std::fprintf(stderr, " window stores: written twice %zu, written once %zu\n", twice, single);
|
||||
return twice == single && single >= 3u;
|
||||
});
|
||||
|
||||
if (passed) std::puts("Vector window shape and SSA control tests passed");
|
||||
return passed ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "Translation/ShaderInputInfoBuilder.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace ShaderRecompiler;
|
||||
|
||||
namespace {
|
||||
|
||||
struct BufferInput {
|
||||
std::uint64_t address;
|
||||
std::uint32_t stride;
|
||||
std::uint32_t records;
|
||||
std::uint32_t fetchIndex;
|
||||
};
|
||||
|
||||
ShaderVertexStageInfo vertexInputs(const std::vector<BufferInput>& buffers, bool embedded = false) {
|
||||
ShaderVertexStageInfo vertex{};
|
||||
for (const auto& buffer : buffers) {
|
||||
const auto index = vertex.resourcesNum++;
|
||||
vertex.resources[index].fields = {static_cast<std::uint32_t>(buffer.address), static_cast<std::uint32_t>(buffer.address >> 32u) | buffer.stride << 16u, buffer.records, 0u};
|
||||
vertex.resourcesDst[index] = {static_cast<std::int32_t>(index * 4u), 4, static_cast<std::int32_t>(index), buffer.fetchIndex};
|
||||
}
|
||||
vertex.fetchEmbedded = embedded;
|
||||
return vertex;
|
||||
}
|
||||
|
||||
template<typename TRun>
|
||||
std::string errorOf(TRun&& run) {
|
||||
try {
|
||||
run();
|
||||
} catch (const std::exception& error) {
|
||||
return error.what();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::vector<std::optional<ShaderVertexStageInfo>> vertexCases{std::nullopt};
|
||||
auto overfull = vertexInputs({{0x10000u, 16u, 100u, 0u}});
|
||||
overfull.resourcesNum = ShaderVertexStageInfo::MaxResources + 1u;
|
||||
vertexCases.push_back(overfull);
|
||||
vertexCases.push_back(vertexInputs({{0x10000u, 16u, 100u, 0u}, {0x10004u, 16u, 99u, 0u}}));
|
||||
vertexCases.push_back(vertexInputs({{0x10000u, 16u, 100u, 0u}, {0x10004u, 16u, 99u, 0u}}, true));
|
||||
vertexCases.push_back(vertexInputs({{0x10000u, 16u, 100u, 0u}, {0x10004u, 16u, 100u, 0u}, {0x20000u, 8u, 50u, 0u}, {0x10008u, 16u, 100u, 1u}}));
|
||||
std::vector<BufferInput> attributes;
|
||||
std::vector<BufferInput> separate;
|
||||
for (std::uint32_t index = 0; index < ShaderVertexStageInfo::MaxResources; ++index) {
|
||||
attributes.push_back({0x30000u + index * 4u, 128u, 10u, 0u});
|
||||
separate.push_back({0x40000u + index * 0x1000u, 4u + index * 4u, 10u, 0u});
|
||||
}
|
||||
vertexCases.push_back(vertexInputs(attributes));
|
||||
vertexCases.push_back(vertexInputs(separate));
|
||||
|
||||
const MeshConfiguration mesh{3u, 64u, 64u, 64u, 64u, 64u, 0u, 0u};
|
||||
auto noThreads = mesh;
|
||||
noThreads.threadsPerGroup = 0u;
|
||||
auto badProvoking = mesh;
|
||||
badProvoking.provokingVertex = 3u;
|
||||
std::vector<std::optional<MeshConfiguration>> meshCases{std::nullopt, mesh, noThreads, badProvoking};
|
||||
struct MeshExpectation {
|
||||
std::uint32_t outputPrimitiveSize;
|
||||
std::uint32_t provokingVertex;
|
||||
bool accepted;
|
||||
};
|
||||
constexpr std::array meshExpectations{MeshExpectation{0u, 0u, false}, MeshExpectation{1u, 0u, false}, MeshExpectation{2u, 0u, true}, MeshExpectation{2u, 1u, true}, MeshExpectation{2u, 2u, false}, MeshExpectation{3u, 2u, true}, MeshExpectation{3u, 3u, false}, MeshExpectation{4u, 0u, false}, MeshExpectation{4u, 3u, false}};
|
||||
const auto meshWith = [&](const MeshExpectation& expectation) {
|
||||
auto varied = mesh;
|
||||
varied.outputPrimitiveSize = expectation.outputPrimitiveSize;
|
||||
varied.provokingVertex = expectation.provokingVertex;
|
||||
return varied;
|
||||
};
|
||||
for (const auto& expectation : meshExpectations) meshCases.push_back(meshWith(expectation));
|
||||
|
||||
const TessellationConfiguration tessellation{3u, 3u, 1u, 2u, 2u, 16u};
|
||||
std::vector<std::optional<TessellationConfiguration>> tessellationCases{std::nullopt, tessellation};
|
||||
const auto vary = [&](auto change) {
|
||||
auto varied = tessellation;
|
||||
change(varied);
|
||||
tessellationCases.push_back(varied);
|
||||
};
|
||||
vary([](TessellationConfiguration& config) { config.inputControlPoints = 0u; });
|
||||
vary([](TessellationConfiguration& config) { config.outputControlPoints = 33u; });
|
||||
vary([](TessellationConfiguration& config) { config.domain = 3u; });
|
||||
vary([](TessellationConfiguration& config) { config.partitioning = 1u; });
|
||||
vary([](TessellationConfiguration& config) { config.outputTopology = 1u; });
|
||||
vary([](TessellationConfiguration& config) { config.patchesPerGroup = 0u; });
|
||||
vary([](TessellationConfiguration& config) { config.patchesPerGroup = 257u; });
|
||||
|
||||
constexpr std::array stages{ShaderStageKind::Vertex, ShaderStageKind::Local, ShaderStageKind::TessellationControl, ShaderStageKind::TessellationEvaluation, ShaderStageKind::Mesh};
|
||||
std::size_t mismatches = 0;
|
||||
std::size_t accepted = 0;
|
||||
std::set<std::string> errors;
|
||||
for (const auto stage : stages) {
|
||||
for (std::size_t vertexCase = 0; vertexCase < vertexCases.size(); ++vertexCase) {
|
||||
for (std::size_t meshCase = 0; meshCase < meshCases.size(); ++meshCase) {
|
||||
for (std::size_t tessellationCase = 0; tessellationCase < tessellationCases.size(); ++tessellationCase) {
|
||||
GuestContext context{};
|
||||
context.waveSize = 64u;
|
||||
context.vertex = vertexCases[vertexCase];
|
||||
const auto* meshConfig = meshCases[meshCase] ? &*meshCases[meshCase] : nullptr;
|
||||
const auto* tessellationConfig = tessellationCases[tessellationCase] ? &*tessellationCases[tessellationCase] : nullptr;
|
||||
const auto built = errorOf([&] { static_cast<void>(BuildShaderStageInputInfo(stage, context, 64u, meshConfig, tessellationConfig)); });
|
||||
const auto validated = errorOf([&] { ValidateVertexStageInputs(stage, context, meshConfig, tessellationConfig); });
|
||||
if (built != validated) {
|
||||
++mismatches;
|
||||
std::fprintf(stderr, "stage %d, vertex case %zu, mesh case %zu, tessellation case %zu: the build says \"%s\", the validation \"%s\"\n", static_cast<int>(stage), vertexCase, meshCase, tessellationCase, built.c_str(), validated.c_str());
|
||||
}
|
||||
if (built.empty()) ++accepted;
|
||||
else errors.insert(built);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::array expected{"GuestContext.vertex is not set", "invalid vertex resource count", "disagree on record count", "tessellation configuration is missing", "invalid tessellation control-point counts", "unsupported tessellation configuration", "invalid tessellation patches per group", "has no mesh configuration", "invalid mesh configuration"};
|
||||
for (const auto* message : expected) {
|
||||
const bool seen = std::any_of(errors.begin(), errors.end(), [&](const std::string& error) { return error.find(message) != std::string::npos; });
|
||||
if (!seen) {
|
||||
++mismatches;
|
||||
std::fprintf(stderr, "no case raised \"%s\"\n", message);
|
||||
}
|
||||
}
|
||||
GuestContext meshContext{};
|
||||
meshContext.waveSize = 64u;
|
||||
meshContext.vertex = vertexInputs({{0x10000u, 16u, 100u, 0u}});
|
||||
for (const auto& expectation : meshExpectations) {
|
||||
const auto varied = meshWith(expectation);
|
||||
const bool built = errorOf([&] { static_cast<void>(BuildShaderStageInputInfo(ShaderStageKind::Mesh, meshContext, 64u, &varied, nullptr)); }).empty();
|
||||
const bool validated = errorOf([&] { ValidateVertexStageInputs(ShaderStageKind::Mesh, meshContext, &varied, nullptr); }).empty();
|
||||
if (built != expectation.accepted || validated != expectation.accepted) {
|
||||
++mismatches;
|
||||
std::fprintf(stderr, "mesh output primitive size %u, provoking vertex %u: expected %s, the build %s, the validation %s\n", expectation.outputPrimitiveSize, expectation.provokingVertex, expectation.accepted ? "accepted" : "rejected", built ? "accepts" : "rejects", validated ? "accepts" : "rejects");
|
||||
}
|
||||
}
|
||||
if (accepted == 0) {
|
||||
++mismatches;
|
||||
std::fprintf(stderr, "no case was accepted\n");
|
||||
}
|
||||
if (mismatches != 0) return 1;
|
||||
std::printf("vertex input validation matches the input info build in %zu cases (%zu accepted)\n", stages.size() * vertexCases.size() * meshCases.size() * tessellationCases.size(), accepted);
|
||||
return 0;
|
||||
}
|
||||
@@ -41,7 +41,8 @@ int main() {
|
||||
require(coverage.Covers(base + block, block), "clipping an import excluded the rest of the arena");
|
||||
coverage.Initialize(base, 4 * block + 1);
|
||||
coverage.Exclude(base, 4 * block + 1);
|
||||
require(!coverage.Restore(base + 1, block - 1) && !coverage.Covers(base, block), "a partial replacement restored an excluded block");
|
||||
require(coverage.Restore(base + 1, block - 1) && coverage.Covers(base + 4096, block - 4096), "a partial replacement did not restore its complete pages");
|
||||
require(!coverage.Covers(base, 4096) && !coverage.Covers(base, block), "a partial replacement restored an incompletely remapped page");
|
||||
require(coverage.Restore(base + block, block) && coverage.Covers(base + block, block), "fresh private backing stayed excluded");
|
||||
require(!coverage.Restore(base + block, block), "restoring an already watched block reports a change");
|
||||
require(!coverage.Covers(base, block) && !coverage.Covers(base + 2 * block, block), "restoring one block restored its neighbours");
|
||||
@@ -60,6 +61,44 @@ int main() {
|
||||
coverage.Exclude(base, block, 7);
|
||||
require(!coverage.Restore(base, block, 6) && !coverage.Covers(base, block), "a delayed mapping notification erased a repeated import");
|
||||
require(coverage.Restore(base, block, 8), "a fresh replacement did not restore the latest import exclusion");
|
||||
coverage.Initialize(base, 2 * block);
|
||||
coverage.Exclude(base, block, 10);
|
||||
require(coverage.Restore(base + 16384, 16384, 11), "a fresh partial-block private mapping stayed excluded");
|
||||
require(coverage.Covers(base + 16384, 16384), "the restored private pages are not watched");
|
||||
require(!coverage.Covers(base, 16384) && !coverage.Covers(base + 32768, 32768), "restoring private pages watched an imported neighbour");
|
||||
require(!coverage.Covers(base + 16383, 2) && !coverage.Covers(base + 32767, 2), "a boundary query included excluded pages");
|
||||
require(!coverage.Restore(base + 32768, 4096, 10), "a stale partial mapping restored an exclusion");
|
||||
coverage.Exclude(base + 4096, 1, 12);
|
||||
require(!coverage.Covers(base + 16384, 16384), "a repeated import kept partial coverage from an older mapping");
|
||||
require(!coverage.Restore(base + 16384, 16384, 11), "a delayed partial mapping restored a newer import");
|
||||
require(coverage.Restore(base + 16384, 16384, 13), "fresh partial coverage was not restored after re-import");
|
||||
constexpr std::size_t page = 4096;
|
||||
for (std::size_t first = 0; first < block / page; ++first) {
|
||||
for (std::size_t count = 1; count <= block / page - first; ++count) {
|
||||
coverage.Initialize(base, 3 * block);
|
||||
coverage.Exclude(base + block, block, 20);
|
||||
require(coverage.Restore(base + block + first * page, count * page, 21), "a complete-page replacement was not restored");
|
||||
for (std::size_t index = 0; index < 3 * block / page; ++index) {
|
||||
const bool expected = index < block / page || index >= 2 * block / page || (index >= block / page + first && index < block / page + first + count);
|
||||
require(coverage.Covers(base + index * page, page) == expected, "partial restoration changed an unrelated page");
|
||||
if (index != 0) {
|
||||
const bool previous = index - 1 < block / page || index - 1 >= 2 * block / page || (index - 1 >= block / page + first && index - 1 < block / page + first + count);
|
||||
require(coverage.Covers(base + index * page - 1, 2) == (previous && expected), "a page boundary proof ignored an excluded page");
|
||||
}
|
||||
}
|
||||
require(coverage.Covers(base + block, block) == (count == block / page), "partial page coverage proved a whole block");
|
||||
require(!coverage.Restore(base + block + first * page, count * page, 21), "restoring watched pages again reports a change");
|
||||
}
|
||||
}
|
||||
coverage.Initialize(base, 2 * block + 17);
|
||||
coverage.Exclude(base + 2 * block, 17, 30);
|
||||
require(!coverage.Restore(base + 2 * block, 16, 31), "an incomplete terminal page was restored");
|
||||
require(coverage.Restore(base + 2 * block, 17, 31) && coverage.Covers(base + 2 * block, 17), "a complete terminal arena page stayed excluded");
|
||||
coverage.Initialize(base, 2 * block);
|
||||
coverage.Exclude(base, block, 40);
|
||||
coverage.Exclude(base + block, block, 43);
|
||||
require(coverage.Restore(base + block - page, 2 * page, 42), "a newer boundary page was not restored");
|
||||
require(coverage.Covers(base + block - page, page) && !coverage.Covers(base + block, page), "a stale boundary mapping erased a newer neighbouring exclusion");
|
||||
std::cout << "write watch coverage passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -41,6 +41,7 @@ struct Surface {
|
||||
VkFormat format;
|
||||
std::uint32_t slice = 0;
|
||||
std::uint8_t clearStencil = 0;
|
||||
std::uint32_t swizzle = 0;
|
||||
};
|
||||
|
||||
struct DrawOptions {
|
||||
@@ -71,11 +72,11 @@ struct Shaders {
|
||||
std::uint32_t vertexPush = 0;
|
||||
};
|
||||
|
||||
inline Shaders Compile(AgcDriver::VulkanDevice& device) {
|
||||
inline Shaders Compile(AgcDriver::VulkanDevice& device, const void* vertices = Triangles().data()) {
|
||||
const auto target = device.Target();
|
||||
Shaders shaders;
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(Triangles().data(), 16u, static_cast<std::uint32_t>(Triangles().size()));
|
||||
const auto vertexBuffer = BufferDescriptor(vertices, 16u, static_cast<std::uint32_t>(Triangles().size()));
|
||||
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
|
||||
ShaderRecompiler::RecompileRequest vertex{
|
||||
@@ -121,6 +122,7 @@ inline std::uint32_t Draw(AgcDriver::VulkanDevice& device, const Shaders& compil
|
||||
state.hasColorTarget = true;
|
||||
state.depth = AgcDriver::Graphics::DepthTarget{surface.depth, surface.stencil, extent, surface.format, 1.0f, surface.clearStencil, surface.htile, surface.htileStencil};
|
||||
state.depth->firstSlice = surface.slice;
|
||||
state.depth->depthSwizzle = surface.swizzle;
|
||||
state.depthTest = options.depthTest;
|
||||
state.depthWrite = options.depthWrite;
|
||||
state.depthCompare = options.depthCompare;
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
using DepthFastClearHarness::Compile;
|
||||
using DepthFastClearHarness::Covered;
|
||||
using DepthFastClearHarness::Draw;
|
||||
using DepthFastClearHarness::Height;
|
||||
using DepthFastClearHarness::HtileBytes;
|
||||
using DepthFastClearHarness::Require;
|
||||
using DepthFastClearHarness::Surface;
|
||||
using DepthFastClearHarness::Width;
|
||||
|
||||
constexpr std::size_t Block = 65536;
|
||||
alignas(256) std::array<std::uint32_t, HtileBytes / 4u> Htile{};
|
||||
alignas(256) std::array<std::uint32_t, HtileBytes / 4u> ViewedHtile{};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> LoadCode{
|
||||
0x7e02020du, 0x7e04020cu, 0xf0001108u, 0x00010300u, 0xbf8c3f70u, 0x34080082u, 0xe0701000u, 0x80000304u, 0xbf810000u, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
constexpr std::uint32_t LoadThreads = 32;
|
||||
constexpr std::size_t ColorBytes = static_cast<std::size_t>(Width) * Height * 4u;
|
||||
|
||||
void* AllocateWatched(std::size_t bytes) {
|
||||
#ifdef _WIN32
|
||||
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block);
|
||||
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
|
||||
#else
|
||||
void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Require(raw != MAP_FAILED, "cannot map the depth plane block");
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
|
||||
const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
|
||||
if (aligned != begin) munmap(raw, aligned - begin);
|
||||
if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
|
||||
void* block = reinterpret_cast<void*>(aligned);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
|
||||
#endif
|
||||
GuestAllocations::Mutation().Add(block, bytes, true, true, true);
|
||||
return block;
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, LoadThreads> LoadRow(AgcDriver::VulkanDevice& device, std::uint64_t address, std::uint32_t width, std::uint32_t height, std::uint32_t swizzle) {
|
||||
alignas(256) static std::array<std::uint32_t, LoadThreads> output{};
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto out = reinterpret_cast<std::uintptr_t>(output.data());
|
||||
const std::array<std::uint32_t, 4> buffer{static_cast<std::uint32_t>(out), static_cast<std::uint32_t>((out >> 32u) & 0xffffu), static_cast<std::uint32_t>(sizeof(output)), 0x31016facu};
|
||||
const std::array<std::uint32_t, 8> texture{static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (22u << 20u) | (((width - 1u) & 3u) << 30u), ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0xfacu | (swizzle << 20u) | (9u << 28u), 0u, 0u, 0u, 0u};
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin());
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
const std::span<const std::uint32_t> code(LoadCode);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{LoadThreads, 1, 1}, 0, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderRecompiler::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;
|
||||
output.fill(0xdeadbeefu);
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
return output;
|
||||
}
|
||||
|
||||
bool RowHolds(const std::array<std::uint32_t, LoadThreads>& row, float depth) {
|
||||
return std::all_of(row.begin(), row.end(), [&](std::uint32_t bits) { return bits == std::bit_cast<std::uint32_t>(depth); });
|
||||
}
|
||||
|
||||
std::size_t Holding(std::span<const float> plane, float depth) {
|
||||
return static_cast<std::size_t>(std::count(plane.begin(), plane.end(), depth));
|
||||
}
|
||||
|
||||
void DrawBlendKeepingMemory(AgcDriver::VulkanDevice& device, const DepthFastClearHarness::Shaders& compiled, std::uint64_t address) {
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderRecompiler::ShaderStage::Vertex, &compiled.vertex, 0},
|
||||
{ShaderRecompiler::ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())}
|
||||
}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {address, {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, ColorBytes, 0xe4u};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0.0f, 1.0f};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend = {VK_TRUE, VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, 15};
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(DepthFastClearHarness::Triangles().size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto planeBytes = static_cast<std::size_t>(AgcDriver::Graphics::DepthSliceBytes({Width, Height}, 4u));
|
||||
Require(planeBytes <= Block, "the depth plane does not fit one block");
|
||||
auto* block = static_cast<float*>(AllocateWatched(Block));
|
||||
const std::span<float> plane(block, planeBytes / sizeof(float));
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(block);
|
||||
const auto shaders = Compile(*device);
|
||||
const auto reader = Compile(*device, block);
|
||||
const Surface depth{address, 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, AgcDriver::Graphics::XorSwizzleMode(AgcDriver::Graphics::TextureTileMode::kZ64KBX)};
|
||||
|
||||
Require(Draw(*device, shaders, depth, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 did not cover the target");
|
||||
Draw(*device, reader, depth, {.depthTest = false});
|
||||
Require(Holding(plane, 0.5f) == static_cast<std::size_t>(Width) * Height, "a vertex fetch from the depth plane did not see the depth 0.5 a draw wrote into it");
|
||||
|
||||
if (!AgcDriver::GuestMemory::WriteWatched() || !AgcDriver::GuestMemory::Watched(address, Block)) {
|
||||
std::puts("guest memory is not write-watched here: a buffer write into the depth plane is not exercised");
|
||||
std::puts("depth plane buffer memory tests passed");
|
||||
return 0;
|
||||
}
|
||||
std::fill(plane.begin(), plane.end(), 0.75f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Require(Draw(*device, shaders, depth, {}) == Covered, "a draw at depth 0.5 failed LESS against the 0.75 written into the depth plane's memory");
|
||||
Require(Draw(*device, shaders, depth, {.depthWrite = true}) == Covered, "a draw at depth 0.5 writing depth failed LESS against 0.75");
|
||||
Draw(*device, reader, depth, {.depthTest = false});
|
||||
Require(Holding(plane, 0.5f) == static_cast<std::size_t>(Width) * Height, "a vertex fetch from the depth plane did not see the depth 0.5 written after the plane was reloaded");
|
||||
|
||||
auto* fresh = static_cast<float*>(AllocateWatched(Block));
|
||||
const std::span<float> freshPlane(fresh, planeBytes / sizeof(float));
|
||||
const auto freshReader = Compile(*device, fresh);
|
||||
const Surface unstored{reinterpret_cast<std::uintptr_t>(fresh), 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, depth.swizzle};
|
||||
Require(Draw(*device, shaders, unstored, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 into a new plane did not cover the target");
|
||||
std::fill(freshPlane.begin(), freshPlane.end(), 0.75f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Draw(*device, freshReader, depth, {.depthTest = false});
|
||||
Require(Holding(freshPlane, 0.75f) == freshPlane.size(), "the depth 0.5 a draw wrote before a guest write into the plane was stored over that write");
|
||||
Require(Draw(*device, shaders, unstored, {}) == Covered, "a draw at depth 0.5 failed LESS against the 0.75 written over depth the surface had not stored");
|
||||
std::fill(freshPlane.begin(), freshPlane.end(), 0.25f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Require(Draw(*device, shaders, unstored, {}) == 0u, "a draw at depth 0.5 passed LESS against the 0.25 written into the plane before it");
|
||||
Require(Draw(*device, shaders, unstored, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 over the reloaded plane did not cover the target");
|
||||
Draw(*device, freshReader, depth, {.depthTest = false});
|
||||
Require(Holding(freshPlane, 0.5f) == static_cast<std::size_t>(Width) * Height, "the depth 0.5 a draw wrote after a guest write into the plane did not reach memory");
|
||||
|
||||
Htile.fill(0xffffffffu);
|
||||
auto* cleared = static_cast<float*>(AllocateWatched(Block));
|
||||
const std::span<float> clearedPlane(cleared, planeBytes / sizeof(float));
|
||||
const auto clearedReader = Compile(*device, cleared);
|
||||
const Surface compressed{reinterpret_cast<std::uintptr_t>(cleared), 0, reinterpret_cast<std::uintptr_t>(Htile.data()), false, VK_FORMAT_D32_SFLOAT, 0, 0, depth.swizzle};
|
||||
Require(Draw(*device, shaders, compressed, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 into a plane with HTILE did not cover the target");
|
||||
Draw(*device, clearedReader, depth, {.depthTest = false});
|
||||
Require(Holding(clearedPlane, 0.5f) == static_cast<std::size_t>(Width) * Height, "a vertex fetch from a plane with HTILE did not see the depth 0.5 a draw wrote into it");
|
||||
std::fill(clearedPlane.begin(), clearedPlane.end(), 0.25f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
AgcDriver::Graphics::NoteDepthMetadataFill(compressed.htile, HtileBytes, 0u);
|
||||
Require(Draw(*device, shaders, compressed, {}) == Covered, "a guest write into the depth plane won over a later HTILE fast clear of it");
|
||||
|
||||
auto* viewed = static_cast<float*>(AllocateWatched(Block));
|
||||
const std::span<float> viewedBlock(viewed, Block / sizeof(float));
|
||||
std::fill(viewedBlock.begin(), viewedBlock.end(), 0.25f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
ViewedHtile.fill(0xffffffffu);
|
||||
const Surface sampled{reinterpret_cast<std::uintptr_t>(viewed), 0, reinterpret_cast<std::uintptr_t>(ViewedHtile.data()), false, VK_FORMAT_D32_SFLOAT, 0, 0, depth.swizzle};
|
||||
const auto viewedReader = Compile(*device, viewed);
|
||||
constexpr std::size_t ViewOffset = 0x100;
|
||||
const auto view = sampled.depth + ViewOffset;
|
||||
const auto matchesMemory = [&](const std::array<std::uint32_t, LoadThreads>& row) {
|
||||
Draw(*device, viewedReader, depth, {.depthTest = false});
|
||||
return std::memcmp(row.data(), reinterpret_cast<const std::byte*>(viewed) + ViewOffset, sizeof(row)) == 0;
|
||||
};
|
||||
Require(Draw(*device, shaders, sampled, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 into a viewed plane did not cover the target");
|
||||
Require(matchesMemory(LoadRow(*device, view, Width, Height / 2u, 0u)), "a linear texture at an offset into a depth plane did not read the depth drawn into it");
|
||||
AgcDriver::Graphics::NoteDepthMetadataFill(sampled.htile, HtileBytes, 0u);
|
||||
Draw(*device, shaders, sampled, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_NEVER});
|
||||
Require(matchesMemory(LoadRow(*device, view, Width, Height / 2u, 0u)), "a cached linear texture at an offset into a depth plane did not read the depth cleared after it was cached");
|
||||
Require(Draw(*device, shaders, sampled, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 over the cleared viewed plane did not cover the target");
|
||||
Require(matchesMemory(LoadRow(*device, view, Width, Height / 2u, 0u)), "a linear texture at an offset into a depth plane, viewed through its cached storage image, did not read the depth drawn after the image was made");
|
||||
|
||||
auto* blended = static_cast<float*>(AllocateWatched(Block));
|
||||
const std::span<float> blendedBlock(blended, Block / sizeof(float));
|
||||
std::fill(blendedBlock.begin(), blendedBlock.end(), 0.25f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
const Surface underBlend{reinterpret_cast<std::uintptr_t>(blended), 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, depth.swizzle};
|
||||
Require(Draw(*device, shaders, underBlend, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 under a later blended color target did not cover the target");
|
||||
DrawBlendKeepingMemory(*device, shaders, underBlend.depth);
|
||||
Draw(*device, Compile(*device, blended), depth, {.depthTest = false});
|
||||
Require(Holding(blendedBlock, 0.5f) == static_cast<std::size_t>(Width) * Height, "a color target blending over a depth plane did not load the depth drawn into the plane");
|
||||
Htile.fill(0xffffffffu);
|
||||
auto* reused = static_cast<float*>(AllocateWatched(Block));
|
||||
const std::span<float> reusedPlane(reused, planeBytes / sizeof(float));
|
||||
const auto reusedReader = Compile(*device, reused);
|
||||
const Surface recleared{reinterpret_cast<std::uintptr_t>(reused), 0, reinterpret_cast<std::uintptr_t>(Htile.data()), false, VK_FORMAT_D32_SFLOAT, 0, 0, depth.swizzle};
|
||||
Require(Draw(*device, shaders, recleared, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the draw writing depth 0.5 into a plane that is cleared each frame did not cover the target");
|
||||
Draw(*device, reusedReader, depth, {.depthTest = false});
|
||||
Require(Holding(reusedPlane, 0.5f) == static_cast<std::size_t>(Width) * Height, "a vertex fetch did not see the depth 0.5 drawn into a plane that is cleared each frame");
|
||||
std::fill(reusedPlane.begin(), reusedPlane.end(), 0.25f);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
for (int frame = 0; frame < 2; ++frame) {
|
||||
const float value = 0.75f - 0.125f * static_cast<float>(frame);
|
||||
AgcDriver::Graphics::NoteDepthMetadataFill(recleared.htile, HtileBytes, 0u);
|
||||
Require(Draw(*device, shaders, recleared, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .minDepth = value, .maxDepth = value}) == Covered, "the draw after the HTILE clear did not cover the target");
|
||||
Draw(*device, reusedReader, depth, {.depthTest = false});
|
||||
Require(Holding(reusedPlane, value) == static_cast<std::size_t>(Width) * Height, "after a guest write into a stored depth plane, the depth drawn behind an HTILE clear did not reach memory (frame " + std::to_string(frame) + ")");
|
||||
}
|
||||
std::puts("depth plane buffer memory tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 8> VertexCode{0x7e000208u, 0x7e020280u, 0x7e040280u, 0x7e0602f2u, 0xf80008cfu, 0x03020100u, 0xbf810000u, 0xbf810000u};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> PixelCode{0xf4080100u, 0xfa000000u, 0xbf8cc07fu, 0xe0300000u, 0x80010000u, 0xbf8c3f70u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 16> TriangleVertexCode{
|
||||
0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u,
|
||||
0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u,
|
||||
};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> ArithmeticPixelCode{0xf4000100u, 0xfa000000u, 0xbf8cc07fu, 0x7e000204u, 0x06000002u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u};
|
||||
alignas(256) std::array<float, 4> PixelConstant{};
|
||||
alignas(256) std::array<std::byte, 1024> ColorMemory{};
|
||||
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
|
||||
struct Header {
|
||||
Shader shader{};
|
||||
ShaderUserData userData{};
|
||||
};
|
||||
|
||||
alignas(8) Header VertexHeader{};
|
||||
alignas(8) Header PixelHeader{};
|
||||
alignas(8) Header ArithmeticPixelHeader{};
|
||||
|
||||
void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) {
|
||||
header.shader.file_header = 0x34333231u;
|
||||
header.shader.version = 0x18u;
|
||||
header.shader.header_size = sizeof(Header);
|
||||
header.shader.shader_size = static_cast<std::uint32_t>(bytes);
|
||||
header.shader.code = code;
|
||||
header.shader.type = type;
|
||||
header.shader.user_data = &header.userData;
|
||||
AgcDriverRegisterShader_nid_postfix(&header.shader);
|
||||
}
|
||||
|
||||
std::uint32_t* GuestBlock() {
|
||||
#ifdef _WIN32
|
||||
auto* block = static_cast<std::uint32_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
auto* block = static_cast<std::uint32_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
|
||||
#endif
|
||||
Require(block != nullptr, "data-only hit: cannot allocate a guest block");
|
||||
std::memset(block, 0, BlockBytes);
|
||||
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
|
||||
return block;
|
||||
}
|
||||
|
||||
void Append(std::vector<std::uint32_t>& commands, std::uint32_t opcode, std::initializer_list<std::uint32_t> payload) {
|
||||
commands.push_back(0xc0000000u | (static_cast<std::uint32_t>(payload.size() - 1) << 16u) | (opcode << 8u));
|
||||
commands.insert(commands.end(), payload);
|
||||
}
|
||||
|
||||
void PixelUserData(std::vector<std::uint32_t>& commands, const std::uint32_t* pointer, std::uint32_t unread) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
Append(commands, 0x76, {0x00c, static_cast<std::uint32_t>(address)});
|
||||
Append(commands, 0x76, {0x00d, static_cast<std::uint32_t>(address >> 32u)});
|
||||
Append(commands, 0x76, {0x00e, unread});
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> Setup(bool triangle = false) {
|
||||
const auto color = reinterpret_cast<std::uintptr_t>(ColorMemory.data());
|
||||
const auto vertex = reinterpret_cast<std::uintptr_t>(triangle ? TriangleVertexCode.data() : VertexCode.data());
|
||||
const auto pixel = reinterpret_cast<std::uintptr_t>(triangle ? ArithmeticPixelCode.data() : PixelCode.data());
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> context{
|
||||
{0x2d5, 0x2000},
|
||||
{0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800},
|
||||
{0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0},
|
||||
{0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa},
|
||||
{0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff},
|
||||
{0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240},
|
||||
{0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010},
|
||||
{0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028},
|
||||
{0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u},
|
||||
{0x3b8, 0x9000000}, {0x1e0, 0},
|
||||
{0xc, 0}, {0xd, 0x40040},
|
||||
{0x81, 0x80000000}, {0x82, 0x40040},
|
||||
{0x90, 0x80000000}, {0x91, 0x40040},
|
||||
{0x94, 0x80000000}, {0x95, 0x40040},
|
||||
{0x318, static_cast<std::uint32_t>(color >> 8u)}, {0x390, static_cast<std::uint32_t>(color >> 40u)},
|
||||
{0x10f, std::bit_cast<std::uint32_t>(32.0f)}, {0x110, std::bit_cast<std::uint32_t>(32.0f)},
|
||||
{0x111, std::bit_cast<std::uint32_t>(-2.0f)}, {0x112, std::bit_cast<std::uint32_t>(2.0f)},
|
||||
{0x113, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0},
|
||||
{0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
};
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> shader{
|
||||
{0xc8, static_cast<std::uint32_t>(vertex >> 8u)}, {0xc9, static_cast<std::uint32_t>(vertex >> 40u)}, {0x8b, triangle ? 0u : 1u << 1u}, {0x8c, 0},
|
||||
{0x008, static_cast<std::uint32_t>(pixel >> 8u)}, {0x009, static_cast<std::uint32_t>(pixel >> 40u)}, {0x00b, 3u << 1u},
|
||||
};
|
||||
if (triangle) context.insert(context.end(), {{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}});
|
||||
std::vector<std::uint32_t> commands;
|
||||
Append(commands, 0x79, {0x242, 4});
|
||||
for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value});
|
||||
for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value});
|
||||
return commands;
|
||||
}
|
||||
|
||||
AgcDriver::DriverDetail::DrawEntryCounters Submit(std::vector<std::uint32_t> commands) {
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
return AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
|
||||
}
|
||||
|
||||
std::string Counts(const AgcDriver::DriverDetail::DrawEntryCounters& counters) {
|
||||
return std::to_string(counters.lookups) + " lookups, " + std::to_string(counters.dataHits) + " data-only hits, stages " + std::to_string(counters.dataStagesReused) + " reused / " + std::to_string(counters.dataStagesCompiled) + " compiled / " + std::to_string(counters.dataVerified) + " verified";
|
||||
}
|
||||
|
||||
void UnreadUserWordsKeepTheStage(bool verify) {
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
auto* first = GuestBlock();
|
||||
auto* second = GuestBlock();
|
||||
const auto describe = [](std::uint32_t* table, std::uint32_t* data, float value) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
data[0] = std::bit_cast<std::uint32_t>(value);
|
||||
table[0] = static_cast<std::uint32_t>(address);
|
||||
table[1] = static_cast<std::uint32_t>(address >> 32u) & 0xffffu;
|
||||
table[2] = 16;
|
||||
table[3] = 0x4bfacu;
|
||||
};
|
||||
describe(first, first + 1024, 1.0f);
|
||||
describe(second, second + 1024, 0.5f);
|
||||
|
||||
auto commands = Setup();
|
||||
PixelUserData(commands, first, 0x11);
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
auto counters = Submit(std::move(commands));
|
||||
Require(counters.lookups == 1 && counters.dataHits == 0, "the first draw: " + Counts(counters));
|
||||
commands.clear();
|
||||
PixelUserData(commands, first, 0x11);
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
counters = Submit(std::move(commands));
|
||||
Require(counters.inserts != 0, "the repeated register key was not admitted: " + Counts(counters));
|
||||
|
||||
commands.clear();
|
||||
PixelUserData(commands, first, 0x22);
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
const auto before = counters;
|
||||
counters = Submit(std::move(commands));
|
||||
const auto reused = counters.dataStagesReused - before.dataStagesReused;
|
||||
const auto compiled = counters.dataStagesCompiled - before.dataStagesCompiled;
|
||||
Require(counters.dataHits == before.dataHits + 1, "a draw that changed only a user word no stage reads was not a data-only hit: " + Counts(counters));
|
||||
if (verify) Require(compiled == 2 && counters.dataVerified - before.dataVerified == 2, "verify mode: a draw that changed only an unread user word did not build and compare both stages: " + Counts(counters));
|
||||
else Require(reused == 2 && compiled == 0, "a draw that changed only a user word no stage reads did not reuse both stages: " + Counts(counters));
|
||||
|
||||
commands.clear();
|
||||
PixelUserData(commands, second, 0x22);
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
const auto moved = counters;
|
||||
counters = Submit(std::move(commands));
|
||||
Require(counters.dataHits == moved.dataHits + 1, "a draw that pointed the pixel stage at another buffer descriptor was not a data-only hit: " + Counts(counters));
|
||||
Require(counters.dataStagesCompiled - moved.dataStagesCompiled >= 1, "a draw that pointed the pixel stage at another buffer descriptor reused the pixel stage: " + Counts(counters));
|
||||
if (!verify) Require(counters.dataStagesReused - moved.dataStagesReused == 1, "a draw that changed only the pixel stage's descriptor pointer did not reuse the vertex stage: " + Counts(counters));
|
||||
}
|
||||
|
||||
unsigned BrightestColorByte() {
|
||||
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(ColorMemory.data()), ColorMemory.size());
|
||||
unsigned brightest = 0;
|
||||
for (const auto value : ColorMemory) brightest = std::max(brightest, static_cast<unsigned>(std::to_integer<std::uint8_t>(value)));
|
||||
return brightest;
|
||||
}
|
||||
|
||||
void ReadUserWordsChangeTheOutput() {
|
||||
Registered(VertexHeader, TriangleVertexCode.data(), sizeof(TriangleVertexCode), 2);
|
||||
Registered(ArithmeticPixelHeader, ArithmeticPixelCode.data(), sizeof(ArithmeticPixelCode), 1);
|
||||
PixelConstant[0] = 0.25f;
|
||||
auto* table = reinterpret_cast<std::uint32_t*>(PixelConstant.data());
|
||||
const auto draw = [&](float word, bool setup) {
|
||||
auto commands = setup ? Setup(true) : std::vector<std::uint32_t>{};
|
||||
PixelUserData(commands, table, std::bit_cast<std::uint32_t>(word));
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
Submit(std::move(commands));
|
||||
return BrightestColorByte();
|
||||
};
|
||||
draw(0.25f, true);
|
||||
const auto first = draw(0.25f, false);
|
||||
const auto before = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
|
||||
const auto second = draw(0.5f, false);
|
||||
const auto after = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
|
||||
const auto closeTo = [](unsigned value, unsigned expected) { return value + 2 >= expected && value <= expected + 2; };
|
||||
Require(closeTo(first, 128), "a pixel stage adding user word 2 (0.25) to a loaded 0.25 wrote " + std::to_string(first) + ", expected 128");
|
||||
Require(closeTo(second, 191), "a pixel stage adding user word 2 changed to 0.5 wrote " + std::to_string(second) + ", expected 191 (" + Counts(after) + ", before: " + Counts(before) + ")");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
bool libraries = false;
|
||||
{
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
libraries = device->GraphicsPipelineLibraries();
|
||||
}
|
||||
if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped;
|
||||
const bool verify = std::getenv("APS5_VERIFY_DRAW_DATA_HITS") != nullptr;
|
||||
UnreadUserWordsKeepTheStage(verify);
|
||||
ReadUserWordsChangeTheOutput();
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::cout << "Data-only hits " << (verify ? "verified" : "reused") << " the stages whose read user words did not change\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -505,13 +505,15 @@ int main() {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto capabilities = device->Target().supportedCapabilities;
|
||||
const bool hardware = std::find(capabilities.begin(), capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormFlushToZero)) != capabilities.end();
|
||||
const bool declared = std::find(capabilities.begin(), capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormFlushToZero)) != capabilities.end();
|
||||
const bool native = !declared && device->Target().float32FlushToZero;
|
||||
const bool hardware = declared || native;
|
||||
if (hardware) {
|
||||
bool softwareDeclares = true;
|
||||
bool hardwareDeclares = false;
|
||||
const auto software = CompiledWords(*device, false, softwareDeclares);
|
||||
const auto hardwareWords = CompiledWords(*device, true, hardwareDeclares);
|
||||
Require(!softwareDeclares && hardwareDeclares, "f32 denormal flush: only the hardware-flush module declares DenormFlushToZero 32");
|
||||
Require(!softwareDeclares && hardwareDeclares == declared, declared ? "f32 denormal flush: only the hardware-flush module declares DenormFlushToZero 32" : "f32 denormal flush: a module flushed natively declares DenormFlushToZero 32 without the device capability");
|
||||
Require(hardwareWords < software, "f32 denormal flush: the hardware-flush module is " + std::to_string(hardwareWords) + " words, the emulated one " + std::to_string(software));
|
||||
}
|
||||
for (const bool flush : {false, true}) {
|
||||
|
||||
@@ -64,6 +64,12 @@ alignas(256) constexpr std::array<std::uint32_t, 22> ChainCode{
|
||||
0xbf8cc07fu, 0x7e02020cu, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 22> TwoGuardCode{
|
||||
0xf4080100u, 0xfa000000u, 0xf4040280u, 0xfa000018u, 0xf4040380u, 0xfa000020u, 0xbe8c03ffu, 0x11111111u,
|
||||
0xbf8cc07fu, 0xbf068002u, 0xbf840003u, 0xf4000305u, 0xfa000000u, 0xbf820002u, 0xf4000307u, 0xfa000000u,
|
||||
0xbf8cc07fu, 0x7e02020cu, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 21> DescriptorCode{
|
||||
0xf4080100u, 0xfa000000u, 0xf4000200u, 0xfa000010u, 0xf4040280u, 0xfa000018u, 0x7e0202ffu, 0x11111111u,
|
||||
0xbf8cc07fu, 0xbf068008u, 0xbf850006u, 0xf4080405u, 0xfa000010u, 0xbf8cc07fu, 0xe0300000u, 0x80040100u,
|
||||
@@ -117,7 +123,7 @@ struct Outcome {
|
||||
};
|
||||
|
||||
template <std::size_t CodeWords>
|
||||
Outcome Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, CodeWords>& code, std::uint64_t pointer, std::uint32_t guard) {
|
||||
Outcome Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, CodeWords>& code, std::uint64_t pointer, std::uint32_t guard, std::uint32_t groups = 1u, std::uint64_t second = 0u) {
|
||||
const auto output = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Output.data()));
|
||||
Root = {};
|
||||
Root[0] = static_cast<std::uint32_t>(output);
|
||||
@@ -127,11 +133,13 @@ Outcome Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, Cod
|
||||
Root[4] = guard;
|
||||
Root[6] = static_cast<std::uint32_t>(pointer);
|
||||
Root[7] = static_cast<std::uint32_t>(pointer >> 32u);
|
||||
Root[8] = static_cast<std::uint32_t>(second);
|
||||
Root[9] = static_cast<std::uint32_t>(second >> 32u);
|
||||
std::fill(Output.begin(), Output.end(), 0xdeadbeefu);
|
||||
const auto root = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Root.data()));
|
||||
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(root), static_cast<std::uint32_t>(root >> 32u)};
|
||||
const std::span<const std::uint32_t> span(code);
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {groups > 1u, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(span.data()), span, 0, {}},
|
||||
{64, 0, userData, compute, std::nullopt, std::nullopt, {}},
|
||||
@@ -150,7 +158,7 @@ Outcome Run(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, Cod
|
||||
outcome.cacheable = AgcDriver::DriverDetail::CacheableResult(*result);
|
||||
StderrCapture log;
|
||||
try {
|
||||
device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(span.data()));
|
||||
device.Dispatch(*result, groups, 1, 1, {}, reinterpret_cast<std::uintptr_t>(span.data()));
|
||||
device.WaitIdle();
|
||||
} catch (...) {
|
||||
static_cast<void>(log.Finish());
|
||||
@@ -204,6 +212,11 @@ void RunTests(AgcDriver::VulkanDevice& device) {
|
||||
Require(Faulted(takenUnmapped) && takenUnmapped.log.find(Fault(unmapped, 0x2cu)) != std::string::npos, "guarded pointer: a read through an unmapped pointer that runs did not fault at its address:\n" + takenUnmapped.log);
|
||||
Require(takenUnmapped.pipelineVariantId == taken.pipelineVariantId, "guarded pointer: an unmapped pointer changed the pipeline variant");
|
||||
|
||||
const auto twoGuards = Run(device, TwoGuardCode, 0u, 0u, 2u, unmapped);
|
||||
const bool nullRecord = twoGuards.log.find(Fault(0u, 0x2cu)) != std::string::npos;
|
||||
const bool unmappedRecord = twoGuards.log.find(Fault(unmapped, 0x38u)) != std::string::npos;
|
||||
Require(twoGuards.poisoned == 2u && Faulted(twoGuards) && nullRecord != unmappedRecord, "guarded pointer: two workgroups faulting through different guards did not report exactly one of their two records:\n" + twoGuards.log);
|
||||
|
||||
const auto someLanes = Run(device, ExecCode, valid, 5u);
|
||||
Require(!Faulted(someLanes), "guarded pointer: a mapped pointer under EXEC faulted:\n" + someLanes.log);
|
||||
RequireWords(someLanes, 5u, Payload, "guarded pointer: a mapped pointer read under EXEC");
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t Words = 32;
|
||||
constexpr std::uint32_t Size = 16;
|
||||
constexpr std::uint32_t Layers = 3;
|
||||
constexpr std::uint32_t Format16UNorm = 7;
|
||||
constexpr std::uint32_t Type1D = 8;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr std::uint32_t Type1DArray = 12;
|
||||
constexpr std::uint32_t ReferenceWord = 6;
|
||||
constexpr std::uint32_t Result = 8;
|
||||
constexpr std::uint32_t Step1D = 4000;
|
||||
constexpr std::uint32_t StepArray = 1200;
|
||||
constexpr std::uint32_t Step2D = 200;
|
||||
constexpr std::uint32_t Row2D = 2;
|
||||
constexpr std::uint32_t CompareLess = 1;
|
||||
|
||||
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
|
||||
alignas(256) std::array<std::uint8_t, 16384> Line{};
|
||||
alignas(4096) std::array<std::uint8_t, 32768> ArrayTexels{};
|
||||
alignas(4096) std::array<std::uint8_t, 32768> PlaneTexels{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 16> Code1D_Lz{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0xf0bc0100, 0x00610c14, 0xbf8c3f70, 0xe0701020,
|
||||
0x80000c01, 0xbf810000, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 16> Code1D_ImplicitLod{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0xf0a00100, 0x00610c14, 0xbf8c3f70, 0xe0701020,
|
||||
0x80000c01, 0xbf810000, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> Code1D_ExplicitLod{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0x7e2c0280, 0xf0b00100, 0x00610c14, 0xbf8c3f70,
|
||||
0xe0701020, 0x80000c01, 0xbf810000, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 18> Code1D_Lz_Offset{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e2802ff, 0x00000f00, 0x7e2a0308, 0x7e2c0302, 0xf0fc0100, 0x00610c14,
|
||||
0xbf8c3f70, 0xe0701020, 0x80000c01, 0xbf810000, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 19> Code1DArray_Lz_Offset{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e2802ff, 0x00000f00, 0x7e2a0308, 0x7e2c0302, 0x7e2e0303, 0xf0fc0120, 0x00610c14,
|
||||
0xbf8c3f70, 0xe0701020, 0x80000c01, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> Code1DArray_Lz{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0x7e2c0303, 0xf0bc0120, 0x00610c14, 0xbf8c3f70, 0xe0701020, 0x80000c01,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> Code1DArray_ImplicitLod{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0x7e2c0303, 0xf0a00120, 0x00610c14, 0xbf8c3f70, 0xe0701020, 0x80000c01,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> Code2D_Lz{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0x7e2c0303, 0xf0bc0108, 0x00610c14, 0xbf8c3f70, 0xe0701020, 0x80000c01,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> Code2D_ImplicitLod{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0x7e2c0303, 0xf0a00108, 0x00610c14, 0xbf8c3f70, 0xe0701020, 0x80000c01,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 16> Code1D_Gather_C_Lz{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0xf13c0100, 0x00610c14, 0xbf8c3f70, 0xe0701020,
|
||||
0x80000c01, 0xbf810000, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 17> Code1DArray_Gather_C_Lz{
|
||||
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301018, 0x80000801, 0xbf8c3f70,
|
||||
0x7e280308, 0x7e2a0302, 0x7e2c0303, 0xf13c0120, 0x00610c14, 0xbf8c3f70, 0xe0701020, 0x80000c01,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
std::uint32_t TexelOf(std::uint32_t tid) {
|
||||
return (tid * 7u + 3u) % Size;
|
||||
}
|
||||
|
||||
bool ReferenceBelow(std::uint32_t tid) {
|
||||
return (tid & 1u) == 0u;
|
||||
}
|
||||
|
||||
std::uint32_t Bits(float value) {
|
||||
return std::bit_cast<std::uint32_t>(value);
|
||||
}
|
||||
|
||||
void FillInput1D() {
|
||||
Buffer.fill(0xdeadbeefu);
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const auto x = TexelOf(tid);
|
||||
const float texel = static_cast<float>((x + 1u) * Step1D) / 65535.0f;
|
||||
const float half = static_cast<float>(Step1D / 2u) / 65535.0f;
|
||||
Buffer[tid * Words] = Bits((static_cast<float>(x) + 0.5f) / static_cast<float>(Size));
|
||||
Buffer[tid * Words + ReferenceWord] = Bits(ReferenceBelow(tid) ? texel - half : texel + half);
|
||||
}
|
||||
}
|
||||
|
||||
void FillInput1DArray() {
|
||||
Buffer.fill(0xdeadbeefu);
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const auto x = TexelOf(tid);
|
||||
const auto layer = (tid / 7u) % Layers;
|
||||
const float texel = static_cast<float>((layer * Size + x + 1u) * StepArray) / 65535.0f;
|
||||
const float half = static_cast<float>(StepArray / 2u) / 65535.0f;
|
||||
Buffer[tid * Words] = Bits((static_cast<float>(x) + 0.5f) / static_cast<float>(Size));
|
||||
Buffer[tid * Words + 1u] = Bits(static_cast<float>(layer));
|
||||
Buffer[tid * Words + ReferenceWord] = Bits(ReferenceBelow(tid) ? texel - half : texel + half);
|
||||
}
|
||||
}
|
||||
|
||||
void FillInput2D() {
|
||||
Buffer.fill(0xdeadbeefu);
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const auto x = TexelOf(tid);
|
||||
const float texel = static_cast<float>((Row2D * Size + x + 1u) * Step2D) / 65535.0f;
|
||||
const float half = static_cast<float>(Step2D / 2u) / 65535.0f;
|
||||
Buffer[tid * Words] = Bits((static_cast<float>(x) + 0.5f) / static_cast<float>(Size));
|
||||
Buffer[tid * Words + 1u] = Bits((static_cast<float>(Row2D) + 0.5f) / static_cast<float>(Size));
|
||||
Buffer[tid * Words + ReferenceWord] = Bits(ReferenceBelow(tid) ? texel - half : texel + half);
|
||||
}
|
||||
}
|
||||
|
||||
void FillInputArrayFirstLayer() {
|
||||
Buffer.fill(0xdeadbeefu);
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const auto x = TexelOf(tid);
|
||||
const float texel = static_cast<float>((0u * Size + x + 1u) * StepArray) / 65535.0f;
|
||||
const float half = static_cast<float>(StepArray / 2u) / 65535.0f;
|
||||
Buffer[tid * Words] = Bits((static_cast<float>(x) + 0.5f) / static_cast<float>(Size));
|
||||
Buffer[tid * Words + ReferenceWord] = Bits(ReferenceBelow(tid) ? texel - half : texel + half);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> LineDescriptor() {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Line.data()));
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format16UNorm << 20u) | (((Size - 1u) & 3u) << 30u),
|
||||
(Size - 1u) >> 2u,
|
||||
0xfacu | (Type1D << 28u),
|
||||
0u, 0u, 0u, 0u,
|
||||
};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> ArrayDescriptor() {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(ArrayTexels.data()));
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format16UNorm << 20u) | (((Size - 1u) & 3u) << 30u),
|
||||
(Size - 1u) >> 2u,
|
||||
0xfacu | (Type1DArray << 28u),
|
||||
Layers - 1u, 0u, 0u, 0u,
|
||||
};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> PlaneDescriptor() {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(PlaneTexels.data()));
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format16UNorm << 20u) | (((Size - 1u) & 3u) << 30u),
|
||||
((Size - 1u) >> 2u) | ((Size - 1u) << 14u),
|
||||
0xfacu | (Type2D << 28u),
|
||||
0u, 0u, 0u, 0u,
|
||||
};
|
||||
}
|
||||
|
||||
void FillLine() {
|
||||
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(LineDescriptor()));
|
||||
Require(geometry.guestBytes <= Line.size(), "1D texture does not fit texel storage");
|
||||
std::fill(Line.begin(), Line.end(), 0xeeu);
|
||||
for (std::uint32_t x = 0; x < Size; ++x) {
|
||||
const auto value = static_cast<std::uint16_t>((x + 1u) * Step1D);
|
||||
auto* texel = &Line[geometry.GuestLayerOffset(0) + geometry.mips.at(0).tiledOffset + x * 2u];
|
||||
texel[0] = static_cast<std::uint8_t>(value);
|
||||
texel[1] = static_cast<std::uint8_t>(value >> 8u);
|
||||
}
|
||||
}
|
||||
|
||||
void FillArray() {
|
||||
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(ArrayDescriptor()));
|
||||
Require(geometry.guestBytes <= ArrayTexels.size(), "1D array texture does not fit texel storage");
|
||||
std::fill(ArrayTexels.begin(), ArrayTexels.end(), 0xeeu);
|
||||
for (std::uint32_t layer = 0; layer < Layers; ++layer) {
|
||||
for (std::uint32_t x = 0; x < Size; ++x) {
|
||||
const auto value = static_cast<std::uint16_t>((layer * Size + x + 1u) * StepArray);
|
||||
auto* texel = &ArrayTexels[geometry.GuestLayerOffset(layer) + geometry.mips.at(0).tiledOffset + x * 2u];
|
||||
texel[0] = static_cast<std::uint8_t>(value);
|
||||
texel[1] = static_cast<std::uint8_t>(value >> 8u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FillPlane() {
|
||||
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(PlaneDescriptor()));
|
||||
Require(geometry.guestBytes <= PlaneTexels.size(), "2D texture does not fit texel storage");
|
||||
std::fill(PlaneTexels.begin(), PlaneTexels.end(), 0xeeu);
|
||||
for (std::uint32_t y = 0; y < Size; ++y) {
|
||||
for (std::uint32_t x = 0; x < Size; ++x) {
|
||||
const auto value = static_cast<std::uint16_t>((y * Size + x + 1u) * Step2D);
|
||||
auto* texel = &PlaneTexels[geometry.GuestLayerOffset(0) + geometry.mips.at(0).tiledOffset + static_cast<std::uint64_t>(y) * geometry.mips.at(0).pitchBytes + x * 2u];
|
||||
texel[0] = static_cast<std::uint8_t>(value);
|
||||
texel[1] = static_cast<std::uint8_t>(value >> 8u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, 4> SamplerDescriptor() {
|
||||
return {CompareLess << 12u, 0xfffu << 12u, 1u << 26u, 0u};
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::array<std::uint32_t, 8>& textureDesc) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto buffer = BufferDescriptor(Buffer.data(), static_cast<std::uint32_t>(Buffer.size() * 4u));
|
||||
const auto sampler = SamplerDescriptor();
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin());
|
||||
std::copy(textureDesc.begin(), textureDesc.end(), userData.begin() + 4);
|
||||
std::copy(sampler.begin(), sampler.end(), userData.begin() + 12);
|
||||
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();
|
||||
}
|
||||
|
||||
void Check(const std::string& op) {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const float value = std::bit_cast<float>(Buffer[tid * Words + Result]);
|
||||
const float expected = ReferenceBelow(tid) ? 1.0f : 0.0f;
|
||||
Require(value == expected, op + ": thread " + std::to_string(tid) + " is " + std::to_string(value) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void ExpectGatherReject(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::array<std::uint32_t, 8>& textureDesc) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto buffer = BufferDescriptor(Buffer.data(), static_cast<std::uint32_t>(sizeof(Buffer)));
|
||||
const auto sampler = SamplerDescriptor();
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin());
|
||||
std::copy(textureDesc.begin(), textureDesc.end(), userData.begin() + 4);
|
||||
std::copy(sampler.begin(), sampler.end(), userData.begin() + 12);
|
||||
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;
|
||||
try {
|
||||
(void)ShaderRecompiler::Recompile(request);
|
||||
Require(false, "remapped 1D depth gather was accepted unexpectedly");
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string(error.what()).find("has an unsupported 1D gather variant") != std::string::npos, "unexpected error for 1D depth gather: " + std::string(error.what()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
FillLine();
|
||||
FillArray();
|
||||
FillPlane();
|
||||
|
||||
FillInput1D();
|
||||
Run(*device, Code1D_Lz, LineDescriptor());
|
||||
Check("1D image_sample_c_lz");
|
||||
|
||||
FillInput1D();
|
||||
Run(*device, Code1D_ImplicitLod, LineDescriptor());
|
||||
Check("1D image_sample_c");
|
||||
|
||||
FillInput1D();
|
||||
Run(*device, Code1D_ExplicitLod, LineDescriptor());
|
||||
Check("1D image_sample_c_l");
|
||||
|
||||
FillInput1D();
|
||||
Run(*device, Code1D_Lz_Offset, LineDescriptor());
|
||||
Check("1D image_sample_c_lz_o");
|
||||
|
||||
FillInput1DArray();
|
||||
Run(*device, Code1DArray_Lz, ArrayDescriptor());
|
||||
Check("1D array image_sample_c_lz");
|
||||
|
||||
FillInput1DArray();
|
||||
Run(*device, Code1DArray_Lz_Offset, ArrayDescriptor());
|
||||
Check("1D array image_sample_c_lz_o");
|
||||
|
||||
FillInput1DArray();
|
||||
Run(*device, Code1DArray_ImplicitLod, ArrayDescriptor());
|
||||
Check("1D array image_sample_c");
|
||||
|
||||
FillInputArrayFirstLayer();
|
||||
Run(*device, Code1D_Lz, ArrayDescriptor());
|
||||
Check("1D image_sample_c_lz on 1D array first layer");
|
||||
|
||||
FillInputArrayFirstLayer();
|
||||
Run(*device, Code1D_ImplicitLod, ArrayDescriptor());
|
||||
Check("1D image_sample_c on 1D array first layer");
|
||||
|
||||
FillInput2D();
|
||||
Run(*device, Code2D_Lz, PlaneDescriptor());
|
||||
Check("2D image_sample_c_lz");
|
||||
|
||||
FillInput2D();
|
||||
Run(*device, Code2D_ImplicitLod, PlaneDescriptor());
|
||||
Check("2D image_sample_c");
|
||||
|
||||
ExpectGatherReject(*device, Code1D_Gather_C_Lz, LineDescriptor());
|
||||
ExpectGatherReject(*device, Code1DArray_Gather_C_Lz, ArrayDescriptor());
|
||||
|
||||
std::puts("1D, 1D-array, and 2D comparison tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include <SDL_loadso.h>
|
||||
#include "prx/libSceAgcDriver/Submit/include/Acb.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void Require(bool condition, const std::string& message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
alignas(256) std::array<std::uint32_t, 7> AddressWriterCode{0x7e000202u, 0x7e020203u, 0x7e040204u, 0x7e060280u, 0xdc7c8000u, 0x00000003u, 0xbf810000u};
|
||||
alignas(256) std::array<std::uint32_t, 7> CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u};
|
||||
|
||||
constexpr std::uint32_t ThreadDimensions = 0x8061u;
|
||||
constexpr std::uint32_t GroupCounts = 0x8041u;
|
||||
constexpr std::uint32_t Compute = 0x20u;
|
||||
constexpr std::size_t GuestBytes = 1u << 20u;
|
||||
|
||||
struct Slot {
|
||||
alignas(256) std::array<std::uint32_t, 4> arguments{};
|
||||
alignas(256) std::array<std::uint32_t, 4> counter{};
|
||||
alignas(256) std::array<std::uint32_t, 4> counterDescriptor{};
|
||||
alignas(256) std::uint32_t done = 0;
|
||||
};
|
||||
|
||||
struct Shape {
|
||||
const char* name;
|
||||
std::array<std::uint32_t, 3> threads;
|
||||
std::array<std::uint32_t, 3> group;
|
||||
};
|
||||
|
||||
constexpr std::array<Shape, 4> Shapes{{
|
||||
{"100x1x1 threads in groups of 64x1x1", {100u, 1u, 1u}, {64u, 1u, 1u}},
|
||||
{"5x3x2 threads in groups of 4x2x2", {5u, 3u, 2u}, {4u, 2u, 2u}},
|
||||
{"64x2x1 threads in groups of 32x1x1", {64u, 2u, 1u}, {32u, 1u, 1u}},
|
||||
{"0x7x1 threads in groups of 8x1x1", {0u, 7u, 1u}, {8u, 1u, 1u}},
|
||||
}};
|
||||
|
||||
std::uint32_t Low(const volatile void* pointer) {
|
||||
return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer));
|
||||
}
|
||||
|
||||
std::uint32_t High(const volatile void* pointer) {
|
||||
return static_cast<std::uint32_t>(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(pointer)) >> 32u);
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> Descriptor(const void* pointer, std::uint32_t bytes) {
|
||||
return {Low(pointer), High(pointer) & 0xffffu, bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::uint32_t Load(const std::uint32_t& value) {
|
||||
return reinterpret_cast<const volatile std::uint32_t&>(value);
|
||||
}
|
||||
|
||||
std::uint32_t Expected(const Shape& shape) {
|
||||
return shape.threads[0] * shape.threads[1] * shape.threads[2];
|
||||
}
|
||||
|
||||
void SetShaderRegister(std::vector<std::uint32_t>& words, std::uint32_t offset, std::uint32_t value) {
|
||||
words.insert(words.end(), {0xc0017600u, offset, value});
|
||||
}
|
||||
|
||||
void Program(std::vector<std::uint32_t>& words, const void* code, std::array<std::uint32_t, 3> group, std::initializer_list<std::uint32_t> userData) {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(code));
|
||||
SetShaderRegister(words, 0x20c, static_cast<std::uint32_t>(address >> 8u));
|
||||
SetShaderRegister(words, 0x20d, static_cast<std::uint32_t>(address >> 40u) & 0xffu);
|
||||
for (std::uint32_t axis = 0; axis < 3; ++axis) SetShaderRegister(words, 0x207u + axis, group[axis]);
|
||||
SetShaderRegister(words, 0x212, 0);
|
||||
SetShaderRegister(words, 0x213, static_cast<std::uint32_t>(userData.size()) << 1u);
|
||||
std::uint32_t offset = 0x240;
|
||||
for (const auto value : userData) SetShaderRegister(words, offset++, value);
|
||||
}
|
||||
|
||||
void Reset(Slot& slot) {
|
||||
slot.arguments = {0xffffffffu, 0xffffffffu, 0xffffffffu, 0};
|
||||
slot.counter = {};
|
||||
slot.counterDescriptor = Descriptor(slot.counter.data(), sizeof(slot.counter));
|
||||
slot.done = 0;
|
||||
}
|
||||
|
||||
void Check(const Slot& slot, const Shape& shape, const std::string& name) {
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
while (Load(slot.done) == 0) {
|
||||
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(10), name + ": the completion label did not land");
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
}
|
||||
Require(Load(slot.counter[0]) == Expected(shape), name + ": " + std::to_string(Load(slot.counter[0])) + " threads ran, expected " + std::to_string(Expected(shape)));
|
||||
}
|
||||
|
||||
void RunSubmitted(Slot& slot, const Shape& shape, bool producedOnGpu) {
|
||||
Reset(slot);
|
||||
if (!producedOnGpu) {
|
||||
for (std::size_t axis = 0; axis < 3; ++axis) slot.arguments[axis] = shape.threads[axis];
|
||||
}
|
||||
std::vector<std::uint32_t> words;
|
||||
if (producedOnGpu) {
|
||||
Program(words, AddressWriterCode.data(), {1u, 1u, 1u}, {Low(slot.arguments.data()), High(slot.arguments.data()), shape.threads[0], shape.threads[1], shape.threads[2]});
|
||||
words.insert(words.end(), {0xc0031500u, 1, 1, 1, GroupCounts});
|
||||
}
|
||||
Program(words, CounterCode.data(), shape.group, {Low(slot.counterDescriptor.data()), High(slot.counterDescriptor.data())});
|
||||
words.insert(words.end(), {0xc0021102u, 1u, Low(slot.arguments.data()), High(slot.arguments.data())});
|
||||
words.insert(words.end(), {0xc0011600u, 0u, ThreadDimensions});
|
||||
words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(&slot.done), High(&slot.done), 1u});
|
||||
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitAcb(Compute, &packet) == 0, "compute submit failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
Check(slot, shape, std::string(shape.name) + (producedOnGpu ? ", arguments stored by the GPU" : ", arguments stored by the CPU"));
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult Recompile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, std::span<const ShaderRecompiler::MemoryRegion> memory, std::array<std::uint32_t, 3> group, std::array<std::uint32_t, 3> partialThreads, std::uint32_t pushBytes) {
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{group, 0, {false, false, false}, false, 1, partialThreads};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderRecompiler::ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.ComputeTarget(32),
|
||||
{0, 0, 0, pushBytes}
|
||||
};
|
||||
request.useCache = false;
|
||||
return ShaderRecompiler::Recompile(request);
|
||||
}
|
||||
|
||||
void RunCpuFallback(Slot& slot, const Shape& shape) {
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
Reset(slot);
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
Require(device != nullptr, "cannot open the CPU fallback test device");
|
||||
const std::array<std::uint32_t, 5> producerData{Low(slot.arguments.data()), High(slot.arguments.data()), shape.threads[0], shape.threads[1], shape.threads[2]};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> producerMemory{{{reinterpret_cast<std::uintptr_t>(AddressWriterCode.data()), std::as_bytes(std::span(AddressWriterCode))}}};
|
||||
const auto producer = Recompile(*device, AddressWriterCode, producerData, producerMemory, {1u, 1u, 1u}, {}, 128u);
|
||||
const std::array<std::uint32_t, 2> consumerData{Low(slot.counterDescriptor.data()), High(slot.counterDescriptor.data())};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 2> consumerMemory{{
|
||||
{reinterpret_cast<std::uintptr_t>(CounterCode.data()), std::as_bytes(std::span(CounterCode))},
|
||||
{reinterpret_cast<std::uintptr_t>(slot.counterDescriptor.data()), std::as_bytes(std::span(slot.counterDescriptor))},
|
||||
}};
|
||||
const auto consumer = Recompile(*device, CounterCode, consumerData, consumerMemory, shape.group, {~0u, ~0u, ~0u}, 0u);
|
||||
const auto preparedProducer = device->PrepareDispatch(producer, {}, 1, reinterpret_cast<std::uintptr_t>(AddressWriterCode.data()));
|
||||
const auto preparedConsumer = device->PrepareDispatch(consumer, {}, 0, reinterpret_cast<std::uintptr_t>(CounterCode.data()));
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
auto* recorder = AgcDriver::Graphics::Recorder::Active();
|
||||
Require(recorder != nullptr, "the CPU fallback test has no active recorder");
|
||||
device->Dispatch(producer, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(AddressWriterCode.data()), preparedProducer);
|
||||
const auto labelAddress = reinterpret_cast<std::uintptr_t>(&slot.arguments[2]);
|
||||
Require(device->WriteLabelOnGpu(labelAddress, std::as_bytes(std::span(&shape.threads[2], 1)), 1, Compute, false) == 0, "the argument label was not recorded on the GPU");
|
||||
const auto outcome = device->DispatchIndirect(consumer, reinterpret_cast<std::uintptr_t>(slot.arguments.data()), {}, reinterpret_cast<std::uintptr_t>(CounterCode.data()), preparedConsumer, nullptr, shape.group);
|
||||
Require(outcome.cpuReason == 2, std::string(shape.name) + ": the indirect dispatch did not take the pending-label CPU fallback (reason " + std::to_string(outcome.cpuReason) + ")");
|
||||
recorder->Sync();
|
||||
slot.done = 1;
|
||||
Check(slot, shape, std::string(shape.name) + ", read on the CPU behind a pending argument label");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
device.reset();
|
||||
#ifdef _WIN32
|
||||
auto* block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, GuestBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
auto* block = static_cast<std::uint8_t*>(std::aligned_alloc(GuestBytes, GuestBytes));
|
||||
#endif
|
||||
Require(block != nullptr, "cannot allocate guest memory");
|
||||
GuestAllocations::Mutation().Add(block, GuestBytes, true, true, true);
|
||||
auto* slot = new (block) Slot{};
|
||||
const bool gpuThreadDimensions = std::getenv("APS5_NO_GPU_THREAD_DIMENSIONS") == nullptr;
|
||||
for (const auto& shape : Shapes) {
|
||||
if (gpuThreadDimensions) RunSubmitted(*slot, shape, true);
|
||||
RunSubmitted(*slot, shape, false);
|
||||
std::cout << shape.name << ": passed\n";
|
||||
}
|
||||
if (gpuThreadDimensions) {
|
||||
for (const auto& shape : Shapes) {
|
||||
RunCpuFallback(*slot, shape);
|
||||
std::cout << shape.name << ", CPU fallback: passed\n";
|
||||
}
|
||||
}
|
||||
std::cout << "indirect thread dimension tests passed\n";
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
try {
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
} catch (const std::exception& shutdownError) {
|
||||
std::cerr << shutdownError.what() << '\n';
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
@@ -195,6 +196,11 @@ void RunTests(const AgcDriver::VulkanDevice& device) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
#ifdef _WIN32
|
||||
if (std::getenv("APS5_TEMPLATE_AUDIT_MS") == nullptr) _putenv_s("APS5_TEMPLATE_AUDIT_MS", "0");
|
||||
#else
|
||||
setenv("APS5_TEMPLATE_AUDIT_MS", "0", 0);
|
||||
#endif
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,6 +18,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -275,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),
|
||||
@@ -480,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");
|
||||
@@ -872,8 +1020,10 @@ int main(int argc, char** argv) {
|
||||
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 {
|
||||
@@ -912,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);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@@ -95,6 +96,10 @@ void MatchTests() {
|
||||
Require(!MatchSgprPatternFill(packet, Code, data, noGroup), "a dispatch without the group id matched");
|
||||
}
|
||||
|
||||
alignas(8) std::uint32_t Finished = 0;
|
||||
std::uint32_t Submitted = 0;
|
||||
constexpr std::uint32_t MaxResubmits = 16;
|
||||
|
||||
void Submit(const std::vector<std::uint32_t>& data, std::uint32_t groups) {
|
||||
std::vector<std::uint32_t> words;
|
||||
const auto registers = [&](std::uint32_t first, std::span<const std::uint32_t> values) {
|
||||
@@ -111,6 +116,8 @@ void Submit(const std::vector<std::uint32_t>& data, std::uint32_t groups) {
|
||||
registers(0x213, resource);
|
||||
registers(0x240, data);
|
||||
words.insert(words.end(), {0xc0031500u, groups, 1, 1, 0x41u});
|
||||
const auto finished = reinterpret_cast<std::uintptr_t>(&Finished);
|
||||
words.insert(words.end(), {0xc0033700u, 0x00100200u, static_cast<std::uint32_t>(finished), static_cast<std::uint32_t>(finished >> 32u), ++Submitted});
|
||||
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
|
||||
AgcDriver::Submit(&packet, 0);
|
||||
}
|
||||
@@ -118,32 +125,43 @@ void Submit(const std::vector<std::uint32_t>& data, std::uint32_t groups) {
|
||||
struct KeyRead {
|
||||
DccKeys keys;
|
||||
bool waited;
|
||||
std::uint32_t resubmits;
|
||||
};
|
||||
|
||||
std::uint64_t lastNote = 0;
|
||||
|
||||
KeyRead ReadAfterWrite(std::uint64_t surfaceBytes = SurfaceBytes) {
|
||||
void Settle() {
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
AgcDriver::GuestMemory::FlushGpuWrites(KeysAddress(), KeyBytes);
|
||||
}
|
||||
|
||||
KeyRead SubmitAndRead(const std::vector<std::uint32_t>& data, std::uint32_t groups, std::uint64_t surfaceBytes = SurfaceBytes, bool retireFirst = false) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
|
||||
Submit(data, groups);
|
||||
if (retireFirst) Settle();
|
||||
std::uint32_t resubmits = 0;
|
||||
for (;;) {
|
||||
bool retired = false;
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
auto* recorder = AgcDriver::Graphics::Recorder::Active();
|
||||
if (const auto noted = recorder != nullptr ? recorder->NewestWriteNote(KeysAddress(), KeyBytes) : 0; noted > lastNote) {
|
||||
lastNote = noted;
|
||||
const auto keys = AgcDriver::Graphics::CurrentDccKeys(KeysAddress(), surfaceBytes);
|
||||
return {keys, !recorder->PendingWriteOverlaps(KeysAddress(), KeyBytes)};
|
||||
return {keys, !recorder->PendingWriteOverlaps(KeysAddress(), KeyBytes), resubmits};
|
||||
}
|
||||
retired = std::atomic_ref<std::uint32_t>(Finished).load() == Submitted;
|
||||
}
|
||||
if (retired) {
|
||||
Require(resubmits < MaxResubmits, "the dispatch completed " + std::to_string(resubmits + 1) + " times without a pending write over the keys");
|
||||
++resubmits;
|
||||
Submit(data, groups);
|
||||
}
|
||||
Require(std::chrono::steady_clock::now() < deadline, "the submitted dispatch never became a pending write over the keys");
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
void Settle() {
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
AgcDriver::GuestMemory::FlushGpuWrites(KeysAddress(), KeyBytes);
|
||||
}
|
||||
|
||||
void RequireStored(std::uint32_t count, const Pattern& pattern, std::uint32_t before, const std::string& what) {
|
||||
Settle();
|
||||
for (std::uint32_t i = 0; i < Dwords; ++i) {
|
||||
@@ -159,38 +177,37 @@ void ExecuteTests() {
|
||||
const std::array<std::pair<std::uint8_t, DccKeys>, 6> codes{{{0x00, DccKeys::Clear0000}, {0x40, DccKeys::Clear0001}, {0x80, DccKeys::Clear1110}, {0xc0, DccKeys::Clear1111}, {0x20, DccKeys::ClearRegister}, {0xff, DccKeys::Uncompressed}}};
|
||||
for (const auto& [key, keys] : codes) {
|
||||
Settle();
|
||||
Submit(UserData(Dwords, Dwords, Uniform(key)), Dwords / 64);
|
||||
const auto read = ReadAfterWrite();
|
||||
const auto read = SubmitAndRead(UserData(Dwords, Dwords, Uniform(key)), Dwords / 64);
|
||||
Require(read.keys == keys && !read.waited, Describe("a pending key code fill by the kernel", read));
|
||||
RequireStored(Dwords, Uniform(key), 0, "the kernel's key code fill");
|
||||
}
|
||||
|
||||
Submit(UserData(Dwords, Dwords, Uniform(0x5a)), Dwords / 64);
|
||||
const auto other = ReadAfterWrite();
|
||||
Settle();
|
||||
const auto retired = SubmitAndRead(UserData(Dwords, Dwords, Uniform(0x40)), Dwords / 64, SurfaceBytes, true);
|
||||
Require(retired.resubmits > 0, "a fill that retired before the read was not submitted again");
|
||||
Require(retired.keys == DccKeys::Clear0001 && !retired.waited, Describe("a pending 0001 fill submitted again after it retired before the read", retired));
|
||||
RequireStored(Dwords, Uniform(0x40), 0, "the 0001 fill submitted again");
|
||||
|
||||
const auto other = SubmitAndRead(UserData(Dwords, Dwords, Uniform(0x5a)), Dwords / 64);
|
||||
Require(other.keys == DccKeys::Mixed && other.waited, Describe("a pending fill of a byte that is not a key code", other));
|
||||
RequireStored(Dwords, Uniform(0x5a), 0, "the kernel's fill of another byte");
|
||||
|
||||
const Pattern several{0x40404040u, 0x40404040u, 0x40404040u, 0x80808080u};
|
||||
Submit(UserData(Dwords, Dwords, several), Dwords / 64);
|
||||
const auto mixed = ReadAfterWrite();
|
||||
const auto mixed = SubmitAndRead(UserData(Dwords, Dwords, several), Dwords / 64);
|
||||
Require(mixed.keys == DccKeys::Mixed && mixed.waited, Describe("a pending fill of several key codes", mixed));
|
||||
RequireStored(Dwords, several, 0, "the kernel's fill of several key codes");
|
||||
|
||||
Submit(UserData(Dwords, Dwords, Uniform(0x40)), Dwords / 64);
|
||||
const auto first = ReadAfterWrite();
|
||||
const auto first = SubmitAndRead(UserData(Dwords, Dwords, Uniform(0x40)), Dwords / 64);
|
||||
Require(first.keys == DccKeys::Clear0001 && !first.waited, Describe("a pending 0001 fill by the kernel", first));
|
||||
Submit(UserData(Dwords, Dwords / 2, Uniform(0x5a)), Dwords / 64);
|
||||
const auto later = ReadAfterWrite();
|
||||
const auto later = SubmitAndRead(UserData(Dwords, Dwords / 2, Uniform(0x5a)), Dwords / 64);
|
||||
Require(later.keys == DccKeys::Mixed && later.waited, Describe("a later writer over half of a pending 0001 fill", later));
|
||||
Settle();
|
||||
for (std::uint32_t i = 0; i < Dwords; ++i) Require(Keys[i] == (i < Dwords / 2 ? 0x5a5a5a5au : 0x40404040u), "the later writer over the 0001 fill: dword " + std::to_string(i) + " holds " + std::to_string(Keys[i]));
|
||||
|
||||
Submit(UserData(Dwords, Dwords / 2, Uniform(0xc0)), Dwords / 64);
|
||||
const auto half = ReadAfterWrite();
|
||||
const auto half = SubmitAndRead(UserData(Dwords, Dwords / 2, Uniform(0xc0)), Dwords / 64);
|
||||
Require(half.keys == DccKeys::Mixed, Describe("a pending 1111 fill of half the keys over 0001 keys", half));
|
||||
Settle();
|
||||
Submit(UserData(Dwords, Dwords / 2, Uniform(0xc0)), Dwords / 64);
|
||||
const auto inside = ReadAfterWrite(SurfaceBytes / 2);
|
||||
const auto inside = SubmitAndRead(UserData(Dwords, Dwords / 2, Uniform(0xc0)), Dwords / 64, SurfaceBytes / 2);
|
||||
Require(inside.keys == DccKeys::Clear1111, Describe("the half of the keys a pending 1111 fill covers", inside));
|
||||
Settle();
|
||||
for (std::uint32_t i = 0; i < Dwords; ++i) Require(Keys[i] == (i < Dwords / 2 ? 0xc0c0c0c0u : 0x40404040u), "the 1111 fill of half the keys: dword " + std::to_string(i) + " holds " + std::to_string(Keys[i]));
|
||||
|
||||
@@ -118,6 +118,14 @@ alignas(256) constexpr std::array<std::uint32_t, 30> DomainCode{
|
||||
0x7e2802f2, 0xf80008cf, 0x14131110, 0xf800020f, 0x14131214, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 37> DomainComputedPoint{
|
||||
0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xbf8c3f70, 0x7c0214ff,
|
||||
0x7f7fffff, 0xd5010002, 0x01a90280, 0xd7460002, 0x04050902, 0xe0341010, 0x04030c02, 0xe0341020,
|
||||
0x04030e02, 0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505, 0x3e201906, 0x3e201d02, 0x10221705,
|
||||
0x3e221b06, 0x3e221f02, 0xd5510012, 0x040a0d05, 0x102424ff, 0x40400000, 0x7e260280, 0x7e2802f2,
|
||||
0xf80008cf, 0x14131110, 0xf800020f, 0x14131214, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 31> DomainLoweredInterior{
|
||||
0xf8000941, 0x00000000, 0x34020e84, 0x16020283, 0xe0341000, 0x04030a01, 0xe0341010, 0x04030c01,
|
||||
0xe0341020, 0x04030e01, 0xbf8c3f70, 0x08040af2, 0x08040d02, 0x10201505, 0x3e201906, 0x3e201d02,
|
||||
@@ -590,12 +598,12 @@ std::uint32_t CoveredPixels(std::uint32_t patch, bool interior) {
|
||||
return count;
|
||||
}
|
||||
|
||||
void RequirePatches(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target, std::span<const std::uint32_t> hull, const std::string& program, const PatchDraw& patches = Triangles, std::span<const std::uint32_t> local = LocalCode) {
|
||||
void RequirePatches(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target, std::span<const std::uint32_t> hull, const std::string& program, const PatchDraw& patches = Triangles, std::span<const std::uint32_t> local = LocalCode, std::span<const std::uint32_t> domain = DomainCode) {
|
||||
std::uint32_t cursor = 0;
|
||||
const Stages stages{
|
||||
Compile(target, ShaderStage::Local, local, cursor, patches),
|
||||
Compile(target, ShaderStage::TessellationControl, hull, cursor, patches),
|
||||
Compile(target, ShaderStage::TessellationEvaluation, DomainCode, cursor, patches),
|
||||
Compile(target, ShaderStage::TessellationEvaluation, domain, cursor, patches),
|
||||
Compile(target, ShaderStage::Fragment, PixelCode, cursor, patches),
|
||||
};
|
||||
const auto center = Row(-0.5f + 1.0f / 3.0f);
|
||||
@@ -985,6 +993,7 @@ int main() {
|
||||
RequirePatches(*device, target, HullRestoredExecSaved, "hull program saving its restored exec");
|
||||
RequirePatches(*device, target, HullNextInput, "hull program reading the next of 4 input control points of 24 bytes", NextInputTriangles, LocalCodeStride24);
|
||||
RequirePatches(*device, target, HullDynamicIndex, "hull program reading a control point at a computed index");
|
||||
RequirePatches(*device, target, HullCode, "domain program reading control points at a computed index", Triangles, LocalCode, DomainComputedPoint);
|
||||
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});
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t MaxThreads = 64;
|
||||
constexpr std::uint32_t Results = 4;
|
||||
alignas(256) std::array<std::uint32_t, MaxThreads * Results> Output{};
|
||||
alignas(256) std::array<std::uint32_t, 4> Unused{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 22> Code{
|
||||
0x34060084, 0x4a080081, 0xbf068008, 0xbf850001, 0x4a080890, 0xbefc0380, 0x7e028704, 0xe0701000,
|
||||
0x80010103, 0x4a080081, 0xbe890383, 0x7e0a0280, 0xbefc0380, 0x7e0c8704, 0x4a0a0b06, 0x4a080890,
|
||||
0x80898109, 0xbf078009, 0xbf85fff9, 0xe0701004, 0x80010503, 0xbf810000,
|
||||
};
|
||||
|
||||
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::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::uint32_t threads, std::uint32_t overwrite) {
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(9, 0u);
|
||||
const auto input = BufferDescriptor(Unused.data(), static_cast<std::uint32_t>(Unused.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
userData[8] = overwrite;
|
||||
const std::span<const std::uint32_t> code(Code);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check(std::uint32_t waveSize, std::uint32_t threads, std::uint32_t overwrite) {
|
||||
for (std::uint32_t tid = 0; tid < threads; ++tid) {
|
||||
const std::uint32_t* out = &Output[tid * Results];
|
||||
const std::uint32_t written = tid + 1u;
|
||||
const std::uint32_t branch = overwrite != 0u ? written + 16u : written;
|
||||
const std::uint32_t loop = 3u * written + 48u;
|
||||
const auto where = std::string("vector window liveness: wave") + std::to_string(waveSize) + " lane " + std::to_string(tid) + (overwrite != 0u ? " (overwritten before the read)" : " (overwrite branched over)");
|
||||
Require(out[0] == branch, where + ": the window register written before the branch read " + Hex(out[0]) + ", expected " + Hex(branch));
|
||||
Require(out[1] == loop, where + ": the window register carried around the loop read " + Hex(out[1]) + ", expected " + Hex(loop));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
for (const std::uint32_t waveSize : {32u, 64u}) {
|
||||
for (const std::uint32_t overwrite : {0u, 1u}) {
|
||||
Run(*device, waveSize, waveSize, overwrite);
|
||||
Check(waveSize, waveSize, overwrite);
|
||||
}
|
||||
}
|
||||
std::puts("vector window liveness tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def native(path):
|
||||
return Path('\\\\?\\' + str(path)) if os.name == 'nt' else path
|
||||
|
||||
|
||||
def snapshot(directory):
|
||||
return {str(path.relative_to(directory)): None if path.is_dir() else path.read_bytes()
|
||||
for path in directory.rglob('*')}
|
||||
|
||||
|
||||
def main():
|
||||
executable = str(Path(sys.argv[1]).resolve())
|
||||
mode = sys.argv[2]
|
||||
assert mode in ('plain', 'relative', 'extended', 'extended-forward', 'device', 'plain-long', 'extended-long')
|
||||
assert os.name == 'nt' or mode in ('plain', 'relative', 'plain-long')
|
||||
temporary = Path(tempfile.gettempdir()).resolve()
|
||||
sandbox = Path(tempfile.mkdtemp(prefix='aps5-prebuilt-discovery-')).resolve()
|
||||
try:
|
||||
outside = sandbox / 'outside'
|
||||
outside.mkdir()
|
||||
(outside / 'z.pack').write_bytes(b'outside directory canary')
|
||||
(outside / 'sentinel').write_bytes(b'unchanged outside the cache root')
|
||||
original = snapshot(outside)
|
||||
for layout in ('pack', 'loose', 'versioned', 'file', 'empty', 'missing'):
|
||||
root = sandbox / (layout + '-\u00e4\u6f22')
|
||||
if mode.endswith('long'):
|
||||
while len(str(root)) < 270:
|
||||
root /= 'd' * 40
|
||||
assert len(str(root / 'prebuilt')) >= 278
|
||||
root = native(root)
|
||||
root.mkdir(parents=True)
|
||||
plain = str(root)[4:] if os.name == 'nt' else str(root)
|
||||
alias = plain
|
||||
if mode.startswith('extended'):
|
||||
alias = str(root)
|
||||
if mode == 'extended-forward':
|
||||
alias = '\\\\?\\' + plain.replace('\\', '/')
|
||||
elif mode == 'device':
|
||||
alias = '\\\\.\\' + plain
|
||||
elif mode == 'relative':
|
||||
alias = os.path.join('.', os.path.relpath(plain, outside))
|
||||
environment = dict(os.environ, ANYPS5_NO_SHADER_CACHE='0', ANYPS5_SHADER_CACHE_DIR=alias,
|
||||
APS5_NO_PIPELINE_JOURNAL='1')
|
||||
|
||||
def run(action):
|
||||
try:
|
||||
result = subprocess.run([executable, '--' + action + '-prebuilt-discovery', layout], cwd=outside,
|
||||
env=environment, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, errors='replace', timeout=5)
|
||||
except subprocess.TimeoutExpired as error:
|
||||
raise AssertionError('prebuilt discovery did not return within five seconds; child terminated') from error
|
||||
assert snapshot(outside) == original, 'prebuilt discovery changed files outside its root'
|
||||
assert result.returncode == 0, mode + ' ' + layout + ':\n' + result.stdout
|
||||
assert 'prebuilt discovery ' + ('seeded' if action == 'seed' else 'verified') + ' ' + layout in result.stdout
|
||||
|
||||
run('seed')
|
||||
before = snapshot(root)
|
||||
run('verify')
|
||||
assert snapshot(root) == before, 'prebuilt discovery modified its read-only input'
|
||||
print(mode + ': six prebuilt layouts verified; cache and outside trees unchanged')
|
||||
finally:
|
||||
assert sandbox.parent == temporary and sandbox.name.startswith('aps5-prebuilt-discovery-')
|
||||
shutil.rmtree(native(sandbox))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "prx/libkernel/Apr/include/AprCommandBuffer.hpp"
|
||||
#include "prx/libkernel/Time/include/Time.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
@@ -226,18 +225,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 +239,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);
|
||||
int APS5_VABI wcstombs_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit);
|
||||
[[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 +289,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 +395,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 +450,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 +847,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;
|
||||
@@ -992,11 +885,6 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void) {
|
||||
}
|
||||
|
||||
|
||||
int APS5_VABI wcstombs_s_nid_postfix(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit) {
|
||||
std::array<std::uint64_t, 16> state{};
|
||||
return wcsrtombs_s_nid_postfix(result, destination, capacity, &source, limit, state.data());
|
||||
}
|
||||
|
||||
[[noreturn]] void APS5_VABI __cxa_call_unexpected_nid_postfix(void* unwindException) {
|
||||
__cxa_call_terminate_nid_postfix(unwindException);
|
||||
}
|
||||
@@ -1046,6 +934,11 @@ void APS5_VABI sceAmprIgnoreHandlerS(const char* message, void* pointer, int err
|
||||
SonyIgnoreHandlerS(message, pointer, error);
|
||||
}
|
||||
|
||||
APS5_EXPORT("sZLrjx-yEx4", sceAmprWcstombsS);
|
||||
int APS5_VABI sceAmprWcstombsS(std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t* source, std::size_t limit) {
|
||||
return wcstombs_s_nid_postfix(result, destination, capacity, source, limit);
|
||||
}
|
||||
|
||||
APS5_EXPORT("VUzjXknPPBs", sceAmprMbstowcs);
|
||||
std::size_t APS5_VABI sceAmprMbstowcs(std::uint16_t* dest, const char* src, std::size_t size) {
|
||||
return SonyMbstowcs(dest, src, size);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
add_library(libSceAudio3d SHARED EXCLUDE_FROM_ALL Export.cpp)
|
||||
target_include_directories(libSceAudio3d PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(libSceAudio3d PRIVATE libSceAudioOut)
|
||||
configure_windows_unwind(libSceAudio3d)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
@@ -21,4 +22,14 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_audio3d_port PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudio3d>")
|
||||
endif()
|
||||
add_test_executable(guest_audio3d_mixer_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dMixer.cpp)
|
||||
target_include_directories(guest_audio3d_mixer_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_audio3d_mixer_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_audio3d_mixer_tests)
|
||||
add_test(NAME guest_audio3d_mixer COMMAND guest_audio3d_mixer_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_audio3d_mixer PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,23 +1,31 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Mixer.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/PreciseWait.hpp"
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceAudioOutOpen(int user_id, int type, int index, std::uint32_t len, std::uint32_t freq, std::uint32_t param);
|
||||
int APS5_VABI sceAudioOutOutput(int handle, const void* ptr);
|
||||
int APS5_VABI sceAudioOutClose(int handle);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int AUDIO3D_ERROR_INVALID_PORT = static_cast<int>(0x80EA0002);
|
||||
constexpr int AUDIO3D_ERROR_INVALID_OBJECT = static_cast<int>(0x80EA0003);
|
||||
constexpr int AUDIO3D_ERROR_INVALID_PARAMETER = static_cast<int>(0x80EA0004);
|
||||
constexpr int AUDIO3D_ERROR_NOT_SUPPORTED = static_cast<int>(0x80EA0008);
|
||||
constexpr int AUDIO3D_ERROR_OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
|
||||
constexpr int AUDIO3D_ERROR_NOT_READY = static_cast<int>(0x80EA0007);
|
||||
|
||||
@@ -27,24 +35,44 @@ constexpr std::uint32_t AUDIO3D_OBJECT_INVALID = 0xFFFFFFFF;
|
||||
constexpr std::uint32_t AUDIO3D_RATE_48000 = 0;
|
||||
constexpr std::uint32_t AUDIO3D_SAMPLE_RATE = 48000;
|
||||
constexpr std::uint32_t AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2;
|
||||
constexpr int AUDIO3D_AUDIO_OUT_PORT_TYPE = 126;
|
||||
constexpr std::uint32_t AUDIO3D_AUDIO_OUT_FORMAT_STEREO = 1;
|
||||
constexpr std::uint32_t AUDIO3D_BLOCKING_ASYNC = 0;
|
||||
constexpr std::uint32_t AUDIO3D_BLOCKING_SYNC = 1;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_RESET_STATE = 0x20000;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL = 0x10001;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS = 0x10002;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE = 0x10003;
|
||||
constexpr std::uint32_t AUDIO3D_FORMAT_S16 = 0;
|
||||
constexpr std::uint32_t AUDIO3D_FORMAT_FLOAT = 1;
|
||||
constexpr std::uint32_t AUDIO3D_OUTPUT_ROUTE_BOTH = 0;
|
||||
constexpr std::uint32_t AUDIO3D_OUTPUT_ROUTE_HMU_ONLY = 1;
|
||||
constexpr std::uint32_t AUDIO3D_OUTPUT_ROUTE_TV_ONLY = 2;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
struct Audio3dAudioOut {
|
||||
std::size_t buffer_bytes = 0;
|
||||
std::deque<std::vector<std::uint8_t>> pending;
|
||||
};
|
||||
|
||||
struct Audio3dBed {
|
||||
std::vector<std::uint8_t> pcm;
|
||||
std::uint32_t channels = 0;
|
||||
std::uint32_t format = 0;
|
||||
std::uint32_t samples = 0;
|
||||
};
|
||||
|
||||
struct Audio3dPort {
|
||||
bool open = false;
|
||||
std::uint32_t queue_depth = 0;
|
||||
Clock::duration frame_duration{};
|
||||
std::uint32_t advanced = 0;
|
||||
std::deque<Clock::time_point> playing;
|
||||
std::uint32_t granularity = 0;
|
||||
std::uint32_t buffer_mode = 0;
|
||||
std::uint32_t max_objects = 0;
|
||||
std::uint32_t next_object_id = 0;
|
||||
std::set<std::uint32_t> objects;
|
||||
std::deque<Audio3dBed> beds;
|
||||
std::deque<std::vector<std::int16_t>> mixed;
|
||||
int audio_out_handle = 0;
|
||||
std::map<int, Audio3dAudioOut> audio_out_ports;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
@@ -59,9 +87,39 @@ bool PortIsOpen(std::uint32_t port_id) {
|
||||
return port_id == AUDIO3D_PORT_ID && g_port.open;
|
||||
}
|
||||
|
||||
std::uint32_t QueueLevel(Clock::time_point now) {
|
||||
while (!g_port.playing.empty() && g_port.playing.front() <= now) g_port.playing.pop_front();
|
||||
return g_port.advanced + static_cast<std::uint32_t>(g_port.playing.size());
|
||||
std::uint32_t QueueLevel() {
|
||||
return static_cast<std::uint32_t>(g_port.mixed.size());
|
||||
}
|
||||
|
||||
std::size_t AudioOutBufferBytes(std::uint32_t len, std::uint32_t param) {
|
||||
static constexpr std::uint32_t channels[] = {1, 2, 8, 1, 2, 8, 8, 8};
|
||||
static constexpr std::uint32_t sample_size[] = {2, 2, 2, 4, 4, 4, 2, 4};
|
||||
const std::uint32_t format = param & 0xFF;
|
||||
if (format >= std::size(channels)) return 0;
|
||||
return static_cast<std::size_t>(len) * channels[format] * sample_size[format];
|
||||
}
|
||||
|
||||
int DrainAssociatedAudioOuts() {
|
||||
while (true) {
|
||||
int handle = 0;
|
||||
std::vector<std::uint8_t> frame;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
const auto entry = std::find_if(g_port.audio_out_ports.begin(), g_port.audio_out_ports.end(),
|
||||
[](const auto& candidate) { return !candidate.second.pending.empty(); });
|
||||
if (entry == g_port.audio_out_ports.end()) return 0;
|
||||
handle = entry->first;
|
||||
frame = std::move(entry->second.pending.front());
|
||||
entry->second.pending.pop_front();
|
||||
}
|
||||
const int result = sceAudioOutOutput(handle, frame.data());
|
||||
if (result < 0) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
const auto entry = g_port.audio_out_ports.find(handle);
|
||||
if (entry != g_port.audio_out_ports.end()) entry->second.pending.push_front(std::move(frame));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,6 +130,7 @@ void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
|
||||
if (p == nullptr) APS5_INVALID_ARG_EX;
|
||||
constexpr Audio3dOpenParameters defaults{0x20, 256, 0, 512, 2, 2, 0, 0};
|
||||
static_assert(offsetof(Audio3dOpenParameters, num_beds) == 0x20);
|
||||
static_assert(sizeof(Audio3dOpenParameters) == 0x28);
|
||||
std::memcpy(p, &defaults, defaults.size_this);
|
||||
}
|
||||
|
||||
@@ -93,15 +152,40 @@ int APS5_VABI sceAudio3dTerminate() {
|
||||
int APS5_VABI sceAudio3dPortAdvance(uint32_t port_id) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (QueueLevel(Clock::now()) >= g_port.queue_depth) Unsupported(__func__, "queue is full");
|
||||
++g_port.advanced;
|
||||
if (g_port.buffer_mode != AUDIO3D_BUFFER_ADVANCE_AND_PUSH) return AUDIO3D_ERROR_NOT_SUPPORTED;
|
||||
if (QueueLevel() >= g_port.queue_depth) return AUDIO3D_ERROR_NOT_READY;
|
||||
std::vector<std::int16_t> frame;
|
||||
if (g_port.beds.empty()) {
|
||||
Audio3dInternal::MixFrame(nullptr, 0, g_port.granularity, frame);
|
||||
} else {
|
||||
const Audio3dBed& bed = g_port.beds.front();
|
||||
const Audio3dInternal::Source source{
|
||||
bed.pcm.data(),
|
||||
bed.samples,
|
||||
bed.channels,
|
||||
bed.format == AUDIO3D_FORMAT_FLOAT,
|
||||
1.0f,
|
||||
0.0f,
|
||||
};
|
||||
Audio3dInternal::MixFrame(&source, 1, g_port.granularity, frame);
|
||||
g_port.beds.pop_front();
|
||||
}
|
||||
g_port.mixed.push_back(std::move(frame));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortClose(uint32_t port_id) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
g_port = Audio3dPort{};
|
||||
int handle = 0;
|
||||
std::vector<int> associated;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
handle = g_port.audio_out_handle;
|
||||
for (const auto& entry : g_port.audio_out_ports) associated.push_back(entry.first);
|
||||
g_port = Audio3dPort{};
|
||||
}
|
||||
for (const int port : associated) sceAudioOutClose(port);
|
||||
if (handle != 0) sceAudioOutClose(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -109,7 +193,7 @@ int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_leve
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (queue_level == nullptr && queue_available == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
const std::uint32_t level = QueueLevel(Clock::now());
|
||||
const std::uint32_t level = QueueLevel();
|
||||
if (queue_level != nullptr) *queue_level = level;
|
||||
if (queue_available != nullptr) *queue_available = g_port.queue_depth - level;
|
||||
return 0;
|
||||
@@ -164,33 +248,53 @@ int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* param
|
||||
g_port = Audio3dPort{};
|
||||
g_port.open = true;
|
||||
g_port.queue_depth = parameters->queue_depth;
|
||||
g_port.granularity = parameters->granularity;
|
||||
g_port.buffer_mode = parameters->buffer_mode;
|
||||
g_port.max_objects = parameters->max_objects;
|
||||
g_port.frame_duration = std::chrono::duration_cast<Clock::duration>(
|
||||
std::chrono::nanoseconds(std::uint64_t{parameters->granularity} * 1000000000u / AUDIO3D_SAMPLE_RATE));
|
||||
*id = AUDIO3D_PORT_ID;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortPush(uint32_t port_id, uint32_t blocking) {
|
||||
Clock::time_point wait_until;
|
||||
int handle = 0;
|
||||
std::uint32_t granularity = 0;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (blocking != AUDIO3D_BLOCKING_ASYNC && blocking != AUDIO3D_BLOCKING_SYNC) {
|
||||
Unsupported(__func__, "blocking mode " + std::to_string(blocking));
|
||||
}
|
||||
const Clock::time_point now = Clock::now();
|
||||
const std::uint32_t level = QueueLevel(now);
|
||||
Clock::time_point end = g_port.playing.empty() ? now : g_port.playing.back();
|
||||
for (; g_port.advanced != 0; --g_port.advanced) {
|
||||
end += g_port.frame_duration;
|
||||
g_port.playing.push_back(end);
|
||||
}
|
||||
if (blocking == AUDIO3D_BLOCKING_ASYNC || level < g_port.queue_depth) return 0;
|
||||
wait_until = g_port.playing[level - g_port.queue_depth];
|
||||
handle = g_port.audio_out_handle;
|
||||
granularity = g_port.granularity;
|
||||
}
|
||||
PreciseSleepUntil(wait_until);
|
||||
return 0;
|
||||
if (handle == 0) {
|
||||
const int opened = sceAudioOutOpen(AUDIO3D_USER_ID_SYSTEM, AUDIO3D_AUDIO_OUT_PORT_TYPE, 0,
|
||||
granularity, AUDIO3D_SAMPLE_RATE, AUDIO3D_AUDIO_OUT_FORMAT_STEREO);
|
||||
if (opened < 0) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) {
|
||||
sceAudioOutClose(opened);
|
||||
return AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
if (g_port.audio_out_handle == 0) g_port.audio_out_handle = opened;
|
||||
else sceAudioOutClose(opened);
|
||||
}
|
||||
std::deque<std::vector<std::int16_t>> frames;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
handle = g_port.audio_out_handle;
|
||||
const std::size_t target = blocking == AUDIO3D_BLOCKING_SYNC ? 0 : g_port.queue_depth - 1;
|
||||
while (g_port.mixed.size() > target) {
|
||||
frames.push_back(std::move(g_port.mixed.front()));
|
||||
g_port.mixed.pop_front();
|
||||
}
|
||||
}
|
||||
for (const std::vector<std::int16_t>& frame : frames) {
|
||||
const int result = sceAudioOutOutput(handle, frame.data());
|
||||
if (result < 0) return result;
|
||||
}
|
||||
return DrainAssociatedAudioOuts();
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id, const void* attribute, size_t attribute_size) {
|
||||
@@ -242,10 +346,159 @@ int APS5_VABI sceAudio3dObjectSetAttributes(uint32_t port_id, uint32_t object_id
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortFlush(uint32_t port_id) {
|
||||
int handle = 0;
|
||||
std::vector<int> associated;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
g_port.mixed.clear();
|
||||
handle = g_port.audio_out_handle;
|
||||
for (auto& entry : g_port.audio_out_ports) {
|
||||
entry.second.pending.clear();
|
||||
associated.push_back(entry.first);
|
||||
}
|
||||
}
|
||||
for (const int port : associated) sceAudioOutOutput(port, nullptr);
|
||||
if (handle != 0) sceAudioOutOutput(handle, nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dBedWrite2(uint32_t port_id, uint32_t num_channels, uint32_t format, const void* buffer, uint32_t num_samples, uint32_t output_route, bool) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
g_port.advanced = 0;
|
||||
g_port.playing.clear();
|
||||
if (num_channels != 2 && num_channels != 6 && num_channels != 8) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (format > AUDIO3D_FORMAT_FLOAT) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (output_route != AUDIO3D_OUTPUT_ROUTE_BOTH && output_route != AUDIO3D_OUTPUT_ROUTE_HMU_ONLY && output_route != AUDIO3D_OUTPUT_ROUTE_TV_ONLY) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (buffer == nullptr || num_samples == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
const std::size_t sample_size = format == AUDIO3D_FORMAT_S16 ? 2u : 4u;
|
||||
if (reinterpret_cast<std::uintptr_t>(buffer) % sample_size != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
const std::uint32_t samples = std::min(num_samples, g_port.granularity);
|
||||
const std::size_t bytes = static_cast<std::size_t>(samples) * num_channels * sample_size;
|
||||
if (g_port.beds.size() >= g_port.queue_depth) return AUDIO3D_ERROR_NOT_READY;
|
||||
const auto* first = static_cast<const std::uint8_t*>(buffer);
|
||||
Audio3dBed bed;
|
||||
bed.pcm.assign(first, first + bytes);
|
||||
bed.channels = num_channels;
|
||||
bed.format = format;
|
||||
bed.samples = samples;
|
||||
g_port.beds.push_back(std::move(bed));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dBedWrite(uint32_t port_id, uint32_t num_channels, uint32_t format, const void* buffer, uint32_t num_samples) {
|
||||
return sceAudio3dBedWrite2(port_id, num_channels, format, buffer, num_samples, AUDIO3D_OUTPUT_ROUTE_BOTH, false);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dAudioOutOpen(uint32_t port_id, int user_id, std::int32_t type, std::int32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (len != g_port.granularity) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (index != 0 || (param & 0xFF) > 7) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
const int handle = sceAudioOutOpen(user_id, type, index, len, freq, param);
|
||||
if (handle < 0) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
||||
bool registered = false;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (PortIsOpen(port_id)) {
|
||||
g_port.audio_out_ports.emplace(handle, Audio3dAudioOut{AudioOutBufferBytes(len, param)});
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
if (!registered) {
|
||||
sceAudioOutClose(handle);
|
||||
return AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dAudioOutClose(std::int32_t handle) {
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (g_port.audio_out_ports.erase(handle) == 0) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
}
|
||||
sceAudioOutClose(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dAudioOutOutput(std::int32_t handle, const void* ptr) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
const auto port = g_port.audio_out_ports.find(handle);
|
||||
if (port == g_port.audio_out_ports.end()) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (ptr == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (port->second.pending.size() >= g_port.queue_depth) return AUDIO3D_ERROR_NOT_READY;
|
||||
const auto* first = static_cast<const std::uint8_t*>(ptr);
|
||||
port->second.pending.emplace_back(first, first + port->second.buffer_bytes);
|
||||
return static_cast<int>(g_port.granularity);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dAudioOutOutputs(AudioOutOutputParam* param, uint32_t num) {
|
||||
if (param == nullptr || num == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
for (std::uint32_t i = 0; i < num; ++i) {
|
||||
const int result = sceAudio3dAudioOutOutput(param[i].handle, param[i].ptr);
|
||||
if (result < 0) return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dStrError(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortOpenEx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortCreate(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortDestroy(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortGetStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dReportRegisterHandler(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dReportUnregisterHandler(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dCreateSpeakerArray(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dDeleteSpeakerArray(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAUDIO3D_MIXER_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAUDIO3D_MIXER_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Audio3dInternal {
|
||||
|
||||
struct Source {
|
||||
const void* samples;
|
||||
std::uint32_t num_samples;
|
||||
std::uint32_t channels;
|
||||
bool is_float;
|
||||
float gain;
|
||||
float pan;
|
||||
};
|
||||
|
||||
constexpr float kSurroundFold = 0.7071f;
|
||||
constexpr std::uint32_t kChannelL = 0;
|
||||
constexpr std::uint32_t kChannelR = 1;
|
||||
constexpr std::uint32_t kChannelCenter = 2;
|
||||
[[maybe_unused]] constexpr std::uint32_t kChannelLfe = 3;
|
||||
constexpr std::uint32_t kChannelLs = 4;
|
||||
constexpr std::uint32_t kChannelRs = 5;
|
||||
constexpr std::uint32_t kChannelLrs = 6;
|
||||
constexpr std::uint32_t kChannelRrs = 7;
|
||||
|
||||
inline float ReadSample(const Source& source, std::uint32_t frame, std::uint32_t channel) {
|
||||
const std::size_t index = static_cast<std::size_t>(frame) * source.channels + channel;
|
||||
if (source.is_float) return static_cast<const float*>(source.samples)[index];
|
||||
return static_cast<float>(static_cast<const std::int16_t*>(source.samples)[index]) / 32768.0f;
|
||||
}
|
||||
|
||||
inline void MixFrame(const Source* sources, std::size_t count, std::uint32_t granularity, std::vector<std::int16_t>& out) {
|
||||
std::vector<float> mix(static_cast<std::size_t>(granularity) * 2, 0.0f);
|
||||
for (std::size_t source_index = 0; source_index < count; ++source_index) {
|
||||
const Source& src = sources[source_index];
|
||||
if (src.samples == nullptr || src.num_samples == 0) continue;
|
||||
const float pan = std::clamp(src.pan, -1.0f, 1.0f);
|
||||
const float gl = (pan <= 0.0f ? 1.0f : 1.0f - pan) * src.gain;
|
||||
const float gr = (pan >= 0.0f ? 1.0f : 1.0f + pan) * src.gain;
|
||||
for (std::uint32_t frame = 0; frame < granularity && frame < src.num_samples; ++frame) {
|
||||
const std::size_t left_index = static_cast<std::size_t>(frame) * 2;
|
||||
float left = ReadSample(src, frame, kChannelL);
|
||||
float right = src.channels == 1 ? left : ReadSample(src, frame, kChannelR);
|
||||
if (src.channels > kChannelCenter) {
|
||||
const float center = ReadSample(src, frame, kChannelCenter) * kSurroundFold;
|
||||
left += center;
|
||||
right += center;
|
||||
}
|
||||
if (src.channels > kChannelLs) left += ReadSample(src, frame, kChannelLs) * kSurroundFold;
|
||||
if (src.channels > kChannelRs) right += ReadSample(src, frame, kChannelRs) * kSurroundFold;
|
||||
if (src.channels > kChannelLrs) left += ReadSample(src, frame, kChannelLrs) * kSurroundFold;
|
||||
if (src.channels > kChannelRrs) right += ReadSample(src, frame, kChannelRrs) * kSurroundFold;
|
||||
mix[left_index] += left * gl;
|
||||
mix[left_index + 1] += right * gr;
|
||||
}
|
||||
}
|
||||
out.resize(static_cast<std::size_t>(granularity) * 2);
|
||||
for (std::size_t i = 0; i < out.size(); ++i) {
|
||||
out[i] = static_cast<std::int16_t>(std::lround(std::clamp(mix[i], -1.0f, 1.0f) * 32767.0f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -208,4 +208,164 @@ int32_t APS5_VABI sceAudioInGetRerouteCount(int32_t handle) {
|
||||
return find(handle) ? 0 : AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInDeviceIdOpen(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInDeviceIdAsyncOpen(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetUsbGain(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInIsSharedDevice(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInDeviceIdHqOpen(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInDeviceHqOpen(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetConnections(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetPortConnections(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetPortStatuses(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInGetHandleStatusInfo(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInOpenEx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInHqOpenEx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInDeviceOpenEx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInChangeAppModuleState(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetSparkParam(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetSparkSideTone(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetCompressorPreGain(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetNoiseGateThreshold(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetMicLevel(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetDeviceMuteStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetDevConnection(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInCountPorts(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetUserMute(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInVmicCreate(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInVmicDestroy(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInVmicWrite(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInVmicGetStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInVmicSetMute(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetMode(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInSetMode2(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInExtCtrl(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioInInputs(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,9 +24,16 @@ constexpr std::uint8_t STYLE_FRAME_FLAG_SLANT = 0x02;
|
||||
constexpr std::uint8_t STYLE_FRAME_FLAG_WEIGHT = 0x04;
|
||||
constexpr float POINTS_PER_INCH = 72.0f;
|
||||
|
||||
struct FallbackFace {
|
||||
std::shared_ptr<const std::vector<unsigned char>> data;
|
||||
FT_Face face = nullptr;
|
||||
std::uint32_t subFontIndex = 0;
|
||||
};
|
||||
|
||||
struct FontState {
|
||||
std::shared_ptr<const std::vector<unsigned char>> faceData;
|
||||
FT_Face face = nullptr;
|
||||
std::vector<FallbackFace> fallbacks;
|
||||
float scaleW = 16.0f;
|
||||
float scaleH = 16.0f;
|
||||
std::map<int, int> scriptLanguages;
|
||||
@@ -90,11 +97,15 @@ FontState* TryGetState(FontHandle handle);
|
||||
FontState& ResetState(FontHandle handle);
|
||||
void RemoveState(FontHandle handle);
|
||||
void LoadStateFace(FontState& state, std::shared_ptr<const std::vector<unsigned char>> bytes, std::uint32_t subFontIndex);
|
||||
void AddStateFallbackFace(FontState& state, std::shared_ptr<const std::vector<unsigned char>> bytes, std::uint32_t subFontIndex);
|
||||
FT_Face FallbackFaceForCode(const FontState& state, std::uint32_t code, FT_UInt* glyphIndex);
|
||||
FT_Face FaceForCode(const FontState& state, std::uint32_t code, FT_UInt* glyphIndex);
|
||||
|
||||
bool IsSystemFontSet(std::uint32_t fontSetType);
|
||||
std::vector<SystemFontFile> SystemFontCandidates(std::uint32_t fontSetType);
|
||||
std::filesystem::path SystemFontDirectory();
|
||||
std::optional<SystemFontFile> FindSystemFontFile(std::uint32_t fontSetType);
|
||||
std::vector<SystemFontFile> FindSystemFontFallbackFiles(std::uint32_t fontSetType);
|
||||
|
||||
void TrackGeneratedGlyph(FontGlyph glyph);
|
||||
bool ForgetGeneratedGlyph(FontGlyph glyph);
|
||||
|
||||
@@ -404,10 +404,10 @@ int Font::GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMe
|
||||
float scaleH = 0.0f;
|
||||
const int scaleRc = StyleStateGetScalePixel(style, &scaleW, &scaleH);
|
||||
if (scaleRc != SCE_FONT_OK) return finish(scaleRc);
|
||||
const FT_Face face = state->face;
|
||||
if (!face) return finish(SCE_FONT_ERROR_NO_SUPPORT_FUNCTION);
|
||||
const FT_UInt glyphIndex = ResolveGlyphIndexWithFallback(face, code);
|
||||
if (glyphIndex == 0) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
if (!state->face) return finish(SCE_FONT_ERROR_NO_SUPPORT_FUNCTION);
|
||||
FT_UInt glyphIndex = 0;
|
||||
const FT_Face face = FaceForCode(*state, code, &glyphIndex);
|
||||
if (!face) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
const auto charW = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(scaleW * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(scaleH * 64.0f));
|
||||
FT_F26Dot6 usedW = charW;
|
||||
@@ -475,19 +475,23 @@ int Font::RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRe
|
||||
obj->shift_units_y = 0;
|
||||
obj->layout_seed_pair = 0;
|
||||
StyleStateGetScalePixel(style, &obj->scale_x_0x50, &obj->scale_y_0x54);
|
||||
float outScaleX = 0.0f;
|
||||
float outScaleY = 0.0f;
|
||||
int rc;
|
||||
if (style->scale_unit == 0) {
|
||||
rc = driver->set_char_default_dpi(obj, style->scale_w, style->scale_h, &outScaleX, &outScaleY);
|
||||
} else {
|
||||
rc = driver->set_char_with_dpi(obj, style->dpi_x ? style->dpi_x : 0x48, style->dpi_y ? style->dpi_y : 0x48, style->scale_w, style->scale_h, &outScaleX, &outScaleY);
|
||||
}
|
||||
if (rc != SCE_FONT_OK) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
const auto setCharSize = [&](FontObj* target) {
|
||||
float outScaleX = 0.0f;
|
||||
float outScaleY = 0.0f;
|
||||
if (style->scale_unit == 0) return driver->set_char_default_dpi(target, style->scale_w, style->scale_h, &outScaleX, &outScaleY);
|
||||
return driver->set_char_with_dpi(target, style->dpi_x ? style->dpi_x : 0x48, style->dpi_y ? style->dpi_y : 0x48, style->scale_w, style->scale_h, &outScaleX, &outScaleY);
|
||||
};
|
||||
if (setCharSize(obj) != SCE_FONT_OK) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
std::uint32_t glyphIndex = 0;
|
||||
rc = driver->glyph_index(obj, code, &glyphIndex);
|
||||
if (rc != SCE_FONT_OK || glyphIndex == 0) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
return finish(RenderGlyphIndexToSurface(*obj, glyphIndex, surface, x, y, metrics, result));
|
||||
if (driver->glyph_index(obj, code, &glyphIndex) == SCE_FONT_OK && glyphIndex != 0) return finish(RenderGlyphIndexToSurface(*obj, glyphIndex, surface, x, y, metrics, result));
|
||||
const FontState* state = TryGetState(handle);
|
||||
FT_UInt fallbackIndex = 0;
|
||||
const FT_Face fallbackFace = state ? FallbackFaceForCode(*state, code, &fallbackIndex) : nullptr;
|
||||
if (!fallbackFace) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
FontObj fallback = *obj;
|
||||
fallback.ft_face = fallbackFace;
|
||||
if (setCharSize(&fallback) != SCE_FONT_OK) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
||||
return finish(RenderGlyphIndexToSurface(fallback, fallbackIndex, surface, x, y, metrics, result));
|
||||
}
|
||||
|
||||
bool Font::BuildTrueOutline(GeneratedGlyph& glyph) {
|
||||
@@ -495,9 +499,9 @@ bool Font::BuildTrueOutline(GeneratedGlyph& glyph) {
|
||||
if (!state || !state->face) return false;
|
||||
const auto* font = GetNativeFont(glyph.owner);
|
||||
if (font->magic != HANDLE_MAGIC) return false;
|
||||
const FT_Face face = state->face;
|
||||
const FT_UInt glyphIndex = FT_Get_Char_Index(face, glyph.codepoint);
|
||||
if (glyphIndex == 0) return false;
|
||||
FT_UInt glyphIndex = 0;
|
||||
const FT_Face face = FaceForCode(*state, glyph.codepoint, &glyphIndex);
|
||||
if (!face) return false;
|
||||
const auto charW = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(glyph.glyph.scale_x * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(glyph.glyph.base_scale * 64.0f));
|
||||
if (FT_Set_Char_Size(face, charW, charH, 72, 72) != 0) return false;
|
||||
|
||||
@@ -400,6 +400,7 @@ int APS5_VABI sceFontOpenFontInstance(FontHandle fontHandle, FontHandle setupFon
|
||||
targetState.scaleW = sourceState->scaleW;
|
||||
targetState.scaleH = sourceState->scaleH;
|
||||
if (sourceState->faceData && !sourceState->faceData->empty()) LoadStateFace(targetState, sourceState->faceData, subFontIndex);
|
||||
for (const FallbackFace& fallback : sourceState->fallbacks) AddStateFallbackFace(targetState, fallback.data, fallback.subFontIndex);
|
||||
}
|
||||
if (pFontHandle) *pFontHandle = target;
|
||||
return SCE_FONT_OK;
|
||||
@@ -441,8 +442,13 @@ int APS5_VABI sceFontOpenFontSet(FontLibrary library, std::uint32_t fontSetType,
|
||||
if (openRc == SCE_FONT_ERROR_FS_OPEN_FAILED || openRc == SCE_FONT_ERROR_NO_SUPPORT_FORMAT) return SCE_FONT_ERROR_FONT_OPEN_FAILED;
|
||||
if (openRc != SCE_FONT_OK) return openRc;
|
||||
GetNativeFont(*pFontHandle)->open_info.fontset_flags = fontSetType;
|
||||
const std::vector<SystemFontFile> fallbackFiles = FindSystemFontFallbackFiles(fontSetType);
|
||||
for (const SystemFontFile& fallback : fallbackFiles) APS5_LOG_OUT("fontSetType=0x%08X fallback=%s", fontSetType, fallback.path.string().c_str());
|
||||
FontState& state = ResetState(*pFontHandle);
|
||||
if (auto bytes = SystemFontBytes(file->path)) LoadStateFace(state, std::move(bytes), file->subFontIndex);
|
||||
for (const SystemFontFile& fallback : fallbackFiles) {
|
||||
if (auto bytes = SystemFontBytes(fallback.path)) AddStateFallbackFace(state, std::move(bytes), fallback.subFontIndex);
|
||||
}
|
||||
return SCE_FONT_OK;
|
||||
}
|
||||
ReleaseLibraryLock(lib, libraryLock);
|
||||
|
||||
@@ -81,10 +81,11 @@ int CachedColumnOffset(FontHandle handle, FontHandleNative* font, float& offset)
|
||||
return rc;
|
||||
}
|
||||
|
||||
void LoadKerning(FT_Face face, float scaleW, float scaleH, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) {
|
||||
const FT_UInt previousGlyph = face ? FT_Get_Char_Index(face, preCode) : 0;
|
||||
const FT_UInt glyph = face ? FT_Get_Char_Index(face, code) : 0;
|
||||
if (!face || previousGlyph == 0 || glyph == 0) return;
|
||||
void LoadKerning(const FontState& state, float scaleW, float scaleH, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) {
|
||||
FT_UInt previousGlyph = 0;
|
||||
FT_UInt glyph = 0;
|
||||
const FT_Face face = FaceForCode(state, preCode, &previousGlyph);
|
||||
if (!face || FaceForCode(state, code, &glyph) != face) return;
|
||||
const auto charW = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(scaleW * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(scaleH * 64.0f));
|
||||
FT_Set_Char_Size(face, charW, charH, 72, 72);
|
||||
@@ -187,7 +188,7 @@ int APS5_VABI sceFontGetKerning(FontHandle fontHandle, std::uint32_t preCode, st
|
||||
if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE;
|
||||
*kerning = {};
|
||||
const FontState* state = TryGetState(fontHandle);
|
||||
if (state) LoadKerning(state->face, state->scaleW, state->scaleH, preCode, code, kerning);
|
||||
if (state) LoadKerning(*state, state->scaleW, state->scaleH, preCode, code, kerning);
|
||||
ReleaseFontLock(font, fontLock);
|
||||
return SCE_FONT_OK;
|
||||
}
|
||||
@@ -209,7 +210,7 @@ int APS5_VABI sceFontGetRenderScaledKerning(FontHandle fontHandle, std::uint32_t
|
||||
float scaleH = 0.0f;
|
||||
rc = StyleStateGetScalePixel(&font->cached_style.state, &scaleW, &scaleH);
|
||||
const FontState* state = TryGetState(fontHandle);
|
||||
if (rc == SCE_FONT_OK && state) LoadKerning(state->face, scaleW, scaleH, preCode, code, kerning);
|
||||
if (rc == SCE_FONT_OK && state) LoadKerning(*state, scaleW, scaleH, preCode, code, kerning);
|
||||
}
|
||||
ReleaseCachedStyleLock(font, cachedLock);
|
||||
ReleaseFontLock(font, fontLock);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <unordered_set>
|
||||
|
||||
#include "prx/libc/include/PreciseWait.hpp"
|
||||
#include "prx/libSceFont/include/FontFreeType.hpp"
|
||||
#include "prx/libSceFont/include/FontInternal.hpp"
|
||||
|
||||
namespace {
|
||||
@@ -54,10 +55,9 @@ void ReleaseWord(std::uint32_t& word, std::uint32_t previous) {
|
||||
}
|
||||
|
||||
Font::FontState::~FontState() {
|
||||
if (face) {
|
||||
std::lock_guard lock(faceMutex);
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
std::lock_guard lock(faceMutex);
|
||||
if (face) FT_Done_Face(face);
|
||||
for (const FallbackFace& fallback : fallbacks) FT_Done_Face(fallback.face);
|
||||
}
|
||||
|
||||
void Font::Backoff() {
|
||||
@@ -267,6 +267,36 @@ void Font::LoadStateFace(FontState& state, std::shared_ptr<const std::vector<uns
|
||||
state.face = face;
|
||||
}
|
||||
|
||||
void Font::AddStateFallbackFace(FontState& state, std::shared_ptr<const std::vector<unsigned char>> bytes, std::uint32_t subFontIndex) {
|
||||
std::lock_guard lock(faceMutex);
|
||||
const FT_Library library = HostFreeTypeLibrary();
|
||||
if (!library || !bytes || bytes->empty()) return;
|
||||
FT_Face face = nullptr;
|
||||
if (FT_New_Memory_Face(library, bytes->data(), static_cast<FT_Long>(bytes->size()), static_cast<FT_Long>(subFontIndex), &face) != 0) return;
|
||||
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
||||
state.fallbacks.push_back({std::move(bytes), face, subFontIndex});
|
||||
}
|
||||
|
||||
FT_Face Font::FallbackFaceForCode(const FontState& state, std::uint32_t code, FT_UInt* glyphIndex) {
|
||||
for (const FallbackFace& fallback : state.fallbacks) {
|
||||
const FT_UInt index = ResolveGlyphIndexWithFallback(fallback.face, code);
|
||||
if (index == 0) continue;
|
||||
*glyphIndex = index;
|
||||
return fallback.face;
|
||||
}
|
||||
*glyphIndex = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FT_Face Font::FaceForCode(const FontState& state, std::uint32_t code, FT_UInt* glyphIndex) {
|
||||
const FT_UInt index = ResolveGlyphIndexWithFallback(state.face, code);
|
||||
if (index != 0) {
|
||||
*glyphIndex = index;
|
||||
return state.face;
|
||||
}
|
||||
return FallbackFaceForCode(state, code, glyphIndex);
|
||||
}
|
||||
|
||||
void Font::TrackGeneratedGlyph(FontGlyph glyph) {
|
||||
std::lock_guard lock(glyphMutex);
|
||||
generatedGlyphs.insert(glyph);
|
||||
|
||||
@@ -37,7 +37,7 @@ constexpr std::array<std::uint32_t, 120> SYSTEM_FONT_SET_TYPES = {
|
||||
0x1A0825C3u, 0x1A0825C7u, 0x1A083553u, 0x1A083557u, 0x1A0835D3u, 0x1A0835D7u,
|
||||
};
|
||||
|
||||
enum class Family { Latin, LatinItalic, Typewriter, Vietnamese, Thai, Japanese, ChineseGb };
|
||||
enum class Family { Latin, LatinItalic, Typewriter, Vietnamese, Thai, Japanese, ChineseGb, Hangul, Arabic };
|
||||
|
||||
struct FamilyFiles {
|
||||
std::array<std::string_view, 4> system;
|
||||
@@ -45,7 +45,7 @@ struct FamilyFiles {
|
||||
std::uint32_t substituteSubFont;
|
||||
};
|
||||
|
||||
constexpr std::array<FamilyFiles, 7> FAMILY_FILES = {{
|
||||
constexpr std::array<FamilyFiles, 9> FAMILY_FILES = {{
|
||||
{{"SST-Light.otf", "SST-Roman.otf", "SST-Medium.otf", "SST-Bold.otf"},
|
||||
{"NotoSans-Light.ttf", "NotoSans-Regular.ttf", "NotoSans-Medium.ttf", "NotoSans-Bold.ttf"}, 0},
|
||||
{{"SST-LightItalic.otf", "SST-Italic.otf", "SST-MediumItalic.otf", "SST-BoldItalic.otf"},
|
||||
@@ -60,6 +60,10 @@ constexpr std::array<FamilyFiles, 7> FAMILY_FILES = {{
|
||||
{"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 0},
|
||||
{{"DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf"},
|
||||
{"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 2},
|
||||
{{"SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Medium.otf", "SCEPS4Yoongd-Bold.otf"},
|
||||
{"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 1},
|
||||
{{"SSTArabic-Light.otf", "SSTArabic-Roman.otf", "SSTArabic-Medium.otf", "SSTArabic-Bold.otf"},
|
||||
{"NotoSansArabic-Light.ttf", "NotoSansArabic-Regular.ttf", "NotoSansArabic-Medium.ttf", "NotoSansArabic-Bold.ttf"}, 0},
|
||||
}};
|
||||
|
||||
Family FamilyOf(std::uint32_t fontSetType) {
|
||||
@@ -87,6 +91,37 @@ std::size_t WeightOf(std::uint32_t fontSetType) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Family> FallbackFamilies(std::uint32_t fontSetType) {
|
||||
const std::uint32_t scripts = (fontSetType >> 8) & 0xFFu;
|
||||
const Family primary = FamilyOf(fontSetType);
|
||||
std::vector<Family> families;
|
||||
const auto add = [&](bool wanted, Family family) {
|
||||
if (wanted && family != primary && std::find(families.begin(), families.end(), family) == families.end()) families.push_back(family);
|
||||
};
|
||||
add((fontSetType & 0x10u) != 0, Family::Vietnamese);
|
||||
add((scripts & 0x10u) != 0, Family::Thai);
|
||||
add((scripts & 0x88u) == 0x88u, Family::ChineseGb);
|
||||
add((scripts & 0x04u) != 0, Family::Japanese);
|
||||
add((scripts & 0x80u) != 0, Family::ChineseGb);
|
||||
add((scripts & 0x20u) != 0, Family::Hangul);
|
||||
add((fontSetType & 0x80u) != 0, Family::Arabic);
|
||||
return families;
|
||||
}
|
||||
|
||||
std::vector<Font::SystemFontFile> FamilyCandidates(Family family, std::size_t weight) {
|
||||
const FamilyFiles& files = FAMILY_FILES[static_cast<std::size_t>(family)];
|
||||
return {{std::filesystem::path(files.system[weight]), 0}, {std::filesystem::path(files.substitute[weight]), files.substituteSubFont}};
|
||||
}
|
||||
|
||||
std::optional<Font::SystemFontFile> FirstExisting(const std::filesystem::path& directory, const std::vector<Font::SystemFontFile>& candidates) {
|
||||
for (const Font::SystemFontFile& candidate : candidates) {
|
||||
std::error_code error;
|
||||
const std::filesystem::path path = directory / candidate.path;
|
||||
if (std::filesystem::is_regular_file(path, error)) return Font::SystemFontFile{path, candidate.subFontIndex};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::filesystem::path ExecutableDirectory() {
|
||||
#ifdef _WIN32
|
||||
wchar_t module[MAX_PATH];
|
||||
@@ -115,9 +150,7 @@ bool Font::IsSystemFontSet(std::uint32_t fontSetType) {
|
||||
|
||||
std::vector<Font::SystemFontFile> Font::SystemFontCandidates(std::uint32_t fontSetType) {
|
||||
if (!IsSystemFontSet(fontSetType)) return {};
|
||||
const FamilyFiles& files = FAMILY_FILES[static_cast<std::size_t>(FamilyOf(fontSetType))];
|
||||
const std::size_t weight = WeightOf(fontSetType);
|
||||
return {{std::filesystem::path(files.system[weight]), 0}, {std::filesystem::path(files.substitute[weight]), files.substituteSubFont}};
|
||||
return FamilyCandidates(FamilyOf(fontSetType), WeightOf(fontSetType));
|
||||
}
|
||||
|
||||
std::filesystem::path Font::SystemFontDirectory() {
|
||||
@@ -132,11 +165,22 @@ std::filesystem::path Font::SystemFontDirectory() {
|
||||
std::optional<Font::SystemFontFile> Font::FindSystemFontFile(std::uint32_t fontSetType) {
|
||||
const std::vector<SystemFontFile> candidates = SystemFontCandidates(fontSetType);
|
||||
if (candidates.empty()) return std::nullopt;
|
||||
return FirstExisting(SystemFontDirectory(), candidates);
|
||||
}
|
||||
|
||||
std::vector<Font::SystemFontFile> Font::FindSystemFontFallbackFiles(std::uint32_t fontSetType) {
|
||||
if (!IsSystemFontSet(fontSetType)) return {};
|
||||
const std::filesystem::path directory = SystemFontDirectory();
|
||||
for (const SystemFontFile& candidate : candidates) {
|
||||
std::error_code error;
|
||||
const std::filesystem::path path = directory / candidate.path;
|
||||
if (std::filesystem::is_regular_file(path, error)) return SystemFontFile{path, candidate.subFontIndex};
|
||||
const std::size_t weight = WeightOf(fontSetType);
|
||||
std::vector<SystemFontFile> files;
|
||||
const std::optional<SystemFontFile> primary = FirstExisting(directory, SystemFontCandidates(fontSetType));
|
||||
const auto opened = [&](const SystemFontFile& file) {
|
||||
const auto same = [&](const SystemFontFile& other) { return other.path == file.path && other.subFontIndex == file.subFontIndex; };
|
||||
return (primary && same(*primary)) || std::any_of(files.begin(), files.end(), same);
|
||||
};
|
||||
for (const Family family : FallbackFamilies(fontSetType)) {
|
||||
auto file = FirstExisting(directory, FamilyCandidates(family, weight));
|
||||
if (file && !opened(*file)) files.push_back(std::move(*file));
|
||||
}
|
||||
return std::nullopt;
|
||||
return files;
|
||||
}
|
||||
@@ -40,4 +40,9 @@ int APS5_VABI sceGameLiveStreamingGetProgramInfo() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
+451
-107
@@ -1,4 +1,5 @@
|
||||
#include "Hmd2.hpp"
|
||||
#include "prx/libc/include/general/ExportMacros.hpp"
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libSceHmd/Hmd.hpp"
|
||||
@@ -18,6 +19,12 @@ std::mutex stateMutex;
|
||||
bool initialized = false;
|
||||
std::int32_t openHandle = 0;
|
||||
|
||||
std::int32_t BeforeInitialize(std::int32_t answer, const char* name) {
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (initialized) NotImplemented_nid_no_patch(name);
|
||||
return answer;
|
||||
}
|
||||
|
||||
std::uint32_t CompiledSdkVersion() {
|
||||
static const auto* processParameters = static_cast<const std::byte*>(ApplicationProcessParameters_nid_no_patch());
|
||||
std::uint32_t version;
|
||||
@@ -217,86 +224,6 @@ std::int32_t APS5_VABI sceHmd2SetVibration(std::int32_t handle, std::uint32_t le
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeGetCalibrationData() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeGetCalibrationDataMaxSize() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionClearUserEventEnd() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionClearUserEventStart() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionGetCurrentOutput() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionSetUserEventEnd() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionSetUserEventStart() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeActivateGazeTracking() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeGetDeviceStatus() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeGetUserCalibrationStatus() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeInsertRawDataWithHandle() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeSetEnabledEye() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeSetUserCalibrationData() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeSetUserCalibrationPoint() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeStartUserCalibration() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeStopUserCalibration() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionSeeThroughDraw() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
@@ -322,26 +249,6 @@ int APS5_VABI sceHmd2SeeThroughCallbackGetPoseDataFunc() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeObjectSelectionGetSelectedObject() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeObjectSelectionInitialize() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeObjectSelectionQueryMemory() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2GazeObjectSelectionTerminate() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2ReprojectionClear() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
@@ -367,15 +274,179 @@ int APS5_VABI sceHmd2RpeorjectionSetParam() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2SetFieldOfView() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceHmd2SetIpdOffset() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeZero() {
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("vDgpdS2N9Rw", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("Ri35gKTZpvE", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("9oyW8puiKeI", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("1HZNE6jhBnQ", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("cdwAKBZLXjs", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("Yub1MF58jfY", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("JYXAuQFnPyw", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("VuPKqCu3wzw", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("GSThagVyW5U", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("J4qE5N937MY", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("9KnNj-dyASg", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("Qta2bkstbFk", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("5ZcObnDRBXE", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("AvPNn0qx4WQ", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("jp0apqNGgi8", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("-kZT7X7OMg0", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("1ewLtng-SVk", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("B0b8DeIUGes", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("HclLg4Oj5yw", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("VbEvrQXsZyY", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("bB6MKZi5UCQ", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("OXQ1EDf02XU", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("IAxfMTvFm0M", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("qTJtAtaLeno", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("j7Q16UX6l34", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("pmmNM06rLqU", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("zdQfyDK9Ods", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("aPRwp9GnljM", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("MjY1gBSaed0", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("evAMjJYHqW4", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("r93FLEZ3BBk", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("IdYURdEdNFo", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("CVcL2Cu2AEI", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("NmKIfVxr2s0", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("KeDLkkp62Gg", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("+aadu6-ra+A", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("FgnTiwwGSzk", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("PWAUwOTHbbo", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("WzaEApHnsP8", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("dXzQI8QVY7Y", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("cCvFKGqRh1k", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("uj4-2VEAFPU", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("sIaGStI-iLM", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("JzwpZOemDO8", Hmd2BeforeInitializeZero);
|
||||
APS5_EXPORT("IsC4R4bZFVU", Hmd2BeforeInitializeZero);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeNotInitialized() {
|
||||
return BeforeInitialize(Hmd2::NotInitialized, __func__);
|
||||
}
|
||||
APS5_EXPORT("sXaPEPY-fLA", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("3F5inHhKzXg", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("sJAiB-6p55U", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("E8hDydLjsSs", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("xEQkSb2WJ7Y", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("aaP4dnR4q-0", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("lEU0TeUoGwI", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("cFVyjHBpUUU", Hmd2BeforeInitializeNotInitialized);
|
||||
APS5_EXPORT("zROtb46jTj4", Hmd2BeforeInitializeNotInitialized);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeError0004() {
|
||||
return BeforeInitialize(Hmd2::Error0004, __func__);
|
||||
}
|
||||
APS5_EXPORT("kAMsjYHBbNQ", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("5m73zmLpsQ0", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("EPeH4xuQ2XQ", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("TJ8lyRsjFY4", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("-XQVcLnyX68", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("giQwQoHhYYA", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("zNc9IkgawMI", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("-tnG5UJPvAc", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("FPhFmG+jXuQ", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("CKlkvNngofQ", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("yL1CZaciFWE", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("qlzgyG3s0RY", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("kTIDrDLzMv0", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("9Gee0ef8a+U", Hmd2BeforeInitializeError0004);
|
||||
APS5_EXPORT("ecZVjQdwmBI", Hmd2BeforeInitializeError0004);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeParameterNull() {
|
||||
return BeforeInitialize(Hmd2::ParameterNull, __func__);
|
||||
}
|
||||
APS5_EXPORT("MNtiJTDOPAU", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("RRy9WmqZfgU", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("uobR4m9lgwc", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("-XPk5KWBYdo", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("J44idh9izZM", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("Ps0wAvYaa1E", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("kLnMR8oQ1MY", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("kMz4rbBxvjo", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("DIU1sFRRaiE", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("ZQvU4vDzZGQ", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("TJpyGGP9YEY", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("Dih+Odk+RC0", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("oaqdSJw+HVY", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("m35gtHuanXU", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("CTM7QPLJF9Q", Hmd2BeforeInitializeParameterNull);
|
||||
APS5_EXPORT("+rExHeC9lP0", Hmd2BeforeInitializeParameterNull);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeParameterInvalid() {
|
||||
return BeforeInitialize(Hmd2::ParameterInvalid, __func__);
|
||||
}
|
||||
APS5_EXPORT("bBh5bCp9coo", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("G3PNDE2VjfY", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("nScCT0OPU0o", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("6Q0Ng7OTN9o", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("-fJKh4SnFR0", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("jUA2s-l0npY", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("+9XhyAIJVj4", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("eBGK-o6E4RQ", Hmd2BeforeInitializeParameterInvalid);
|
||||
APS5_EXPORT("MTuFeL9184E", Hmd2BeforeInitializeParameterInvalid);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeReprojectionNotInitialized() {
|
||||
return BeforeInitialize(Hmd2::ReprojectionNotInitialized, __func__);
|
||||
}
|
||||
APS5_EXPORT("d3SEIYTP6wc", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("EVECbTvBezo", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("ciusPdJKjSU", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("DIwqTkcZiJo", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("64ERZWVFBTE", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("z0+0gXUJWs8", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("rjAd4nCY4DM", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("Acyg4vTdGm0", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("aGOWaEeg04I", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
APS5_EXPORT("9up+EPhw1No", Hmd2BeforeInitializeReprojectionNotInitialized);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeError002C() {
|
||||
return BeforeInitialize(Hmd2::Error002C, __func__);
|
||||
}
|
||||
APS5_EXPORT("V1kLQmP-17U", Hmd2BeforeInitializeError002C);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeError1002() {
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("2LVD1EC5SYE", Hmd2BeforeInitializeError1002);
|
||||
APS5_EXPORT("Bf87AL6zWxU", Hmd2BeforeInitializeError1002);
|
||||
APS5_EXPORT("XKt7w1MODFY", Hmd2BeforeInitializeError1002);
|
||||
APS5_EXPORT("Y5s22M-qUDA", Hmd2BeforeInitializeError1002);
|
||||
APS5_EXPORT("JJKY-BtQJLg", Hmd2BeforeInitializeError1002);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeError1003() {
|
||||
return BeforeInitialize(Hmd2::Error1003, __func__);
|
||||
}
|
||||
APS5_EXPORT("9kj9vw8UShQ", Hmd2BeforeInitializeError1003);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeMinusOne() {
|
||||
return BeforeInitialize(-1, __func__);
|
||||
}
|
||||
APS5_EXPORT("EAkv8Mqt33E", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("Rv7bkmKvmz0", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("1oPZizn0SNA", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("TwCpE-nNrlo", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("jkqAeQfdtxw", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("+l0RQph81Z8", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("qsmAtGw1Ncc", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("uKhwYoMsIsA", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("WNdaigr94dI", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("P6S5ZVWf-H0", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("sun6NWE1sdg", Hmd2BeforeInitializeMinusOne);
|
||||
APS5_EXPORT("--TFsJYXo04", Hmd2BeforeInitializeMinusOne);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeValue0400() {
|
||||
return BeforeInitialize(0x400, __func__);
|
||||
}
|
||||
APS5_EXPORT("BWqcwjCozzI", Hmd2BeforeInitializeValue0400);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2BeforeInitializeValue20000() {
|
||||
return BeforeInitialize(0x20000, __func__);
|
||||
}
|
||||
APS5_EXPORT("PRz7TdBGUI0", Hmd2BeforeInitializeValue20000);
|
||||
|
||||
int APS5_VABI sceHmd2InternalSetPortStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
@@ -472,4 +543,277 @@ int APS5_VABI sceHmd2InternalImageCloseBySlot(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenError0004(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error0004, __func__);
|
||||
}
|
||||
APS5_EXPORT("9WeZr4kYfzk", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("N+5h1hCOFAg", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("w2vEs2y5ZmE", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("L5gat1pe0y0", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("4GnLHR9NJ1w", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("AHk7nGSzef0", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("ncjX9d3mc0M", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("l63QYAUuogc", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("mrcBySCdXSo", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("ieBra+td8sU", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("s+rzBnewgGs", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("Nfcm0GBdzZI", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("Tc9rasWfrm4", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("ckTbKTX4KZg", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("D7576PUbUjk", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("tTJ04SrBT8U", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("sDP7hZ-P6OI", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("J+eVNbN9ndY", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("kozvHBNyxf0", Hmd2CheckFirstThenError0004);
|
||||
APS5_EXPORT("638Tmg5y6U4", Hmd2CheckFirstThenError0004);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenZero(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("8ETtFtp5zqY", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("CWPkBm3EXQA", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("R62liQQIAaA", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("-bcWLHsdzo0", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("JngJzgNbu8U", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("xNBSz8xrGdk", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("yUVPfN-jRIU", Hmd2CheckFirstThenZero);
|
||||
APS5_EXPORT("BKPR3RZ5aO4", Hmd2CheckFirstThenZero);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenError1002(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("IG4y+drWAWw", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("rhYUml9vppE", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("6574xHS+jZk", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("mXzaY6xVoxg", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("6af-HIZQQn4", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("6qWtdTHQsTk", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("pUm8t8xWhFg", Hmd2CheckFirstThenError1002);
|
||||
APS5_EXPORT("A0XKI-WjxK8", Hmd2CheckFirstThenError1002);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenMinusOne(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(-1, __func__);
|
||||
}
|
||||
APS5_EXPORT("A9s2j8OioPQ", Hmd2CheckFirstThenMinusOne);
|
||||
APS5_EXPORT("YIgtmrL+HLY", Hmd2CheckFirstThenMinusOne);
|
||||
APS5_EXPORT("Ng8zEkGxDNs", Hmd2CheckFirstThenMinusOne);
|
||||
APS5_EXPORT("EtslTnqO1xU", Hmd2CheckFirstThenMinusOne);
|
||||
APS5_EXPORT("hfrnRTryKQQ", Hmd2CheckFirstThenMinusOne);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstTwoThenParameterInvalid(const void* a0, const void* a1) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::ParameterInvalid, __func__);
|
||||
}
|
||||
APS5_EXPORT("PPX68fNToFM", Hmd2CheckFirstTwoThenParameterInvalid);
|
||||
APS5_EXPORT("LxPIfjHaY4c", Hmd2CheckFirstTwoThenParameterInvalid);
|
||||
APS5_EXPORT("w8gBTTdg+po", Hmd2CheckFirstTwoThenParameterInvalid);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckSecondThenError002C(const void* a0, const void* a1) {
|
||||
(void)a0;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error002C, __func__);
|
||||
}
|
||||
APS5_EXPORT("tWJPX2ycRMM", Hmd2CheckSecondThenError002C);
|
||||
APS5_EXPORT("5UMB9PJA70o", Hmd2CheckSecondThenError002C);
|
||||
APS5_EXPORT("xQs62qCk1EI", Hmd2CheckSecondThenError002C);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenParameterInvalid(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::ParameterInvalid, __func__);
|
||||
}
|
||||
APS5_EXPORT("MMc+bSlqkzk", Hmd2CheckFirstThenParameterInvalid);
|
||||
APS5_EXPORT("NpZHgvSYgEA", Hmd2CheckFirstThenParameterInvalid);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckAllSixThenZero(const void* a0, const void* a1, const void* a2, const void* a3, const void* a4, const void* a5) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a2 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a3 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a4 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a5 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("z-serf9vsb0", Hmd2CheckAllSixThenZero);
|
||||
APS5_EXPORT("IRMUFu+uG5s", Hmd2CheckAllSixThenZero);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckSecondThenParameterInvalid(const void* a0, const void* a1) {
|
||||
(void)a0;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::ParameterInvalid, __func__);
|
||||
}
|
||||
APS5_EXPORT("72U64E+0z6U", Hmd2CheckSecondThenParameterInvalid);
|
||||
APS5_EXPORT("LCQzi5ILGrY", Hmd2CheckSecondThenParameterInvalid);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenError800d0203(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error800d0203, __func__);
|
||||
}
|
||||
APS5_EXPORT("Im0qL0j9Ubg", Hmd2CheckFirstThenError800d0203);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenError1003(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1003, __func__);
|
||||
}
|
||||
APS5_EXPORT("ng6eI8uA0h4", Hmd2CheckFirstThenError1003);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstThenError1010(const void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1010, __func__);
|
||||
}
|
||||
APS5_EXPORT("mPPGgjyJrCI", Hmd2CheckFirstThenError1010);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstTwoThenError1002(const void* a0, const void* a1) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("ZupCJmV5BDI", Hmd2CheckFirstTwoThenError1002);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstTwoThenError1003(const void* a0, const void* a1) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1003, __func__);
|
||||
}
|
||||
APS5_EXPORT("mAQZwOX1IHU", Hmd2CheckFirstTwoThenError1003);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstAndThirdThenZero(const void* a0, const void* a1, const void* a2) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
(void)a1;
|
||||
if (a2 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("oacDkQkinLo", Hmd2CheckFirstAndThirdThenZero);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckFirstAndThirdThenError1003(const void* a0, const void* a1, const void* a2) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
(void)a1;
|
||||
if (a2 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1003, __func__);
|
||||
}
|
||||
APS5_EXPORT("zn7PHGzzEyI", Hmd2CheckFirstAndThirdThenError1003);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckSecondThenInvalidHandle(const void* a0, const void* a1) {
|
||||
(void)a0;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::InvalidHandle, __func__);
|
||||
}
|
||||
APS5_EXPORT("wEHILc7TP04", Hmd2CheckSecondThenInvalidHandle);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2CheckSecondThenError1002(const void* a0, const void* a1) {
|
||||
(void)a0;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("W-ZIlUXcrNA", Hmd2CheckSecondThenError1002);
|
||||
|
||||
|
||||
std::int32_t APS5_VABI Hmd2WriteStateTwo(void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
static constexpr std::uint8_t bytes[] = {0x02, 0x00, 0x00, 0x00, 0x00};
|
||||
std::memcpy(a0, bytes, sizeof bytes);
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("omimgG5itSs", Hmd2WriteStateTwo);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2WriteSize0x2000Align0x100(std::uint64_t* a0, std::uint64_t* a1) {
|
||||
if (a0 == nullptr || a1 == nullptr) return Hmd2::ParameterNull;
|
||||
*a0 = 0x2000;
|
||||
*a1 = 0x100;
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("6PeHcAaKYg8", Hmd2WriteSize0x2000Align0x100);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2WriteFourConstants(void* a0) {
|
||||
if (a0 == nullptr) return Hmd2::ParameterNull;
|
||||
static constexpr std::uint8_t bytes[] = {0xe1, 0xf0, 0x74, 0x3c, 0xd8, 0xf0, 0x74, 0x3c, 0x1f, 0xf1, 0x74, 0x3c, 0xd8, 0xf0, 0x74, 0x3c};
|
||||
std::memcpy(a0, bytes, sizeof bytes);
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("yur+xPdWPAg", Hmd2WriteFourConstants);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2WriteSixHalvesThenInvalidHandle(std::uint64_t a0, void* a1) {
|
||||
(void)a0;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
static constexpr std::uint8_t bytes[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f};
|
||||
std::memcpy(a1, bytes, sizeof bytes);
|
||||
return BeforeInitialize(Hmd2::InvalidHandle, __func__);
|
||||
}
|
||||
APS5_EXPORT("RnieDDp5ge4", Hmd2WriteSixHalvesThenInvalidHandle);
|
||||
APS5_EXPORT("jM3NEJA11yw", Hmd2WriteSixHalvesThenInvalidHandle);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2WriteTwelveHalvesThenInvalidHandle(std::uint64_t a0, void* a1) {
|
||||
(void)a0;
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
static constexpr std::uint8_t bytes[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
std::memcpy(a1, bytes, sizeof bytes);
|
||||
return BeforeInitialize(Hmd2::InvalidHandle, __func__);
|
||||
}
|
||||
APS5_EXPORT("b0H-n-tUk0U", Hmd2WriteTwelveHalvesThenInvalidHandle);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo3CheckSecondThirdThenZero(std::uint64_t a0, const void* a1, const void* a2) {
|
||||
if (a1 == nullptr || a2 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a0 > 3) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("MXhloJ6Iw3w", Hmd2SlotUpTo3CheckSecondThirdThenZero);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo1ThenError1002(std::uint64_t a0) {
|
||||
if (a0 > 1) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("19wrIIiqFgM", Hmd2SlotUpTo1ThenError1002);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo1CheckSecondThenError1002(std::uint64_t a0, const void* a1) {
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a0 > 1) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("Z389vAm47Ys", Hmd2SlotUpTo1CheckSecondThenError1002);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo1ThenMinusOne(std::uint64_t a0) {
|
||||
if (a0 > 1) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(-1, __func__);
|
||||
}
|
||||
APS5_EXPORT("cDcrvEG8j8k", Hmd2SlotUpTo1ThenMinusOne);
|
||||
APS5_EXPORT("tuqJO6O6cb4", Hmd2SlotUpTo1ThenMinusOne);
|
||||
APS5_EXPORT("fd4fu1cziaI", Hmd2SlotUpTo1ThenMinusOne);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo2ThenMinusOne(std::uint64_t a0) {
|
||||
if (a0 > 2) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(-1, __func__);
|
||||
}
|
||||
APS5_EXPORT("UxMg7iL4nRs", Hmd2SlotUpTo2ThenMinusOne);
|
||||
APS5_EXPORT("K1GELwlaaGc", Hmd2SlotUpTo2ThenMinusOne);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo2ThenError1002(std::uint64_t a0) {
|
||||
if (a0 > 2) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(Hmd2::Error1002, __func__);
|
||||
}
|
||||
APS5_EXPORT("RFn39VCHjGY", Hmd2SlotUpTo2ThenError1002);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo2ThenError1003(std::uint64_t a0) {
|
||||
if (a0 > 2) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(Hmd2::Error1003, __func__);
|
||||
}
|
||||
APS5_EXPORT("FRoSjceDv10", Hmd2SlotUpTo2ThenError1003);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2SlotUpTo2CheckSecondThenError002C(std::uint64_t a0, const void* a1) {
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a0 > 2) return Hmd2::ParameterInvalid;
|
||||
return BeforeInitialize(Hmd2::Error002C, __func__);
|
||||
}
|
||||
APS5_EXPORT("wQ44gBwBw44", Hmd2SlotUpTo2CheckSecondThenError002C);
|
||||
|
||||
std::int32_t APS5_VABI Hmd2IndexZeroCheckSecondThenZero(std::uint64_t a0, const void* a1) {
|
||||
if (a1 == nullptr) return Hmd2::ParameterNull;
|
||||
if (a0 != 0) return Hmd2::Error100F;
|
||||
return BeforeInitialize(0, __func__);
|
||||
}
|
||||
APS5_EXPORT("wiv8+7ogrq0", Hmd2IndexZeroCheckSecondThenZero);
|
||||
|
||||
}
|
||||
@@ -14,6 +14,14 @@ inline constexpr std::int32_t InvalidHandle = static_cast<std::int32_t>(0x8a7200
|
||||
inline constexpr std::int32_t AlreadyOpened = static_cast<std::int32_t>(0x8a720006);
|
||||
inline constexpr std::int32_t ParameterNull = static_cast<std::int32_t>(0x8a720008);
|
||||
inline constexpr std::int32_t ParameterInvalid = static_cast<std::int32_t>(0x8a720009);
|
||||
inline constexpr std::int32_t Error0004 = static_cast<std::int32_t>(0x8a720004);
|
||||
inline constexpr std::int32_t ReprojectionNotInitialized = static_cast<std::int32_t>(0x8a72000b);
|
||||
inline constexpr std::int32_t Error002C = static_cast<std::int32_t>(0x8a72002c);
|
||||
inline constexpr std::int32_t Error1002 = static_cast<std::int32_t>(0x8a721002);
|
||||
inline constexpr std::int32_t Error1003 = static_cast<std::int32_t>(0x8a721003);
|
||||
inline constexpr std::int32_t Error1010 = static_cast<std::int32_t>(0x8a721010);
|
||||
inline constexpr std::int32_t Error800d0203 = static_cast<std::int32_t>(0x800d0203);
|
||||
inline constexpr std::int32_t Error100F = static_cast<std::int32_t>(0x8a72100f);
|
||||
|
||||
inline constexpr std::uint32_t UserIdSdkVersion = 0x07000000;
|
||||
|
||||
|
||||
@@ -111,9 +111,10 @@ int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param
|
||||
if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE;
|
||||
(void)context;
|
||||
if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (!param->jpeg_mem_addr || !param->image_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (!param->jpeg_mem_addr || !param->image_mem_addr || (reinterpret_cast<std::uintptr_t>(param->image_mem_addr) & 3u) != 0) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (param->jpeg_mem_size == 0 || param->image_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (param->alpha_value > 0xFF) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
const std::uint32_t downScale = requireDownScale(__func__, param->decode_mode, param->down_scale);
|
||||
|
||||
const std::span<const std::uint8_t> jpeg = toBytes(param->jpeg_mem_addr, param->jpeg_mem_size);
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
add_library(libSceNet SHARED EXCLUDE_FROM_ALL Export.cpp)
|
||||
target_include_directories(libSceNet PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(libSceNet PRIVATE libSceNetCtl)
|
||||
configure_windows_unwind(libSceNet)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(net_dontwait_tests ${LIBS_INCLUDE_DIR}/tests/NetDontWait.cpp)
|
||||
target_include_directories(net_dontwait_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(net_dontwait_tests PRIVATE libSceNet libkernel libc)
|
||||
configure_windows_unwind(net_dontwait_tests)
|
||||
foreach(api IN ITEMS send sendto sendmsg recv recvfrom recvmsg duplex blocking)
|
||||
add_test(NAME net_dontwait_${api} COMMAND net_dontwait_tests ${api})
|
||||
set_tests_properties(net_dontwait_${api} PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(net_dontwait_${api} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNet>")
|
||||
endif()
|
||||
endforeach()
|
||||
add_test_executable(net_blocking_send_tests ${LIBS_INCLUDE_DIR}/tests/NetBlockingSend.cpp)
|
||||
target_include_directories(net_blocking_send_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(net_blocking_send_tests PRIVATE libkernel libc libSceNetCtl)
|
||||
configure_windows_unwind(net_blocking_send_tests)
|
||||
if(WIN32)
|
||||
target_link_libraries(net_blocking_send_tests PRIVATE ws2_32)
|
||||
endif()
|
||||
foreach(api IN ITEMS send sendto sendmsg)
|
||||
add_test(NAME net_dontwait_blocking_${api} COMMAND net_blocking_send_tests ${api})
|
||||
set_tests_properties(net_dontwait_blocking_${api} PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(net_dontwait_blocking_${api} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
endforeach()
|
||||
add_test_executable(guest_datagram_receive_tests ${LIBS_INCLUDE_DIR}/tests/GuestDatagramReceive.cpp)
|
||||
target_include_directories(guest_datagram_receive_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_datagram_receive_tests PRIVATE libSceNet libkernel libc)
|
||||
|
||||
+148
-111
@@ -39,6 +39,7 @@
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestHeap.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketError.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include "prx/libkernel/Time/include/Time.hpp"
|
||||
@@ -120,32 +121,7 @@ bool initialize_sockets() {
|
||||
return result == 0;
|
||||
}
|
||||
void close_socket(NativeSocket socket) { closesocket(socket); }
|
||||
int net_error(int code) {
|
||||
switch (code) {
|
||||
case WSAEWOULDBLOCK: return NET_EAGAIN;
|
||||
case WSAEINPROGRESS: return NET_EINPROGRESS;
|
||||
case WSAEALREADY: return NET_EALREADY;
|
||||
case WSAEADDRINUSE: return NET_EADDRINUSE;
|
||||
case WSAEADDRNOTAVAIL: return NET_EADDRNOTAVAIL;
|
||||
case WSAEAFNOSUPPORT: return NET_EAFNOSUPPORT;
|
||||
case WSAENETUNREACH: return NET_ENETUNREACH;
|
||||
case WSAEHOSTUNREACH: return NET_EHOSTUNREACH;
|
||||
case WSAECONNABORTED: return NET_ECONNABORTED;
|
||||
case WSAENOBUFS: return NET_ENOBUFS;
|
||||
case WSAEISCONN: return NET_EISCONN;
|
||||
case WSAENOTCONN: return NET_ENOTCONN;
|
||||
case WSAEACCES: return NET_EACCES;
|
||||
case WSAECONNRESET: return 54;
|
||||
case WSAETIMEDOUT: return NET_ETIMEDOUT;
|
||||
case WSAECONNREFUSED: return NET_ECONNREFUSED;
|
||||
case WSAEMSGSIZE: return NET_EMSGSIZE;
|
||||
case WSAEINTR: return 4;
|
||||
case WSAEINVAL: return NET_EINVAL;
|
||||
case WSAENOTSOCK: return NET_ENOTSOCK;
|
||||
case WSAESHUTDOWN: return 32;
|
||||
default: return 5;
|
||||
}
|
||||
}
|
||||
int net_error(int code) { return GuestSockets::GuestErrorFromHost(code); }
|
||||
int native_error() { return net_error(WSAGetLastError()); }
|
||||
#else
|
||||
using NativeSocket = int;
|
||||
@@ -154,41 +130,25 @@ constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
|
||||
constexpr int NATIVE_SEND_FLAGS = MSG_NOSIGNAL;
|
||||
bool initialize_sockets() { return true; }
|
||||
void close_socket(NativeSocket socket) { ::close(socket); }
|
||||
int net_error(int code) {
|
||||
switch (code) {
|
||||
case EAGAIN: return NET_EAGAIN;
|
||||
#if EWOULDBLOCK != EAGAIN
|
||||
case EWOULDBLOCK: return NET_EAGAIN;
|
||||
#endif
|
||||
case EINPROGRESS: return NET_EINPROGRESS;
|
||||
case EALREADY: return NET_EALREADY;
|
||||
case EADDRINUSE: return NET_EADDRINUSE;
|
||||
case EADDRNOTAVAIL: return NET_EADDRNOTAVAIL;
|
||||
case EAFNOSUPPORT: return NET_EAFNOSUPPORT;
|
||||
case ENETUNREACH: return NET_ENETUNREACH;
|
||||
case EHOSTUNREACH: return NET_EHOSTUNREACH;
|
||||
case ECONNABORTED: return NET_ECONNABORTED;
|
||||
case ENOBUFS: return NET_ENOBUFS;
|
||||
case EISCONN: return NET_EISCONN;
|
||||
case ENOTCONN: return NET_ENOTCONN;
|
||||
case EACCES: return NET_EACCES;
|
||||
case ECONNRESET: return 54;
|
||||
case ETIMEDOUT: return NET_ETIMEDOUT;
|
||||
case ECONNREFUSED: return NET_ECONNREFUSED;
|
||||
case EMSGSIZE: return NET_EMSGSIZE;
|
||||
case EINTR: return 4;
|
||||
case EINVAL: return NET_EINVAL;
|
||||
case ENOTSOCK: return NET_ENOTSOCK;
|
||||
case EPIPE: return 32;
|
||||
default: return 5;
|
||||
}
|
||||
}
|
||||
int net_error(int code) { return GuestSockets::GuestErrorFromHost(code); }
|
||||
int native_error() { return net_error(errno); }
|
||||
#endif
|
||||
|
||||
struct NativeSocketHandle {
|
||||
#ifdef _WIN32
|
||||
int TransferSocketForKernel(KernelSocketPoll::NativeOwner* owner, bool send, void* buffer, int length);
|
||||
#endif
|
||||
|
||||
struct NativeSocketHandle : KernelSocketPoll::NativeOwner {
|
||||
NativeSocket value;
|
||||
explicit NativeSocketHandle(NativeSocket socket) : value(socket) {}
|
||||
const bool stream;
|
||||
std::atomic<bool> nonblock{false};
|
||||
std::atomic<int> receiveTimeout{0};
|
||||
std::atomic<int> sendTimeout{0};
|
||||
NativeSocketHandle(NativeSocket socket, bool stream) : value(socket), stream(stream) {
|
||||
#ifdef _WIN32
|
||||
transfer = &TransferSocketForKernel;
|
||||
#endif
|
||||
}
|
||||
~NativeSocketHandle() {
|
||||
if (value != INVALID_NATIVE_SOCKET) close_socket(value);
|
||||
}
|
||||
@@ -253,13 +213,10 @@ struct Sock {
|
||||
int family = 0;
|
||||
int type = 0;
|
||||
std::shared_ptr<NativeSocketHandle> native;
|
||||
bool nonblock = false;
|
||||
bool bound = false;
|
||||
bool listening = false;
|
||||
bool aborted = false;
|
||||
bool read_shut = false;
|
||||
int rcv_timeout_us = 0;
|
||||
int snd_timeout_us = 0;
|
||||
int reported_error = 0;
|
||||
int snd_buffer = 0;
|
||||
int rcv_buffer = 0;
|
||||
@@ -349,6 +306,7 @@ int* errno_slot() {
|
||||
}
|
||||
|
||||
extern "C" int* APS5_VABI __error_nid_postfix();
|
||||
extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity);
|
||||
|
||||
void set_errno(int err) {
|
||||
*errno_slot() = err;
|
||||
@@ -393,8 +351,8 @@ int set_kernel_timeout(int descriptor, int optname, const void* value, std::uint
|
||||
const auto it = g_socks.find(descriptor);
|
||||
if (it != g_socks.end()) {
|
||||
const int stored = static_cast<int>(std::min<std::int64_t>(microseconds, INT_MAX));
|
||||
if (optname == NET_SO_RCVTIMEO) it->second.rcv_timeout_us = stored;
|
||||
else it->second.snd_timeout_us = stored;
|
||||
if (optname == NET_SO_RCVTIMEO) it->second.native->receiveTimeout = stored;
|
||||
else it->second.native->sendTimeout = stored;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -482,7 +440,7 @@ std::int64_t message_length(const NetMsghdr* message) {
|
||||
|
||||
int receive_error(const Sock& socket) {
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() == WSAETIMEDOUT && socket.type != NET_SOCK_STREAM && socket.rcv_timeout_us > 0) return NET_EAGAIN;
|
||||
if (WSAGetLastError() == WSAETIMEDOUT && socket.type != NET_SOCK_STREAM && socket.native->receiveTimeout > 0) return NET_EAGAIN;
|
||||
#else
|
||||
(void)socket;
|
||||
#endif
|
||||
@@ -493,21 +451,60 @@ bool message_flags_supported(int flags, int accepted) {
|
||||
return (flags & ~accepted) == 0;
|
||||
}
|
||||
|
||||
bool would_block(const Sock& socket, int flags, bool send) {
|
||||
template <typename TOperation>
|
||||
auto native_transfer(const NativeSocketHandle& socket, int flags, bool send, TOperation operation, bool timed = true, int length = 0) {
|
||||
#ifdef _WIN32
|
||||
if ((flags & NET_MSG_DONTWAIT) == 0) return false;
|
||||
WSAPOLLFD entry{};
|
||||
entry.fd = socket.native->value;
|
||||
entry.events = send ? POLLWRNORM : POLLRDNORM;
|
||||
return WSAPoll(&entry, 1, 0) == 0;
|
||||
using Result = decltype(operation(0));
|
||||
const bool nonblocking = (flags & NET_MSG_DONTWAIT) != 0 || socket.nonblock.load();
|
||||
const int completeLength = send && socket.stream && !nonblocking ? length : 0;
|
||||
const int timeout = timed ? (send ? socket.sendTimeout.load() : socket.receiveTimeout.load()) : 0;
|
||||
const auto deadline = timeout > 0 ? std::chrono::steady_clock::now() + std::chrono::microseconds(timeout)
|
||||
: std::chrono::steady_clock::time_point::max();
|
||||
int transferred = 0;
|
||||
for (;;) {
|
||||
const auto result = operation(transferred);
|
||||
if (result != static_cast<Result>(-1)) {
|
||||
if (completeLength == 0) return result;
|
||||
transferred += static_cast<int>(result);
|
||||
if (transferred == completeLength || result == 0) return static_cast<Result>(transferred);
|
||||
} else if (WSAGetLastError() != WSAEWOULDBLOCK || nonblocking) {
|
||||
return transferred > 0 ? static_cast<Result>(transferred) : result;
|
||||
}
|
||||
int milliseconds = 10;
|
||||
if (timeout > 0) {
|
||||
const auto remaining = deadline - std::chrono::steady_clock::now();
|
||||
if (remaining <= std::chrono::steady_clock::duration::zero()) {
|
||||
WSASetLastError(completeLength > 0 ? WSAEWOULDBLOCK : WSAETIMEDOUT);
|
||||
return transferred > 0 ? static_cast<Result>(transferred) : static_cast<Result>(-1);
|
||||
}
|
||||
milliseconds = static_cast<int>(std::min<std::int64_t>(std::chrono::ceil<std::chrono::milliseconds>(remaining).count(), milliseconds));
|
||||
}
|
||||
WSAPOLLFD descriptor{};
|
||||
descriptor.fd = socket.value;
|
||||
descriptor.events = send ? POLLWRNORM : POLLRDNORM;
|
||||
const int ready = WSAPoll(&descriptor, 1, milliseconds);
|
||||
if (ready < 0) return transferred > 0 ? static_cast<Result>(transferred) : static_cast<Result>(-1);
|
||||
}
|
||||
#else
|
||||
static_cast<void>(socket);
|
||||
static_cast<void>(flags);
|
||||
static_cast<void>(send);
|
||||
return false;
|
||||
static_cast<void>(timed);
|
||||
static_cast<void>(length);
|
||||
return operation(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int TransferSocketForKernel(KernelSocketPoll::NativeOwner* owner, bool send, void* buffer, int length) {
|
||||
const auto* socket = static_cast<NativeSocketHandle*>(owner);
|
||||
return native_transfer(*socket, 0, send, [&](int offset) {
|
||||
return send ? ::send(socket->value, offset ? static_cast<const char*>(buffer) + offset : static_cast<const char*>(buffer), length - offset, 0)
|
||||
: ::recv(socket->value, static_cast<char*>(buffer), length, 0);
|
||||
}, true, length);
|
||||
}
|
||||
#endif
|
||||
|
||||
int native_message_flags(int flags) {
|
||||
int native = (flags & NET_MSG_PEEK) ? MSG_PEEK : 0;
|
||||
#ifndef _WIN32
|
||||
@@ -548,9 +545,10 @@ int wait_for_local_address(int s, const Sock& socket, bool empty, int flags) {
|
||||
if (socket.type == NET_SOCK_STREAM || socket.bound) return 0;
|
||||
const bool named = has_local_address(socket.native->value);
|
||||
if (!named && (socket.read_shut || empty)) return RECEIVE_NOTHING;
|
||||
if (!named && (socket.nonblock || (flags & NET_MSG_DONTWAIT) != 0)) return NET_EAGAIN;
|
||||
const auto deadline = socket.rcv_timeout_us > 0
|
||||
? std::chrono::steady_clock::now() + std::chrono::microseconds(socket.rcv_timeout_us)
|
||||
if (!named && (socket.native->nonblock || (flags & NET_MSG_DONTWAIT) != 0)) return NET_EAGAIN;
|
||||
const int timeout = socket.native->receiveTimeout;
|
||||
const auto deadline = timeout > 0
|
||||
? std::chrono::steady_clock::now() + std::chrono::microseconds(timeout)
|
||||
: std::chrono::steady_clock::time_point::max();
|
||||
std::unique_lock lk(g_mutex);
|
||||
for (bool waited = false;; waited = true) {
|
||||
@@ -726,7 +724,7 @@ bool ControlSocketStatusForKernel(int descriptor, int command, int flags, int* r
|
||||
std::lock_guard lk(g_mutex);
|
||||
const auto it = g_socks.find(descriptor);
|
||||
if (it == g_socks.end()) return false;
|
||||
nonblock = it->second.nonblock;
|
||||
nonblock = it->second.native->nonblock;
|
||||
}
|
||||
*result = 0;
|
||||
*error = 0;
|
||||
@@ -754,7 +752,7 @@ bool FindSocketNativeForKernel(int descriptor, std::shared_ptr<void>* owner, std
|
||||
const auto it = g_socks.find(descriptor);
|
||||
if (it == g_socks.end() || !it->second.native) return false;
|
||||
*native = static_cast<std::uintptr_t>(it->second.native->value);
|
||||
*owner = it->second.native;
|
||||
*owner = std::static_pointer_cast<KernelSocketPoll::NativeOwner>(it->second.native);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -800,6 +798,14 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol)
|
||||
if (!initialize_sockets()) return fail(5);
|
||||
const NativeSocket native = ::socket(family == NET_AF_INET ? AF_INET : AF_INET6, type, protocol);
|
||||
if (native == INVALID_NATIVE_SOCKET) return fail(native_error());
|
||||
#ifdef _WIN32
|
||||
u_long nonblocking = 1;
|
||||
if (ioctlsocket(native, FIONBIO, &nonblocking) != 0) {
|
||||
const int error = native_error();
|
||||
close_socket(native);
|
||||
return fail(error);
|
||||
}
|
||||
#endif
|
||||
const int ipv6_only = 1;
|
||||
if (family == NET_AF_INET6 &&
|
||||
::setsockopt(native, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char*>(&ipv6_only), sizeof(ipv6_only)) != 0) {
|
||||
@@ -809,7 +815,7 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol)
|
||||
}
|
||||
std::shared_ptr<NativeSocketHandle> handle;
|
||||
try {
|
||||
handle = std::make_shared<NativeSocketHandle>(native);
|
||||
handle = std::make_shared<NativeSocketHandle>(native, type == NET_SOCK_STREAM);
|
||||
} catch (const std::bad_alloc&) {
|
||||
close_socket(native);
|
||||
return fail(12);
|
||||
@@ -927,12 +933,15 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) {
|
||||
if (addr && !addrlen) return fail(NET_EINVAL);
|
||||
sockaddr_storage peer{};
|
||||
NativeLength peer_length = sizeof(peer);
|
||||
const NativeSocket accepted = ::accept(listener.native->value,
|
||||
addr ? reinterpret_cast<sockaddr*>(&peer) : nullptr, addr ? &peer_length : nullptr);
|
||||
const NativeSocket accepted = native_transfer(*listener.native, 0, false, [&](int) {
|
||||
peer_length = sizeof(peer);
|
||||
return ::accept(listener.native->value,
|
||||
addr ? reinterpret_cast<sockaddr*>(&peer) : nullptr, addr ? &peer_length : nullptr);
|
||||
}, false);
|
||||
if (accepted == INVALID_NATIVE_SOCKET) return fail(native_error());
|
||||
std::shared_ptr<NativeSocketHandle> handle;
|
||||
try {
|
||||
handle = std::make_shared<NativeSocketHandle>(accepted);
|
||||
handle = std::make_shared<NativeSocketHandle>(accepted, true);
|
||||
} catch (const std::bad_alloc&) {
|
||||
close_socket(accepted);
|
||||
return fail(12);
|
||||
@@ -941,13 +950,17 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) {
|
||||
connection.family = listener.family;
|
||||
connection.type = NET_SOCK_STREAM;
|
||||
connection.native = std::move(handle);
|
||||
#ifdef _WIN32
|
||||
connection.native->receiveTimeout = listener.native->receiveTimeout.load();
|
||||
connection.native->sendTimeout = listener.native->sendTimeout.load();
|
||||
#endif
|
||||
const int descriptor = KernelReserveSocketNumber_nid_no_patch(static_cast<std::uintptr_t>(connection.native->value));
|
||||
if (descriptor < 0) return fail(-descriptor);
|
||||
{
|
||||
std::lock_guard lk(g_mutex);
|
||||
g_socks.emplace(descriptor, connection);
|
||||
}
|
||||
if (listener.nonblock) {
|
||||
if (listener.native->nonblock) {
|
||||
const int enabled = 1;
|
||||
if (sceNetSetsockopt(descriptor, NET_SOL_SOCKET, NET_SO_NBIO, &enabled, sizeof(enabled)) != 0) {
|
||||
const int error = *errno_slot();
|
||||
@@ -1025,7 +1038,21 @@ int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) return fail(NET_EINPROGRESS);
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK) {
|
||||
if (socket.native->nonblock) return fail(NET_EINPROGRESS);
|
||||
fd_set writable;
|
||||
fd_set failed;
|
||||
FD_ZERO(&writable);
|
||||
FD_ZERO(&failed);
|
||||
FD_SET(socket.native->value, &writable);
|
||||
FD_SET(socket.native->value, &failed);
|
||||
if (::select(0, nullptr, &writable, &failed, nullptr) < 0) return fail(native_error());
|
||||
int error = 0;
|
||||
int size = sizeof(error);
|
||||
if (::getsockopt(socket.native->value, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &size) != 0)
|
||||
return fail(native_error());
|
||||
return error == 0 ? 0 : fail(net_error(error));
|
||||
}
|
||||
#endif
|
||||
return fail(native_error());
|
||||
}
|
||||
@@ -1046,9 +1073,10 @@ int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) {
|
||||
#endif
|
||||
const GuestArena::HostWrite destination(len == 0 ? nullptr : buf, len);
|
||||
if (!destination.Open()) return fail(NET_EFAULT);
|
||||
if (would_block(socket, flags, false)) return fail(NET_EAGAIN);
|
||||
const int native_flags = native_message_flags(flags);
|
||||
const int result = ::recv(socket.native->value, static_cast<char*>(buf), static_cast<int>(len), native_flags);
|
||||
const int result = native_transfer(*socket.native, flags, false, [&](int) {
|
||||
return ::recv(socket.native->value, static_cast<char*>(buf), static_cast<int>(len), native_flags);
|
||||
});
|
||||
#ifdef _WIN32
|
||||
if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) return static_cast<int64_t>(len);
|
||||
#endif
|
||||
@@ -1084,10 +1112,12 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void*
|
||||
if (!destination.Open()) return fail(NET_EFAULT);
|
||||
sockaddr_storage peer{};
|
||||
NativeLength peer_length = sizeof(peer);
|
||||
if (would_block(socket, flags, false)) return fail(NET_EAGAIN);
|
||||
const int native_flags = native_message_flags(flags);
|
||||
int result = ::recvfrom(socket.native->value, static_cast<char*>(buf), static_cast<int>(len),
|
||||
native_flags, from ? reinterpret_cast<sockaddr*>(&peer) : nullptr, from ? &peer_length : nullptr);
|
||||
int result = native_transfer(*socket.native, flags, false, [&](int) {
|
||||
peer_length = sizeof(peer);
|
||||
return ::recvfrom(socket.native->value, static_cast<char*>(buf), static_cast<int>(len),
|
||||
native_flags, from ? reinterpret_cast<sockaddr*>(&peer) : nullptr, from ? &peer_length : nullptr);
|
||||
});
|
||||
#ifdef _WIN32
|
||||
if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) result = static_cast<int>(len);
|
||||
#endif
|
||||
@@ -1107,8 +1137,9 @@ int64_t APS5_VABI sceNetSend(int s, const void* buf, size_t len, int flags) {
|
||||
if (it == g_socks.end()) return fail(NET_EBADF);
|
||||
socket = it->second;
|
||||
}
|
||||
if (would_block(socket, flags, true)) return fail(NET_EAGAIN);
|
||||
const int result = ::send(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), NATIVE_SEND_FLAGS | native_message_flags(flags));
|
||||
const int result = native_transfer(*socket.native, flags, true, [&](int offset) {
|
||||
return ::send(socket.native->value, offset ? static_cast<const char*>(buf) + offset : static_cast<const char*>(buf), static_cast<int>(len) - offset, NATIVE_SEND_FLAGS | native_message_flags(flags));
|
||||
}, true, static_cast<int>(len));
|
||||
return result >= 0 ? result : fail(native_error());
|
||||
}
|
||||
|
||||
@@ -1123,18 +1154,21 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co
|
||||
if (it == g_socks.end()) return fail(NET_EBADF);
|
||||
socket = it->second;
|
||||
}
|
||||
if (would_block(socket, flags, true)) return fail(NET_EAGAIN);
|
||||
int result;
|
||||
if (!to) {
|
||||
if (tolen != 0) return fail(NET_EINVAL);
|
||||
result = ::send(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), NATIVE_SEND_FLAGS | native_message_flags(flags));
|
||||
result = native_transfer(*socket.native, flags, true, [&](int offset) {
|
||||
return ::send(socket.native->value, offset ? static_cast<const char*>(buf) + offset : static_cast<const char*>(buf), static_cast<int>(len) - offset, NATIVE_SEND_FLAGS | native_message_flags(flags));
|
||||
}, true, static_cast<int>(len));
|
||||
} else {
|
||||
sockaddr_storage destination{};
|
||||
NativeLength destination_length = 0;
|
||||
if (!guest_to_native_address(to, tolen, destination, destination_length)) return fail(NET_EINVAL);
|
||||
if (destination.ss_family != (socket.family == NET_AF_INET ? AF_INET : AF_INET6)) return fail(NET_EAFNOSUPPORT);
|
||||
result = ::sendto(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), NATIVE_SEND_FLAGS | native_message_flags(flags),
|
||||
reinterpret_cast<const sockaddr*>(&destination), destination_length);
|
||||
result = native_transfer(*socket.native, flags, true, [&](int offset) {
|
||||
return ::sendto(socket.native->value, offset ? static_cast<const char*>(buf) + offset : static_cast<const char*>(buf), static_cast<int>(len) - offset, NATIVE_SEND_FLAGS | native_message_flags(flags),
|
||||
reinterpret_cast<const sockaddr*>(&destination), destination_length);
|
||||
}, true, static_cast<int>(len));
|
||||
}
|
||||
if (result < 0) return fail(native_error());
|
||||
#ifdef _WIN32
|
||||
@@ -1190,7 +1224,6 @@ int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (would_block(socket, flags, false)) return fail(NET_EAGAIN);
|
||||
std::vector<char> buffer;
|
||||
try {
|
||||
buffer.resize(static_cast<std::size_t>(total) + (datagram ? 1u : 0u));
|
||||
@@ -1199,8 +1232,11 @@ int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) {
|
||||
}
|
||||
sockaddr_storage peer{};
|
||||
NativeLength peer_length = sizeof(peer);
|
||||
std::int64_t received = ::recvfrom(socket.native->value, buffer.data(), static_cast<int>(buffer.size()),
|
||||
native_message_flags(flags), reinterpret_cast<sockaddr*>(&peer), &peer_length);
|
||||
std::int64_t received = native_transfer(*socket.native, flags, false, [&](int) {
|
||||
peer_length = sizeof(peer);
|
||||
return ::recvfrom(socket.native->value, buffer.data(), static_cast<int>(buffer.size()),
|
||||
native_message_flags(flags), reinterpret_cast<sockaddr*>(&peer), &peer_length);
|
||||
});
|
||||
if (received < 0) {
|
||||
#ifdef _WIN32
|
||||
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(receive_error(socket));
|
||||
@@ -1282,10 +1318,7 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval
|
||||
int value = 0;
|
||||
std::memcpy(&value, optval, sizeof(value));
|
||||
if (optname == NET_SO_NBIO) {
|
||||
#ifdef _WIN32
|
||||
u_long enabled = value != 0;
|
||||
if (ioctlsocket(socket.native->value, FIONBIO, &enabled) != 0) return fail(native_error());
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
const int old_flags = fcntl(socket.native->value, F_GETFL, 0);
|
||||
if (old_flags < 0 || fcntl(socket.native->value, F_SETFL,
|
||||
value ? old_flags | O_NONBLOCK : old_flags & ~O_NONBLOCK) != 0) return fail(native_error());
|
||||
@@ -1304,9 +1337,9 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval
|
||||
std::lock_guard lk(g_mutex);
|
||||
auto it = g_socks.find(s);
|
||||
if (it != g_socks.end()) {
|
||||
if (optname == NET_SO_NBIO) it->second.nonblock = value != 0;
|
||||
if (optname == NET_SO_RCVTIMEO) it->second.rcv_timeout_us = value;
|
||||
if (optname == NET_SO_SNDTIMEO) it->second.snd_timeout_us = value;
|
||||
if (optname == NET_SO_NBIO) it->second.native->nonblock = value != 0;
|
||||
if (optname == NET_SO_RCVTIMEO) it->second.native->receiveTimeout = value;
|
||||
if (optname == NET_SO_SNDTIMEO) it->second.native->sendTimeout = value;
|
||||
if (optname == NET_SO_SNDBUF) it->second.snd_buffer = value;
|
||||
if (optname == NET_SO_RCVBUF) it->second.rcv_buffer = value;
|
||||
if (optname == NET_SO_ONESBCAST) it->second.onesbcast = value;
|
||||
@@ -1324,7 +1357,7 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint
|
||||
}
|
||||
if (optval == nullptr || optlen == nullptr) return fail(NET_EINVAL);
|
||||
if (level == NET_SOL_SOCKET && (optname == NET_SO_RCVTIMEO || optname == NET_SO_SNDTIMEO)) {
|
||||
const int microseconds = optname == NET_SO_RCVTIMEO ? socket.rcv_timeout_us : socket.snd_timeout_us;
|
||||
const int microseconds = optname == NET_SO_RCVTIMEO ? socket.native->receiveTimeout.load() : socket.native->sendTimeout.load();
|
||||
const KernelTimeval timeout{microseconds / 1000000, microseconds % 1000000};
|
||||
*optlen = std::min(*optlen, static_cast<std::uint32_t>(sizeof(timeout)));
|
||||
std::memcpy(optval, &timeout, *optlen);
|
||||
@@ -1346,9 +1379,9 @@ int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint
|
||||
if (level != NET_SOL_SOCKET) return fail(NET_EOPNOTSUPP);
|
||||
int value = 0;
|
||||
switch (timeout_option(optname)) {
|
||||
case NET_SO_NBIO: value = socket.nonblock ? 1 : 0; break;
|
||||
case NET_SO_RCVTIMEO: value = socket.rcv_timeout_us; break;
|
||||
case NET_SO_SNDTIMEO: value = socket.snd_timeout_us; break;
|
||||
case NET_SO_NBIO: value = socket.native->nonblock ? 1 : 0; break;
|
||||
case NET_SO_RCVTIMEO: value = socket.native->receiveTimeout; break;
|
||||
case NET_SO_SNDTIMEO: value = socket.native->sendTimeout; break;
|
||||
case NET_SO_ONESBCAST: value = socket.onesbcast; break;
|
||||
case NET_SO_ERROR: {
|
||||
int pending = 0;
|
||||
@@ -1500,7 +1533,7 @@ static int fill_sock_info(int s, NetSockInfo& record) {
|
||||
record.remote_adr = remote.sin_addr.s_addr;
|
||||
record.local_port = local.sin_port;
|
||||
record.remote_port = remote.sin_port;
|
||||
record.flags = NET_SOCKINFO_F_SELF | (socket.nonblock ? NET_SOCKINFO_F_NONBLOCK : 0);
|
||||
record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1554,7 +1587,7 @@ int APS5_VABI sceNetShowNetstatWithMemory(int memid) {
|
||||
poolSize = pool->second;
|
||||
sockets.reserve(g_socks.size());
|
||||
for (const auto& [id, socket] : g_socks)
|
||||
sockets.push_back({id, socket.family, socket.type, socket.nonblock, socket.bound, socket.listening});
|
||||
sockets.push_back({id, socket.family, socket.type, socket.native->nonblock, socket.bound, socket.listening});
|
||||
}
|
||||
std::fprintf(stdout, "sceNet netstat: pool=%d bytes=%zu sockets=%zu\n", memid, poolSize, sockets.size());
|
||||
for (const auto& socket : sockets)
|
||||
@@ -2118,9 +2151,13 @@ int APS5_VABI sceNetGetAppMemInfo(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetGetDnsInfo(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceNetGetDnsInfo(std::uint32_t* info, int flags) {
|
||||
if (flags != 0) throw std::runtime_error("sceNetGetDnsInfo: flags " + std::to_string(flags) + " are not supported");
|
||||
if (!info) throw std::runtime_error("sceNetGetDnsInfo: a null info is not supported");
|
||||
std::array<std::uint32_t, 2> servers{};
|
||||
const std::size_t count = std::min(NetCtlHostDnsServers_nid_no_patch(servers.data(), servers.size()), servers.size());
|
||||
std::memcpy(info, servers.data(), sizeof(servers));
|
||||
return static_cast<int>(count);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetGetIfList(void) {
|
||||
|
||||
@@ -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;
|
||||
@@ -350,6 +352,13 @@ static bool FindHostIpv4Interface(HostIpv4Interface& found) {
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity) {
|
||||
HostIpv4Interface host;
|
||||
if (!FindHostIpv4Interface(host)) return 0;
|
||||
for (std::size_t index = 0; index < host.dns.size() && index < capacity; ++index) servers[index] = host.dns[index].s_addr;
|
||||
return host.dns.size();
|
||||
}
|
||||
|
||||
static void WriteInfoWord(NetCtlInfo* info, std::uint32_t value) {
|
||||
std::memcpy(info->opaque, &value, sizeof(value));
|
||||
}
|
||||
@@ -456,11 +465,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 +501,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 = {};
|
||||
|
||||
@@ -20,6 +20,7 @@ static constexpr int MAX_SLOTS = 64;
|
||||
|
||||
namespace {
|
||||
struct Request {
|
||||
int titleCtxId = 0;
|
||||
bool started = false;
|
||||
bool aborted = false;
|
||||
int result = 0;
|
||||
@@ -68,10 +69,17 @@ int APS5_VABI sceNpTusCreateRequest(int titleCtxId) {
|
||||
if (!titleContexts.contains(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
|
||||
if (requests.size() >= MAX_REQUESTS) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_OBJECTS;
|
||||
const int reqId = nextRequest++;
|
||||
requests.emplace(reqId, Request{});
|
||||
requests.emplace(reqId, Request{titleCtxId});
|
||||
return reqId;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteNpTitleCtx(int titleCtxId) {
|
||||
std::lock_guard lock(tusMutex);
|
||||
if (!titleContexts.erase(titleCtxId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID;
|
||||
std::erase_if(requests, [titleCtxId](const auto& entry) { return entry.second.titleCtxId == titleCtxId; });
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteRequest(int reqId) {
|
||||
std::lock_guard lock(tusMutex);
|
||||
return requests.erase(reqId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID;
|
||||
@@ -126,4 +134,614 @@ int APS5_VABI sceNpTusAddAndGetVariableAAsync(int reqId, std::uint64_t targetAcc
|
||||
return StartOffline(reqId);
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetThreadParam(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssCreateNpTitleCtx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssCreateNpTitleCtxA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusCreateTitleCtx(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetTimeout(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusChangeModeForOtherSaveDataOwners(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusWaitAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetMultiSlotVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetMultiSlotVariableVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariable(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsVariable(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariable(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariable(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotVariable(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotVariableVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotVariableAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotVariableVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotData(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotDataVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotDataAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotDataVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetDataAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetDataVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsDataStatus(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsDataStatusAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssGetSmallStorage(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssGetSmallStorageAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssGetStorage(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssGetStorageAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTssGetDataAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetDataForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsVariableAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsVariableForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsVariableForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsDataStatusA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsDataStatusAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsDataStatusForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusGetFriendsDataStatusForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetMultiSlotVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetDataA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetDataAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetDataAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusSetDataAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotDataA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusDeleteMultiSlotDataAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableA(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableAAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableAVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableAVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableForCrossSave(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUser(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -78,8 +78,8 @@ int APS5_VABI scePadGetControllerInformation(int handle, PadControllerInformatio
|
||||
}
|
||||
std::memset(info, 0, sizeof(*info));
|
||||
info->touchPadInfo.pixelDensity = 44.86f;
|
||||
info->touchPadInfo.resolution.x = 1920;
|
||||
info->touchPadInfo.resolution.y = 943;
|
||||
info->touchPadInfo.resolution.x = kTouchPadWidth;
|
||||
info->touchPadInfo.resolution.y = kTouchPadHeight;
|
||||
info->stickInfo.deadZoneLeft = 2;
|
||||
info->stickInfo.deadZoneRight = 2;
|
||||
info->connectionType = PAD_CONNECTION_TYPE_LOCAL;
|
||||
@@ -122,8 +122,8 @@ int APS5_VABI scePadGetExtControllerInformation(int handle, void* info) {
|
||||
std::memset(info, 0, 0x2c);
|
||||
PadControllerInformation* base = static_cast<PadControllerInformation*>(info);
|
||||
base->touchPadInfo.pixelDensity = 44.86f;
|
||||
base->touchPadInfo.resolution.x = 1920;
|
||||
base->touchPadInfo.resolution.y = 943;
|
||||
base->touchPadInfo.resolution.x = kTouchPadWidth;
|
||||
base->touchPadInfo.resolution.y = kTouchPadHeight;
|
||||
base->stickInfo.deadZoneLeft = 2;
|
||||
base->stickInfo.deadZoneRight = 2;
|
||||
base->connectionType = PAD_CONNECTION_TYPE_LOCAL;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include <exception>
|
||||
#include "SceTypes.hpp"
|
||||
|
||||
constexpr std::uint16_t kTouchPadWidth = 1920;
|
||||
constexpr std::uint16_t kTouchPadHeight = 1080;
|
||||
|
||||
struct PadTouchPoint {
|
||||
bool active = false;
|
||||
std::uint16_t x = 0;
|
||||
|
||||
@@ -202,8 +202,8 @@ PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uin
|
||||
if (touchNum == 0 && (state.touchLeft || state.touchRight || injected.touchLeft || injected.touchRight)) {
|
||||
data.buttons |= 0x100000;
|
||||
touchNum = 1;
|
||||
data.touch_data_touch0_x = state.touchRight || injected.touchRight ? 1440 : 480;
|
||||
data.touch_data_touch0_y = 471;
|
||||
data.touch_data_touch0_x = state.touchRight || injected.touchRight ? kTouchPadWidth * 3 / 4 : kTouchPadWidth / 4;
|
||||
data.touch_data_touch0_y = kTouchPadHeight / 2;
|
||||
data.touch_data_touch0_id = 0;
|
||||
}
|
||||
data.touch_data_touch_num = touchNum;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -356,7 +356,10 @@ struct Session {
|
||||
const auto chunk = static_cast<int>(std::min<std::size_t>(length, INT_MAX));
|
||||
for (;;) {
|
||||
#ifdef _WIN32
|
||||
const auto sent = ::send(static_cast<SOCKET>(session.socket), reinterpret_cast<const char*>(buffer), chunk, 0);
|
||||
auto* owner = static_cast<KernelSocketPoll::NativeOwner*>(session.socketOwner.get());
|
||||
const auto sent = owner && owner->transfer
|
||||
? owner->transfer(owner, true, const_cast<unsigned char*>(buffer), chunk)
|
||||
: ::send(static_cast<SOCKET>(session.socket), reinterpret_cast<const char*>(buffer), chunk, 0);
|
||||
#else
|
||||
const auto sent = ::send(static_cast<int>(session.socket), buffer, static_cast<std::size_t>(chunk), MSG_NOSIGNAL);
|
||||
#endif
|
||||
@@ -375,7 +378,10 @@ struct Session {
|
||||
const auto chunk = static_cast<int>(std::min<std::size_t>(length, INT_MAX));
|
||||
for (;;) {
|
||||
#ifdef _WIN32
|
||||
const auto received = ::recv(static_cast<SOCKET>(session.socket), reinterpret_cast<char*>(buffer), chunk, 0);
|
||||
auto* owner = static_cast<KernelSocketPoll::NativeOwner*>(session.socketOwner.get());
|
||||
const auto received = owner && owner->transfer
|
||||
? owner->transfer(owner, false, buffer, chunk)
|
||||
: ::recv(static_cast<SOCKET>(session.socket), reinterpret_cast<char*>(buffer), chunk, 0);
|
||||
#else
|
||||
const auto received = ::recv(static_cast<int>(session.socket), buffer, static_cast<std::size_t>(chunk), 0);
|
||||
#endif
|
||||
|
||||
Loaded 100 of 338 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user