Merge origin/main into PR 8134

Co-authored-by: Muhammad Wasif Ali Wasif <[email protected]>
This commit is contained in:
Dean GalvinandMuhammad Wasif Ali Wasif committed 2026-10-21 14:56:22 +13:00
commit 490adefc09
338 files changed
+13805 -1584

No files matched your search

+85 -4
View File
@@ -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 = &target;
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, &parameters);
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, &copy);
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()
+10 -117
View File
@@ -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()
+284 -31
View File
@@ -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;
}
+70
View File
@@ -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
+160
View File
@@ -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);
+23 -19
View File
@@ -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;
+6
View File
@@ -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);
+7 -6
View File
@@ -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);
+34 -4
View File
@@ -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);
+54 -10
View File
@@ -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
View File
@@ -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);
}
+8
View File
@@ -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;
+2 -1
View File
@@ -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);
+28
View File
@@ -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
View File
@@ -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) {
+16 -5
View File
@@ -64,6 +64,8 @@ static constexpr int NET_CTL_INFO_SECONDARY_DNS = 18;
static constexpr int NET_CTL_INFO_HTTP_PROXY_CONFIG = 19;
static constexpr int NET_CTL_INFO_HTTP_PROXY_SERVER = 20;
static constexpr int NET_CTL_INFO_HTTP_PROXY_PORT = 21;
static constexpr int NET_CTL_INFO_V6_IP_ADDRESS = 1;
static constexpr int NET_CTL_INFO_V6_SECONDARY_DNS = 5;
static constexpr std::uint32_t NET_CTL_DEVICE_WIRED = 0;
static constexpr std::uint32_t NET_CTL_LINK_CONNECTED = 1;
static constexpr std::uint32_t NET_CTL_IP_DHCP = 0;
@@ -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 = {};
+619 -1
View File
@@ -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;
}
}
+4 -4
View File
@@ -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;
+2 -2
View File
@@ -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
+8 -9
View File
@@ -69,17 +69,16 @@ int APS5_VABI sceRazorCpuSync(void) {
}
void APS5_VABI sceRazorCpuDisableFiberUserMarkers(void) {
NotImplemented_nid_no_patch(__func__);
}
int APS5_VABI sceRazorCpuInitDataTags(void* buffer, size_t size) {
(void)buffer;
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuShutdownDataTags(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
size_t APS5_VABI sceRazorCpuGetDataTagStorageSize(uint32_t tagCount) {
@@ -93,7 +92,7 @@ int APS5_VABI sceRazorCpuTagArray(const char* label, uint32_t category, void* ar
(void)array;
(void)bufferSize;
(void)elementSize;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* buffer, size_t bufferSize) {
@@ -101,20 +100,20 @@ int APS5_VABI sceRazorCpuTagBuffer(const char* label, uint32_t category, void* b
(void)category;
(void)buffer;
(void)bufferSize;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuResizeTaggedBuffer(uint32_t category, void* buffer, size_t newBufferSize) {
(void)category;
(void)buffer;
(void)newBufferSize;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuUnTagBuffer(uint32_t category, void* buffer) {
(void)category;
(void)buffer;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t tag, uint64_t size, uint8_t operation) {
@@ -122,11 +121,11 @@ int APS5_VABI sceRazorCpuBeginLogicalFileAccess(const char* filename, uint64_t t
(void)tag;
(void)size;
(void)operation;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceRazorCpuEndLogicalFileAccess(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+8 -2
View File
@@ -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