Merge main into feat/http-cookie-enabled-option

This commit is contained in:
Dean Galvin committed 2026-10-23 14:37:47 +13:00
commit 57bf968aa3
348 files changed
+12199 -1588

No files matched your search

+6
View File
@@ -52,3 +52,9 @@
[submodule "3rdparty/vgmstream"]
path = 3rdparty/vgmstream
url = https://github.com/vgmstream/vgmstream.git
[submodule "3rdparty/ogg"]
path = 3rdparty/ogg
url = https://github.com/xiph/ogg.git
[submodule "3rdparty/vorbis"]
path = 3rdparty/vorbis
url = https://github.com/xiph/vorbis.git
Vendored Submodule
+1
Submodule 3rdparty/ogg added at be05b13e98.
Vendored Submodule
+1
Submodule 3rdparty/vorbis added at 0657aee69d.
+14 -2
View File
@@ -218,7 +218,7 @@ if(TARGET mimalloc)
target_compile_features(pe_import_first PRIVATE cxx_std_20)
if(MINGW)
target_compile_options(pe_import_first PRIVATE -fasynchronous-unwind-tables)
target_link_options(pe_import_first PRIVATE -static -static-libgcc -static-libstdc++)
target_link_options(pe_import_first PRIVATE -municode -static -static-libgcc -static-libstdc++)
endif()
add_custom_command(
OUTPUT ${PATCHED_DIR}/mimalloc.dll
@@ -621,6 +621,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpCppWebApi>")
endif()
add_test_executable(guest_np_cpp_web_api_signed_out_tests tests/GuestNpCppWebApiSignedOut.cpp)
target_include_directories(guest_np_cpp_web_api_signed_out_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_np_cpp_web_api_signed_out_tests PRIVATE libSceNpCppWebApi libc)
configure_windows_unwind(guest_np_cpp_web_api_signed_out_tests)
add_test(NAME guest_np_cpp_web_api_signed_out COMMAND guest_np_cpp_web_api_signed_out_tests)
if(WIN32)
set_tests_properties(guest_np_cpp_web_api_signed_out PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpCppWebApi>")
endif()
add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp)
target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(savedata_core_write_replacement_tests PRIVATE libSceSaveData)
@@ -713,9 +723,11 @@ if(BUILD_TESTING)
add_test_executable(guest_vorbis_dec_tests tests/GuestVorbisDec.cpp)
target_include_directories(guest_vorbis_dec_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec libc)
target_compile_definitions(guest_vorbis_dec_tests PRIVATE OV_EXCLUDE_STATIC_CALLBACKS)
target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec vorbisenc vorbisfile vorbis ogg libc)
configure_windows_unwind(guest_vorbis_dec_tests)
add_test(NAME guest_vorbis_dec COMMAND guest_vorbis_dec_tests)
set_tests_properties(guest_vorbis_dec PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_vorbis_dec PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVorbisDec>")
+4 -3
View File
@@ -5,6 +5,7 @@
#include <cctype>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <stdexcept>
@@ -78,16 +79,16 @@ void MoveImportFirst(std::vector<unsigned char>& bytes, const std::string& dll)
for (std::size_t index = 0; index < descriptors.size(); ++index) std::memcpy(bytes.data() + directory + index * DescriptorBytes, descriptors[index].data(), DescriptorBytes);
}
void MoveImportFirstInFile(const std::string& path, const std::string& dll) {
void MoveImportFirstInFile(const std::filesystem::path& path, const std::string& dll) {
std::vector<unsigned char> bytes;
{
std::ifstream input(path, std::ios::binary);
if (!input) throw std::runtime_error("cannot open " + path);
if (!input) throw std::runtime_error("cannot open " + path.string());
bytes.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
}
MoveImportFirst(bytes, dll);
std::ofstream output(path, std::ios::binary | std::ios::trunc);
if (!output || !output.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) throw std::runtime_error("cannot write " + path);
if (!output || !output.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) throw std::runtime_error("cannot write " + path.string());
}
}
+2 -1
View File
@@ -1,13 +1,14 @@
#ifndef CORE_LIBS_ALLOCATOR_PEIMPORTFIRST_HPP
#define CORE_LIBS_ALLOCATOR_PEIMPORTFIRST_HPP
#include <filesystem>
#include <string>
#include <vector>
namespace Allocator {
void MoveImportFirst(std::vector<unsigned char>& image, const std::string& dll);
void MoveImportFirstInFile(const std::string& path, const std::string& dll);
void MoveImportFirstInFile(const std::filesystem::path& path, const std::string& dll);
}
+27 -7
View File
@@ -2,17 +2,37 @@
#include <cstdio>
#include <exception>
#include <filesystem>
#include <string>
int main(int argc, char** argv) {
if (argc != 3) {
std::fprintf(stderr, "usage: pe_import_first <file> <dll>\n");
return 2;
}
namespace {
int Usage() {
std::fprintf(stderr, "usage: pe_import_first <file> <dll>\n");
return 2;
}
int Run(const std::filesystem::path& file, const std::string& dll) {
try {
Allocator::MoveImportFirstInFile(argv[1], argv[2]);
Allocator::MoveImportFirstInFile(file, dll);
} catch (const std::exception& error) {
std::fprintf(stderr, "pe_import_first: %s: %s\n", argv[1], error.what());
std::fprintf(stderr, "pe_import_first: %s: %s\n", file.string().c_str(), error.what());
return 1;
}
return 0;
}
}
#ifdef _WIN32
int wmain(int argc, wchar_t** argv) {
if (argc != 3) return Usage();
const auto dll = std::filesystem::path(argv[2]).u8string();
return Run(argv[1], std::string(dll.begin(), dll.end()));
}
#else
int main(int argc, char** argv) {
if (argc != 3) return Usage();
return Run(argv[1], argv[2]);
}
#endif
@@ -113,7 +113,7 @@ void movesTheNamedImportFirst() {
void movesTheImportInAFile(const std::filesystem::path& directory) {
const auto path = directory / "moved.dll";
writeFile(path, image({"kernel32.dll", "mimalloc-redirect.dll"}));
Allocator::MoveImportFirstInFile(path.string(), "mimalloc-redirect.dll");
Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll");
const auto moved = readFile(path);
require(importName(moved, 0) == "mimalloc-redirect.dll" && importName(moved, 1) == "kernel32.dll", "the file's import did not move first");
}
@@ -122,7 +122,7 @@ void failsWithoutTouchingTheFile(const std::filesystem::path& directory, const s
const auto path = directory / (what + ".dll");
writeFile(path, bytes);
try {
Allocator::MoveImportFirstInFile(path.string(), "mimalloc-redirect.dll");
Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll");
} catch (const std::exception& error) {
require(std::string(error.what()).find(expected) != std::string::npos, what + ": failed with another error: " + error.what());
require(readFile(path) == bytes, what + ": the file changed although the move failed");
@@ -134,7 +134,8 @@ void failsWithoutTouchingTheFile(const std::filesystem::path& directory, const s
}
int main() {
const auto directory = std::filesystem::temp_directory_path() / ("aps5-pe-import-first-" + std::to_string(std::random_device{}()));
auto directory = std::filesystem::temp_directory_path() / std::filesystem::path(u8"aps5-pe-import-first-a\u00e7\u00e3o-\u65e5\u672c-");
directory += std::to_string(std::random_device{}());
try {
std::filesystem::create_directories(directory);
movesTheNamedImportFirst();
+13
View File
@@ -267,6 +267,19 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_instrumentation_query_size_tests EXCLUDE_FROM_ALL tests/InstrumentationQuerySize.cpp)
target_include_directories(agc_instrumentation_query_size_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_instrumentation_query_size_tests PRIVATE libSceAgc libc)
configure_windows_unwind(agc_instrumentation_query_size_tests)
if(BUILD_TESTING)
set_target_properties(agc_instrumentation_query_size_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_instrumentation_query_size_tests COMMAND agc_instrumentation_query_size_tests)
if(WIN32)
set_tests_properties(agc_instrumentation_query_size_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_context_state_size_tests EXCLUDE_FROM_ALL tests/ContextStateSize.cpp)
target_include_directories(agc_context_state_size_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_context_state_size_tests PRIVATE libSceAgc libc)
@@ -22,13 +22,12 @@ std::uint32_t* APS5_VABI sceAgcDcbGetLodStats(CommandBuffer* buf, std::uint8_t c
}
std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t queryType) {
NotImplemented_nid_no_patch(__func__);
return 0;
if (queryType == 1) return 0x120u;
return queryType == 0 ? 0x10u : 0u;
}
std::uint32_t APS5_VABI sceAgcDcbEndOcclusionQueryGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 0x10u;
}
}
@@ -1,9 +1,16 @@
#include "prx/libSceAgc/Misc/include/Debug.hpp"
#include <atomic>
#include <cstdint>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
std::atomic<std::uint32_t> shaderInstrumentation{0};
}
extern "C" {
int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) {
@@ -11,4 +18,13 @@ int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) {
return 0;
}
int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags) {
shaderInstrumentation.store(flags, std::memory_order_relaxed);
return 0;
}
std::uint32_t APS5_VABI sceAgcGetShaderInstrumentation() {
return shaderInstrumentation.load(std::memory_order_relaxed);
}
}
@@ -53,12 +53,9 @@ std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderS
: CreateInterpolantValue(psWord, gsSemantic);
}
std::uint32_t UpdateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) {
const std::uint32_t f16 = (psWord >> 20u) & 0x3u;
if (f16 != 0 && f16 != InterpolantF16HiOnly && ((psWord >> 28u) & 0x3u) != ((psWord >> 30u) & 0x3u)) {
throw std::runtime_error(std::string(__func__) + ": DEFAULT_VAL of an f16 input with different low and high defaults is unknown");
}
return CreateInterpolantValueSplitF16(psWord, gsSemantic);
std::uint32_t CreateInterpolantValueFlatDefault(std::uint32_t psWord, const ShaderSemantic* gsSemantic) {
const std::uint32_t value = CreateInterpolantValue(psWord, gsSemantic);
return ((psWord & 0x00700000u) == 0x00400000u) ? (value | 0x00000020u) : value;
}
int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) {
@@ -132,7 +129,15 @@ int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const S
}
int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, UpdateInterpolantValue, false);
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, false);
}
int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueFlatDefault, true);
}
int APS5_VABI sceAgcUpdateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueFlatDefault, false);
}
}
-10
View File
@@ -40,14 +40,4 @@ std::uint32_t* APS5_VABI sceAgcCbMemsetExclusive(CommandBuffer* buffer, void* de
return 0;
}
int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcGetShaderInstrumentation() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,48 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include <cstdint>
#include <cstdio>
#include <stdexcept>
extern "C" {
int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t);
std::uint32_t APS5_VABI sceAgcGetShaderInstrumentation();
std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t);
std::uint32_t APS5_VABI sceAgcDcbEndOcclusionQueryGetSize();
}
namespace {
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
void testShaderInstrumentation() {
check(sceAgcGetShaderInstrumentation() == 0, "shader instrumentation starts cleared");
check(sceAgcSetShaderInstrumentation(0x80000005u) == 0, "setting shader instrumentation failed");
check(sceAgcGetShaderInstrumentation() == 0x80000005u, "shader instrumentation flags were not kept");
check(sceAgcSetShaderInstrumentation(0) == 0 && sceAgcGetShaderInstrumentation() == 0, "shader instrumentation was not cleared");
}
void testOcclusionQuerySizes() {
check(sceAgcDcbBeginOcclusionQueryGetSize(0) == 0x10u, "begin occlusion query size for type 0");
check(sceAgcDcbBeginOcclusionQueryGetSize(1) == 0x120u, "begin occlusion query size for type 1");
check(sceAgcDcbBeginOcclusionQueryGetSize(2) == 0, "begin occlusion query size for an unknown type");
check(sceAgcDcbEndOcclusionQueryGetSize() == 0x10u, "end occlusion query size");
}
}
int main() {
try {
testShaderInstrumentation();
testOcclusionQuerySizes();
LibcRunShutdown_nid_postfix();
std::puts("AGC shader instrumentation and occlusion query size tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}
@@ -14,6 +14,8 @@
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps);
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps);
extern "C" int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps);
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps);
extern "C" int APS5_VABI sceAgcUpdateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps);
namespace {
@@ -158,13 +160,46 @@ void testUpdateMapping() {
check(std::memcmp(create.data(), update.data(), written) == 0, "update differs from the split f16 mapping");
checkIdentity(create, static_cast<std::uint32_t>(inputs.size()), "unused interpolants are not the identity");
for (const std::uint32_t index : {1u, 3u}) {
inputs[index].default_value = 0;
update = filled();
expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(update.data(), &gs, &ps); });
check(std::memcmp(update.data(), untouched.data(), sizeof(update)) == 0, "rejected update wrote registers");
inputs[index].default_value = inputs[index].default_value_hi;
inputs[1].default_value = 0;
inputs[3].default_value = 0;
update = filled();
check(sceAgcUpdateInterpolantMapping_10000(update.data(), &gs, &ps) == 0, "update with split f16 defaults failed");
check(update[1].value == 0x01380007u && update[3].value == 0x03680409u, "f16 DEFAULT_VAL does not come from the low default");
}
void testUnversionedMapping() {
std::array<ShaderSemantic, 5> inputs{
semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)};
inputs[0].is_flat_shaded = 1;
inputs[0].default_value = 1;
inputs[0].default_value_hi = 3;
inputs[1].default_value_hi = 1;
inputs[2].is_flat_shaded = 1;
inputs[2].default_value_hi = 2;
inputs[3].is_flat_shaded = 1;
inputs[3].default_value = 2;
std::array<ShaderSemantic, 4> outputs{semantic(1, 7, 3), semantic(2, 4, 1), semantic(3, 9, 0), semantic(4, 2, 1)};
Shader gs{};
gs.output_semantics = outputs.data();
gs.num_output_semantics = static_cast<std::uint16_t>(outputs.size());
Shader ps{};
ps.input_semantics = inputs.data();
ps.num_input_semantics = static_cast<std::uint32_t>(inputs.size());
const std::array<std::uint32_t, 5> flatDefault{0x02680527u, 0x02380024u, 0x02580020u, 0x00000629u, 0x03180002u};
const std::size_t written = inputs.size() * sizeof(ShaderRegister);
const auto untouched = filled();
auto create = filled();
check(sceAgcCreateInterpolantMapping_nid_postfix(create.data(), &gs, &ps) == 0, "unversioned mapping failed");
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
check(create[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i && create[i].value == flatDefault[i], "unversioned mapping changed");
}
checkIdentity(create, static_cast<std::uint32_t>(inputs.size()), "unused interpolants are not the identity");
auto update = filled();
check(sceAgcUpdateInterpolantMapping_nid_postfix(update.data(), &gs, &ps) == 0, "unversioned update failed");
check(std::memcmp(update.data(), create.data(), written) == 0, "unversioned update differs from its mapping");
check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "unversioned update wrote past its inputs");
}
void testRejections() {
@@ -208,8 +243,9 @@ int main() {
testIdentity();
testMapping();
testUpdateMapping();
testUnversionedMapping();
testRejections();
check(mappings == 7u && links == 5u, "mapping preparation call count changed");
check(mappings == 10u && links == 8u, "mapping preparation call count changed");
LibcRunShutdown_nid_postfix();
std::puts("AGC interpolant mapping tests passed");
return 0;
+41 -2
View File
@@ -433,6 +433,16 @@ if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
endif()
if(BUILD_TESTING)
add_test_executable(agc_driver_unsupported_diagnostics_tests tests/UnsupportedDiagnostics.cpp)
target_include_directories(agc_driver_unsupported_diagnostics_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_driver_unsupported_diagnostics_tests PRIVATE libSceAgcDriver)
configure_windows_unwind(agc_driver_unsupported_diagnostics_tests)
add_test(NAME agc_driver_unsupported_diagnostics COMMAND agc_driver_unsupported_diagnostics_tests)
if(WIN32)
set_tests_properties(agc_driver_unsupported_diagnostics PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAgcDriver>")
endif()
add_test_executable(agc_driver_draw_program_trace_tests
tests/DrawProgramTrace.cpp
Execution/src/Driver/Draw/DrawProgramTrace.cpp
@@ -459,6 +469,16 @@ if(WIN32)
endif()
if(BUILD_TESTING)
add_test_executable(agc_driver_nop_data_packet_tests tests/NopDataWritePacket.cpp)
target_include_directories(agc_driver_nop_data_packet_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_driver_nop_data_packet_tests PRIVATE libSceAgcDriver)
configure_windows_unwind(agc_driver_nop_data_packet_tests)
add_test(NAME agc_driver_nop_data_packet COMMAND agc_driver_nop_data_packet_tests)
if(WIN32)
set_tests_properties(agc_driver_nop_data_packet PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(agc_frame_dump_io_tests tests/FrameDumpIo.cpp)
target_include_directories(agc_frame_dump_io_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_frame_dump_io_tests PRIVATE libc)
@@ -1653,9 +1673,17 @@ if(BUILD_TESTING)
ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30)
set_tests_properties(agc_driver_unit_shadow_budget PROPERTIES
ENVIRONMENT "APS5_PIN_WAIT_MS=2000;APS5_UNIT_SHADOW_MIB=8" SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_recorder_completion_labels COMMAND agc_driver_recorder_tests --completion-labels-only)
set_tests_properties(agc_recorder_completion_labels PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_recorder_cmask COMMAND agc_driver_recorder_tests --cmask-only)
set_tests_properties(agc_recorder_cmask PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_recorder_cube COMMAND agc_driver_recorder_tests --cube-only)
set_tests_properties(agc_recorder_cube PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_host_import_lifetime agc_driver_host_import_mutation_race agc_driver_unit_shadow agc_driver_unit_shadow_budget PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_recorder_completion_labels agc_recorder_cmask agc_recorder_cube PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_property(TEST agc_driver_host_import_lifetime APPEND PROPERTY ENVIRONMENT_MODIFICATION
"APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:")
@@ -2043,13 +2071,23 @@ if(BUILD_TESTING)
add_test(NAME agc_driver_store_pending_identity COMMAND agc_driver_store_over_untracked_tests --pending-identity)
set_tests_properties(agc_driver_store_pending_identity PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_store_idle_results_wake COMMAND agc_driver_store_idle_results_tests --idle-epoch-wake)
set_tests_properties(agc_driver_store_idle_results_wake 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_store_pending_identity PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING)
add_test(NAME agc_driver_native_fmask COMMAND agc_driver_sample_multisample_target_tests --native-fmask)
set_tests_properties(agc_driver_native_fmask PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_native_fmask PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_native_msaa_vertex_input COMMAND agc_driver_sample_multisample_target_tests --native-vertex-input)
set_tests_properties(agc_driver_native_msaa_vertex_input PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
@@ -2827,15 +2865,16 @@ if(BUILD_TESTING)
endif()
endforeach()
add_test(NAME agc_driver_shutdown_threads_joined COMMAND agc_driver_shutdown_threads_tests --joined)
add_test(NAME agc_driver_shutdown_threads_queued_prebuilds COMMAND agc_driver_shutdown_threads_tests --queued-prebuilds)
add_test(NAME agc_driver_shutdown_threads_compare_helpers COMMAND agc_driver_shutdown_threads_tests --compare-helpers)
add_test(NAME agc_driver_shutdown_threads_draw_committer COMMAND agc_driver_shutdown_threads_tests --draw-committer)
add_test(NAME agc_driver_shutdown_threads_shader_cache_writer COMMAND agc_driver_shutdown_threads_tests --shader-cache-writer)
add_test(NAME agc_driver_shutdown_threads_late_shader_cache COMMAND agc_driver_shutdown_threads_tests --late-shader-cache)
add_test(NAME agc_driver_shutdown_threads_async_log_held COMMAND agc_driver_shutdown_threads_tests --async-log-held)
add_test(NAME agc_driver_shutdown_threads_exit_after_start COMMAND agc_driver_shutdown_threads_tests --exit-after-start)
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_queued_prebuilds agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start 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_shutdown_threads_joined agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_queued_prebuilds agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_compute_user_data_calls_registered COMMAND agc_driver_compute_user_data_calls_tests --registered)
set_tests_properties(agc_driver_compute_user_data_calls_registered PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
@@ -487,7 +487,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
results.push_back(ShaderRecompiler::BuildBarycentricGeometryShader(*programResults[0], *programResults[1], localDevice->Target(), geometryLimits));
stages.insert(stages.begin() + 1, Graphics::CompiledShader{Stage::Geometry, &results.back(), 0});
} else {
require(graphics.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST && !graphics.primitiveRestart, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric or geometry shaders needs a triangle list without primitive restart");
require((graphics.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || graphics.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) && !graphics.primitiveRestart, "a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric or geometry shaders needs a triangle list or strip without primitive restart");
auto patches = ShaderRecompiler::BuildBarycentricTessellationShaders(*programResults[0], *programResults[1], localDevice->Target(), localDevice->MaxFragmentInputComponents(), graphics.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT ? 2u : 0u);
const auto first = results.size();
results.push_back(std::move(patches.control));
@@ -263,7 +263,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
if (x.binary.stage != y.binary.stage || x.binary.codeAddress != y.binary.codeAddress || x.userDataBase != y.userDataBase || x.firstUserSgpr != y.firstUserSgpr || x.userData != y.userData || x.snapshot != y.snapshot || x.codeOffset != y.codeOffset || x.tailContinuation != y.tailContinuation) return false;
}
if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.reversedBlend != q.reversedBlend || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive || p.ditheredAlphaToCoverage != q.ditheredAlphaToCoverage || p.quadPixelMask != q.quadPixelMask || p.quadSampleMask != q.quadSampleMask || p.iteratesSamples != q.iteratesSamples || p.positionLocation != q.positionLocation || p.sampleLocations != q.sampleLocations || p.sampleOrder != q.sampleOrder) return false;
if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ) return false;
if (s.samples != t.samples || s.mergedCoverage != t.mergedCoverage || s.truncatedCoverage != t.truncatedCoverage || s.quadSampleMask != t.quadSampleMask || s.pixelEarlyZ != t.pixelEarlyZ) return false;
return true;
}
@@ -1146,9 +1146,8 @@ public:
Closing() = true;
pools = {Existing(), ExistingPrebuild()};
}
for (auto* pool : pools) {
if (pool != nullptr) pool->Close();
}
if (pools[0] != nullptr) pools[0]->Close(false);
if (pools[1] != nullptr) pools[1]->Close(true);
}
private:
@@ -1204,11 +1203,17 @@ public:
idle.wait(lock, [&] { return unfinished.empty() || *unfinished.begin() > ticket; });
}
void Close() {
void Close(bool dropQueued) {
std::vector<Worker> joined;
std::deque<Task> dropped;
{
std::unique_lock lock(mutex);
closed = true;
if (dropQueued) {
dropped.swap(tasks);
for (const auto& task : dropped) unfinished.erase(task.sequence);
idle.notify_all();
}
idle.wait(lock, [&] { return tasks.empty() && running == 0; });
joined.swap(workers);
}
@@ -16,8 +16,12 @@ namespace {
std::atomic<bool> requested{false};
std::filesystem::path directory() {
#ifdef _WIN32
const wchar_t* configured = _wgetenv(L"ANYPS5_SCREENSHOTS");
#else
const char* configured = std::getenv("ANYPS5_SCREENSHOTS");
if (configured != nullptr && configured[0] != '\0') return configured;
#endif
if (configured != nullptr && configured[0] != 0) return configured;
const std::unique_ptr<char, decltype(&SDL_free)> basePath(SDL_GetBasePath(), SDL_free);
if (!basePath) throw std::runtime_error(std::string("Screenshot: cannot locate the executable: ") + SDL_GetError());
return std::filesystem::path(reinterpret_cast<const char8_t*>(basePath.get())) / "anyps5-screenshots";
@@ -386,6 +386,8 @@ struct VulkanDevice::State {
bool samplerFilterMinmax = false;
bool nonSeamlessCubeMap = false;
VkSampleCountFlags mergedCoverageSampleCounts = 0;
VkSampleCountFlags truncatedCoverageSampleCounts = 0;
bool mixedAttachmentSamples = false;
bool fragmentShaderPixelInterlock = false;
bool conservativeRasterization = false;
bool provokingVertexLast = false;
@@ -1515,12 +1517,17 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
check(combinations(selected, &count, supported.data()), "vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV");
for (const auto& combination : supported) {
if (combination.coverageReductionMode == VK_COVERAGE_REDUCTION_MODE_MERGE_NV && combination.colorSamples == VK_SAMPLE_COUNT_1_BIT && combination.depthStencilSamples == 0) state->mergedCoverageSampleCounts |= combination.rasterizationSamples;
if (combination.coverageReductionMode == VK_COVERAGE_REDUCTION_MODE_TRUNCATE_NV && combination.colorSamples == VK_SAMPLE_COUNT_1_BIT && combination.depthStencilSamples == combination.rasterizationSamples) state->truncatedCoverageSampleCounts |= combination.rasterizationSamples;
}
}
if (state->mergedCoverageSampleCounts != 0) {
if (state->mergedCoverageSampleCounts != 0 || state->truncatedCoverageSampleCounts != 0) {
deviceExtensions.push_back(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME);
deviceExtensions.push_back(VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME);
}
} else if (hasExtension(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME)) {
state->mixedAttachmentSamples = true;
state->truncatedCoverageSampleCounts = state->properties.limits.framebufferDepthSampleCounts & state->properties.limits.framebufferColorSampleCounts & ~static_cast<VkSampleCountFlags>(VK_SAMPLE_COUNT_1_BIT);
deviceExtensions.push_back(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME);
}
coverageReductionFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV};
coverageReductionFeatures.coverageReductionMode = VK_TRUE;
@@ -1811,7 +1818,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
nonSeamlessCubeFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &nonSeamlessCubeFeatures;
}
if (state->mergedCoverageSampleCounts != 0) {
if (!state->mixedAttachmentSamples && (state->mergedCoverageSampleCounts != 0 || state->truncatedCoverageSampleCounts != 0)) {
coverageReductionFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &coverageReductionFeatures;
}
@@ -3814,6 +3821,8 @@ Graphics::Context VulkanDevice::buildContext() const {
context.samplerFilterMinmax = state->samplerFilterMinmax;
context.nonSeamlessCubeMap = state->nonSeamlessCubeMap;
context.mergedCoverageSampleCounts = state->mergedCoverageSampleCounts;
context.truncatedCoverageSampleCounts = state->truncatedCoverageSampleCounts;
context.mixedAttachmentSamples = state->mixedAttachmentSamples;
context.conservativeRasterization = state->conservativeRasterization;
context.meshWave32 = state->meshWave32;
context.provokingVertexLast = state->provokingVertexLast;
@@ -4937,6 +4946,10 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
recordStep(PhaseRecordBind);
if (record.htileClear.has_value()) Graphics::ArmHtileDepthClear(record.htileClear->first, record.htileClear->second);
resources.MarkGpuWrites(recorder);
if (record.htileClear.has_value()) {
Graphics::QueueHtileDepthClear(record.htileClear->first, record.htileClear->second);
recorder.NotePendingWrite(record.htileClear->first, record.htileClear->second);
}
recordStep(PhaseRecordMarks);
// Only copied written buffers (and BDA fault checks) need work once the GPU is done; without them
// the batch can signal its labels from the GPU.
@@ -213,6 +213,8 @@ struct Context {
bool samplerFilterMinmax = false;
bool nonSeamlessCubeMap = false;
VkSampleCountFlags mergedCoverageSampleCounts = 0;
VkSampleCountFlags truncatedCoverageSampleCounts = 0;
bool mixedAttachmentSamples = false;
bool conservativeRasterization = false;
bool meshWave32 = false;
VkBuffer emptyBuffer = VK_NULL_HANDLE;
@@ -29,6 +29,7 @@ struct DepthPlaneStoreStatistics {
std::uint64_t bytes = 0;
};
DepthPlaneStoreStatistics DepthPlaneStoreCounts();
void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target);
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples);
std::uint64_t DepthSurfacesSerial();
std::uint64_t DepthSurfacesReleased();
@@ -56,6 +57,7 @@ std::uint64_t HtileDepthClearAddress(std::span<const std::uint32_t> code, std::s
std::size_t HtileDepthClearBytes_nid_no_patch(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, std::span<const std::uint32_t> packet, std::uint64_t indirectArguments);
bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::size_t nativeBytes = 0u);
void ArmHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
void QueueHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
struct MemoryValueFill {
std::uint64_t address;
@@ -90,6 +90,7 @@ struct GuestTextureResource {
std::uint32_t minLod = 0;
std::uint32_t allocatedMipCount = 0;
std::uint32_t samples = 1;
std::uint64_t fmaskCmaskAddress = 0;
};
float EffectiveMinLod(const GuestTextureResource& resource);
@@ -76,6 +76,7 @@ bool MultisampleTargetStorable(const Context& context, const ColorTarget& target
void ResolveMultisampleTarget(const Context& context, const ColorTarget& source, StorageTexture& destination, VkImageView destinationView);
bool MultisampledColorFillAbsorbs(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
std::shared_ptr<Texture> MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components);
void ClearMultisampleTargets(VkDevice device);
void InstallResidentImageReadGuard_nid_no_patch();
void UpdateResidentImageUnmapObserver_nid_no_patch();
@@ -68,6 +68,8 @@ struct ColorTarget {
std::uint32_t pipeBankXor = 0;
std::uint32_t samples = 1;
std::uint32_t dccControl = 0;
std::uint64_t fmaskAddress = 0;
std::uint32_t fmaskPipeBankXor = 0;
};
struct DepthTarget {
@@ -140,6 +142,7 @@ struct State {
bool ditheredAlphaToCoverage = false;
bool perSampleShading = false;
bool mergedCoverage = false;
bool truncatedCoverage = false;
VkConservativeRasterizationModeEXT conservativeRasterization = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
VkRect2D scissor;
VkCullModeFlags cullMode;
@@ -58,6 +58,7 @@ struct DepthGuard {
std::size_t bytes;
std::shared_ptr<std::atomic<bool>> active;
std::vector<GuestMemory::UnchangedQuery> live;
std::shared_ptr<std::atomic<bool>> hostWritten;
};
std::shared_mutex& depthGuardMutex() {
@@ -492,6 +493,7 @@ public:
void CopyAspectFrom(const DepthSurface& source, VkImageAspectFlagBits aspect) {
ForgetClear(aspect);
NoteHostWrite();
auto* recorder = RecorderFor(context);
std::unique_ptr<CommandBatch> batch;
if (recorder == nullptr) batch = std::make_unique<CommandBatch>(context);
@@ -541,7 +543,12 @@ public:
return std::any_of(textures.begin(), textures.end(), [](const auto& entry) { return entry.second.use_count() > 1; });
}
void NoteHostWrite() {
hostWritten->store(true, std::memory_order_release);
}
std::uint64_t written = ++depthWrites;
std::shared_ptr<std::atomic<bool>> hostWritten = std::make_shared<std::atomic<bool>>(false);
bool retired = false;
bool Overlaps(std::uint64_t address, std::uint64_t bytes) const {
@@ -691,6 +698,7 @@ public:
if (plane.storage.get() == &candidate) {
if (stencil || !StoreOverwrite(candidate)) copyPlane(plane, plane.storage, stencil, false);
written = ++depthWrites;
NoteHostWrite();
return true;
}
if (std::none_of(plane.replaced.begin(), plane.replaced.end(), [&](const auto& old) { return old.lock().get() == &candidate; })) continue;
@@ -708,6 +716,7 @@ public:
const auto source = std::const_pointer_cast<StorageTexture>(candidate.shared_from_this());
copyPlane(plane, source, stencil, false);
written = ++depthWrites;
NoteHostWrite();
return true;
}
return false;
@@ -913,6 +922,7 @@ public:
DepthSamplingState sampling;
bool clearPending = false;
std::uint64_t clearGeneration = 0u;
bool clearCompletionQueued = false;
std::shared_ptr<OwnedImage> owned;
std::shared_ptr<OwnedImage> sampleShadow;
bool useSampleShadow = false;
@@ -1529,6 +1539,21 @@ DepthSurface* planeSurface(VkDevice device, std::uint64_t address) {
return found == list.rend() ? nullptr : found->get();
}
void settlePendingHtileClear(const Context& context, std::uint64_t address) {
if (Recorder::InCompletion()) return;
auto* recorder = RecorderFor(context);
if (recorder == nullptr) return;
std::pair<std::uint64_t, std::size_t> metadata;
{
std::lock_guard lock(surfacesMutex());
const auto* surface = planeSurface(context.device, address);
if (surface == nullptr || !surface->samplingActive || surface->clearGeneration == 0u || !surface->clearCompletionQueued || !surface->sampling.refusal.empty()) return;
const auto& range = surface->sampling.initial[1];
metadata = {range.address, range.bytes};
}
if (recorder->PendingWriteOverlaps(metadata.first, metadata.second)) recorder->SyncThrough(metadata.first, metadata.second);
}
std::map<std::uint64_t, std::size_t>& clearedHtiles() {
static auto* cleared = new std::map<std::uint64_t, std::size_t>();
return *cleared;
@@ -1828,8 +1853,28 @@ std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes) {
if (bytes == 0u || !anyDepthGuard().load(std::memory_order_acquire)) return;
Require(address <= std::numeric_limits<std::uint64_t>::max() - bytes, "native depth read range overflows");
std::shared_lock lock(depthGuardMutex());
for (const auto& range : depthGuards()) Require(address >= range.address + range.bytes || range.address >= address + bytes, "sample-preserving guest publication of native multisample depth is not implemented");
const auto overlaps = [&](const DepthGuard& range) { return address < range.address + range.bytes && range.address < address + bytes; };
{
std::shared_lock lock(depthGuardMutex());
if (std::ranges::none_of(depthGuards(), overlaps)) return;
}
std::unique_lock lock(depthGuardMutex());
for (const auto& range : depthGuards()) {
if (overlaps(range)) Require(!range.hostWritten->load(std::memory_order_acquire), "sample-preserving guest publication of native multisample depth is not implemented");
}
for (const auto& range : depthGuards()) {
if (overlaps(range)) range.active->store(false, std::memory_order_release);
}
std::erase_if(depthGuards(), [](const auto& range) { return !range.active->load(std::memory_order_acquire); });
anyDepthGuard().store(!depthGuards().empty(), std::memory_order_release);
}
void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target) {
if (target.samples == 1u || !anyDepthGuard().load(std::memory_order_acquire)) return;
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
if (surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && sameSurface(surface->target, target)) surface->NoteHostWrite();
}
}
bool HasResidentDepthGuards_nid_no_patch() {
@@ -1902,6 +1947,7 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const
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());
settlePendingHtileClear(context, target.address);
std::unique_lock lock(surfacesMutex());
const bool cleared = takeClearedHtile(target);
DepthSurface* surface = nullptr;
@@ -1938,7 +1984,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target,
auto& fresh = *surfaces().back();
if (target.samples != 1u && fresh.sampleable && fresh.sampling.refusal.empty()) {
std::unique_lock guardLock(depthGuardMutex());
for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}});
for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}, fresh.hostWritten});
anyDepthGuard().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
fresh.samplingActive = true;
@@ -2199,6 +2245,7 @@ bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::siz
else if (surface->clearPending || surface->clearGeneration != 0u) surface->sampling.Refuse("overlapping native multisample depth clear transitions are not implemented");
else {
surface->clearPending = true;
surface->clearCompletionQueued = false;
completionCandidate = true;
}
existing = true;
@@ -2225,6 +2272,14 @@ void ArmHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
}
}
void QueueHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
if (!surface->active->load(std::memory_order_acquire) || !surface->samplingActive || surface->target.htileAddress != htileAddress || surface->clearGeneration == 0u || !surface->sampling.refusal.empty()) continue;
if (bytes >= surface->sampling.initial[1].bytes) surface->clearCompletionQueued = true;
}
}
void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
@@ -2233,6 +2288,7 @@ void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
if (bytes < metadata.bytes) continue;
Require(GuestMemory::UnchangedSinceCollected(metadata.address, metadata.bytes, surface->clearGeneration), "CPU writes raced native multisample depth clear");
GuestMemory::MarkWritten(metadata.address, metadata.bytes);
surface->clearCompletionQueued = false;
}
}
@@ -2261,6 +2317,7 @@ std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<c
if (std::none_of(surfaces().begin(), surfaces().end(), held)) return nullptr;
}
std::lock_guard gpu(GuestMemory::GpuMutex());
settlePendingHtileClear(context, resource.baseAddress);
std::lock_guard lock(surfacesMutex());
auto* found = planeSurface(context.device, resource.baseAddress);
if (found == nullptr) return nullptr;
@@ -2323,6 +2380,7 @@ bool DepthSurfaceHolds(const Context& context, const GuestTextureResource& resou
if ((resource.dimension != TextureDimension::k2D && resource.dimension != TextureDimension::k2DMsaa) || resource.baseArray != 0) return false;
std::lock_guard gpu(GuestMemory::GpuMutex());
const auto address = resource.baseAddress;
settlePendingHtileClear(context, address);
std::lock_guard lock(surfacesMutex());
auto* found = planeSurface(context.device, address);
if (found == nullptr || !found->Holds(texture)) return false;
@@ -2484,7 +2542,10 @@ bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage
if (std::get<0>(key) != context.device || entry->storage.get() != &storage) continue;
const auto slices = layeredSlices(context, *entry);
entry->Copy(slices, false);
for (const auto& slice : slices) slice.surface->written = ++depthWrites;
for (const auto& slice : slices) {
slice.surface->written = ++depthWrites;
slice.surface->NoteHostWrite();
}
return true;
}
for (const auto& surface : surfaces()) {
@@ -55,6 +55,7 @@ std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std:
mix(state.renderExtent.height);
mix(state.samples);
mix(state.mergedCoverage);
mix(state.truncatedCoverage);
mix(state.customSampleLocations);
if (state.customSampleLocations) {
mix(state.sampleLocationGrid);
@@ -81,6 +82,10 @@ GuestTextureResource ArraySurfaceForTarget(const ColorTarget& color) {
namespace {
bool writesDepthTarget(const State& state) {
return state.depthWrite || (state.stencilTest && (state.stencilFront.writeMask != 0u || state.stencilBack.writeMask != 0u));
}
std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) {
if (color.mipCount != 1 || color.depth != 1 || !color.dccPipeAligned) return DccKeyBytes(bytes);
return DccKeyCount(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, bytes);
@@ -1043,9 +1048,14 @@ struct DrawInputs {
VkPipelineStageFlags shaderStages = 0;
std::uint32_t meshGroups = 0;
std::vector<std::uint32_t> restartTable;
std::uint32_t expandedIndexCount = 0;
const VertexInputLayout& VertexLayout() const { return retainedVertexInput != nullptr ? *retainedVertexInput : vertexInput; }
};
bool StripPatches(const State& state, std::span<const CompiledShader> shaders) {
return state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP && BarycentricPatches(state, shaders);
}
std::shared_ptr<Buffer> zeroVertexBuffer(const Context& context, const ShaderRecompiler::VertexAttribute& attribute) {
auto zero = std::make_shared<Buffer>(context, DecodeVertexFormat(attribute).bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
std::fill(zero->Bytes().begin(), zero->Bytes().end(), std::byte{0});
@@ -1129,19 +1139,58 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
ValidateSampleMask(context, state, shaders);
timer.phase(PhaseValidate);
inputs.maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u;
if (state.quadList) {
Require(!draw.indirect, "indirect quad-list draw is unsupported");
const std::uint32_t quadCount = draw.indexCount / 4u;
if (quadCount == 0u || draw.instanceCount == 0u) {
if (state.quadList || StripPatches(state, shaders)) {
Require(!draw.indirect, state.quadList ? "indirect quad-list draw is unsupported" : "an indirect triangle strip through emulated barycentric patches is unsupported");
const std::uint32_t primitives = state.quadList ? draw.indexCount / 4u : draw.indexCount >= 3u ? draw.indexCount - 2u : 0u;
if (primitives == 0u || draw.instanceCount == 0u) {
inputs.nothing = true;
return inputs;
}
const std::uint32_t outputIndexCount = quadCount * 6u;
const std::size_t quadIndexBytes = static_cast<std::size_t>(outputIndexCount) * sizeof(std::uint32_t);
auto indexCopy = std::make_shared<Buffer>(context, quadIndexBytes, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
const std::uint32_t corners = state.quadList ? 6u : 3u;
const std::uint32_t outputIndexCount = primitives * corners;
const std::size_t expandedIndexBytes = static_cast<std::size_t>(outputIndexCount) * sizeof(std::uint32_t);
auto indexCopy = std::make_shared<Buffer>(context, expandedIndexBytes, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
auto* outIndices = reinterpret_cast<std::uint32_t*>(indexCopy->Bytes().data());
std::uint32_t maxIdx = 0;
const bool lastProvoking = state.provokingVertexMode == VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;
const auto expand = [&](const auto& source) {
for (std::uint32_t primitive = 0; primitive < primitives; ++primitive) {
auto* out = outIndices + static_cast<std::size_t>(primitive) * corners;
if (state.quadList) {
const auto i0 = source(primitive * 4u + 0u);
const auto i1 = source(primitive * 4u + 1u);
const auto i2 = source(primitive * 4u + 2u);
const auto i3 = source(primitive * 4u + 3u);
if (lastProvoking) {
out[0] = i0;
out[1] = i1;
out[2] = i3;
out[3] = i1;
out[4] = i2;
out[5] = i3;
} else {
out[0] = i0;
out[1] = i1;
out[2] = i2;
out[3] = i0;
out[4] = i2;
out[5] = i3;
}
} else {
const std::uint32_t odd = primitive & 1u;
if (lastProvoking) {
out[0] = source(primitive + odd);
out[1] = source(primitive + 1u - odd);
out[2] = source(primitive + 2u);
} else {
out[0] = source(primitive);
out[1] = source(primitive + 1u + odd);
out[2] = source(primitive + 2u - odd);
}
}
for (std::uint32_t corner = 0; corner < corners; ++corner) maxIdx = std::max(maxIdx, out[corner]);
}
};
if (draw.indexed) {
GuestMemory::FlushGpuWrites(draw.indexAddress, static_cast<std::size_t>(indexBytes));
GuestMemory::CheckRange(reinterpret_cast<const void*>(draw.indexAddress), static_cast<std::size_t>(indexBytes), draw.indexSize);
@@ -1156,59 +1205,18 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
return v;
}
};
for (std::uint32_t q = 0; q < quadCount; ++q) {
const auto i0 = readIndex(q * 4u + 0u);
const auto i1 = readIndex(q * 4u + 1u);
const auto i2 = readIndex(q * 4u + 2u);
const auto i3 = readIndex(q * 4u + 3u);
maxIdx = std::max({maxIdx, i0, i1, i2, i3});
if (lastProvoking) {
outIndices[q * 6u + 0u] = i0;
outIndices[q * 6u + 1u] = i1;
outIndices[q * 6u + 2u] = i3;
outIndices[q * 6u + 3u] = i1;
outIndices[q * 6u + 4u] = i2;
outIndices[q * 6u + 5u] = i3;
} else {
outIndices[q * 6u + 0u] = i0;
outIndices[q * 6u + 1u] = i1;
outIndices[q * 6u + 2u] = i2;
outIndices[q * 6u + 3u] = i0;
outIndices[q * 6u + 4u] = i2;
outIndices[q * 6u + 5u] = i3;
}
}
expand(readIndex);
Require(draw.firstVertex <= std::numeric_limits<std::uint32_t>::max() - maxIdx, "indexed draw vertex range overflow");
inputs.maxIndex = maxIdx + draw.firstVertex;
} else {
maxIdx = quadCount * 4u - 1u;
Require(draw.firstVertex <= std::numeric_limits<std::uint32_t>::max() - maxIdx, "auto draw vertex range overflow");
for (std::uint32_t q = 0; q < quadCount; ++q) {
const auto i0 = q * 4u + 0u;
const auto i1 = q * 4u + 1u;
const auto i2 = q * 4u + 2u;
const auto i3 = q * 4u + 3u;
if (lastProvoking) {
outIndices[q * 6u + 0u] = i0;
outIndices[q * 6u + 1u] = i1;
outIndices[q * 6u + 2u] = i3;
outIndices[q * 6u + 3u] = i1;
outIndices[q * 6u + 4u] = i2;
outIndices[q * 6u + 5u] = i3;
} else {
outIndices[q * 6u + 0u] = i0;
outIndices[q * 6u + 1u] = i1;
outIndices[q * 6u + 2u] = i2;
outIndices[q * 6u + 3u] = i0;
outIndices[q * 6u + 4u] = i2;
outIndices[q * 6u + 5u] = i3;
}
}
inputs.maxIndex = maxIdx + draw.firstVertex;
const std::uint32_t lastVertex = state.quadList ? primitives * 4u - 1u : primitives + 1u;
Require(draw.firstVertex <= std::numeric_limits<std::uint32_t>::max() - lastVertex, "auto draw vertex range overflow");
expand([](std::uint32_t idx) { return idx; });
}
inputs.maxIndex = maxIdx + draw.firstVertex;
Require(inputs.maxIndex <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit");
inputs.indexHandle = indexCopy->Handle();
inputs.indexOffset = 0;
inputs.expandedIndexCount = outputIndexCount;
inputs.indices = std::move(indexCopy);
} else if (draw.indexed) {
const bool listTopology = state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
@@ -1491,9 +1499,9 @@ void recordDrawCommands(const Context& context, VkCommandBuffer commands, const
return;
}
if (!inputs.vertexHandles.empty()) context.Resolved(&DeviceFunctions::cmdBindVertexBuffers, "vkCmdBindVertexBuffers")(commands, 0, static_cast<std::uint32_t>(inputs.vertexHandles.size()), inputs.vertexHandles.data(), inputs.vertexOffsets.data());
if (state.quadList) {
if (inputs.expandedIndexCount != 0u) {
context.Resolved(&DeviceFunctions::cmdBindIndexBuffer, "vkCmdBindIndexBuffer")(commands, inputs.indexHandle, inputs.indexOffset, VK_INDEX_TYPE_UINT32);
context.Resolved(&DeviceFunctions::cmdDrawIndexed, "vkCmdDrawIndexed")(commands, (draw.indexCount / 4u) * 6u, draw.instanceCount, 0, static_cast<std::int32_t>(draw.firstVertex), draw.firstInstance);
context.Resolved(&DeviceFunctions::cmdDrawIndexed, "vkCmdDrawIndexed")(commands, inputs.expandedIndexCount, draw.instanceCount, 0, static_cast<std::int32_t>(draw.firstVertex), draw.firstInstance);
return;
}
if (draw.indexed) context.Resolved(&DeviceFunctions::cmdBindIndexBuffer, "vkCmdBindIndexBuffer")(commands, inputs.indexHandle, inputs.indexOffset, draw.indexSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
@@ -2230,7 +2238,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
Require(state.samples == 1u && (!state.depth || HasStencil(state.depth->format)), "stencil replay requires single-sample rendering and an eight-bit stencil format");
recipeOut = nullptr;
}
if (state.quadList) recipeOut = nullptr;
if (state.quadList || StripPatches(state, shaders)) recipeOut = nullptr;
if (context.recorder != nullptr) context.recorder->BoundKeptBytes();
auto& timing = prepared.timing.emplace("Graphics.Draw");
// APS5_PROFILE_DRAW prints the time of each phase of the draw (microseconds) and the [draws] totals.
@@ -2352,7 +2360,10 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
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, state.depthWrite));
if (state.depth) {
targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite));
if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth);
}
timer.phase(PhasePrepare);
const bool recordDraws = RecordDraws();
auto* recorder = prepared.recorder = Recorder::Active();
@@ -2955,7 +2966,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con
};
Require(!draw.indirect, "a draw recipe covers direct draws only");
auto* recorder = Recorder::Active();
if (state.quadList || !RecordDraws() || recorder == nullptr || DumpTargetLimit() != 0) return miss(DrawRecipeMiss::NotRecordable);
if (state.quadList || StripPatches(state, shaders) || !RecordDraws() || recorder == nullptr || DumpTargetLimit() != 0) return miss(DrawRecipeMiss::NotRecordable);
if (recipe.hasDepth != state.depth.has_value()) return miss(DrawRecipeMiss::TargetGone);
const std::size_t expectedViews = state.colors.size() + (recipe.hasDepth ? 1u : 0u);
Require(recipe.targets.size() == state.colors.size() && recipe.targetViews.size() == expectedViews, "draw recipe targets do not match the state");
@@ -2988,6 +2999,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con
if (recipe.hasDepth) {
timer.phase(PhaseSetup);
const auto depthView = DepthSurfaceView(context, *state.depth, state.depthWrite);
if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth);
timer.phase(PhaseReadTarget);
if (recipe.targetViews.back() != depthView) return miss(DrawRecipeMiss::TargetGone);
}
@@ -5,6 +5,8 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include <algorithm>
#include <array>
#include <optional>
#include <stdexcept>
#include <string>
@@ -50,6 +52,45 @@ TextureDimension resolveDimension(std::uint32_t raw) {
}
}
bool ZeroTail(std::span<const std::uint32_t> words) {
return words[4] == 0u && words[5] == 0u && words[6] == 0u && words[7] == 0u;
}
bool SameLevels(const TileMipLayout& a, const TileMipLayout& b) {
return a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.pitchBytes == b.pitchBytes && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY;
}
std::optional<std::array<std::uint32_t, 8>> R128ResidentLevelWords(std::span<const std::uint32_t> words) {
const auto baseLevel = (words[3] >> 12u) & 0xfu;
const auto lastLevel = (words[3] >> 16u) & 0xfu;
const auto typeRaw = (words[3] >> 28u) & 0xfu;
if (baseLevel == 0u || baseLevel > lastLevel || (typeRaw != 8u && typeRaw != 9u)) return std::nullopt;
const std::array<std::uint32_t, 8> chainWords{words[0], words[1], words[2], words[3], 0u, lastLevel << 4u, 0u, 0u};
const auto width = (((words[1] >> 30u) & 0x3u) | ((words[2] & 0xfffu) << 2u)) + 1u;
const auto height = ((words[2] >> 14u) & 0x3fffu) + 1u;
const auto minLod = (words[1] >> 8u) & 0xfffu;
const auto widthMinus1 = std::max(width >> baseLevel, 1u) - 1u;
const auto heightMinus1 = std::max(height >> baseLevel, 1u) - 1u;
const auto rebasedMinLod = minLod > baseLevel * 256u ? minLod - baseLevel * 256u : 0u;
const auto levels = lastLevel - baseLevel;
auto rebased = chainWords;
rebased[1] = (words[1] & ~((0xfffu << 8u) | (0x3u << 30u))) | (rebasedMinLod << 8u) | ((widthMinus1 & 0x3u) << 30u);
rebased[2] = (words[2] & ~(0xfffu | (0x3fffu << 14u))) | ((widthMinus1 >> 2u) & 0xfffu) | ((heightMinus1 & 0x3fffu) << 14u);
rebased[3] = (words[3] & ~(0xffu << 12u)) | (levels << 16u);
rebased[5] = levels << 4u;
try {
const auto chain = DescribeSurface(DecodeTextureResource(chainWords));
const auto view = DescribeSurface(DecodeTextureResource(rebased));
if (chain.mips.size() != lastLevel + 1u || view.mips.size() != levels + 1u || chain.layers != view.layers) return std::nullopt;
for (std::uint32_t level = baseLevel; level <= lastLevel; ++level) {
if (!SameLevels(chain.mips[level], view.mips[level - baseLevel])) return std::nullopt;
}
} catch (const std::exception&) {
return std::nullopt;
}
return rebased;
}
}
bool IsNullTextureResource(std::span<const std::uint32_t> words) {
@@ -140,7 +181,8 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
const bool fmask = ShaderRecompiler::IsFmaskTextureFormat(static_cast<ShaderRecompiler::IrBufferFormat>(format));
if (fmask) {
Require(format == static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "guest FMASK format is not implemented");
Require(!metaCompress && !writeCompress, "FMASK CMASK compression is not implemented");
Require(!writeCompress && (!metaCompress || metaAddr != 0u), "FMASK CMASK compression is not implemented without readable metadata");
Require(!metaCompress || metaPipeAligned, "FMASK CMASK metadata must be pipe aligned");
Require((typeRaw == 9u || typeRaw == 13u) && tileModeRaw == 24u && baseLevel == 0u && lastLevel == 0u && maxMip == 0u, "FMASK reads require a single-mip 2D or 2D-array Z_64KB_X surface");
for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented");
}
@@ -229,14 +271,19 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.dstSelZ = static_cast<std::uint8_t>(dstSelZ);
result.dstSelW = static_cast<std::uint8_t>(dstSelW);
result.bcSwizzle = static_cast<std::uint8_t>(bcSwizzle);
result.dccAddress = metaCompress ? metaAddr << 8u : 0u;
result.dccAddress = metaCompress && !fmask ? metaAddr << 8u : 0u;
result.fmaskCmaskAddress = metaCompress && fmask ? metaAddr << 8u : 0u;
result.dccAlphaOnMsb = dccAlphaPos;
result.dccPipeAligned = metaPipeAligned;
result.iterate256 = iterate256;
result.minLod = minLod;
result.samples = samples;
if (baseLevel > maxMip) {
Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe());
const bool fits = LevelsFitAllocation(result, lastLevel + 1u);
if (!fits && ZeroTail(words)) {
if (const auto resident = R128ResidentLevelWords(words)) return DecodeTextureResource(*resident);
}
Require(fits, "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe());
result.mipCount = lastLevel + 1u;
}
return result;
@@ -9,6 +9,8 @@
#include "prx/libSceAgcDriver/Graphics/include/SampleOrder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "IntermediateRepresentation/IrMetadata/BufferFormat.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <algorithm>
@@ -45,6 +47,43 @@ std::uint64_t collectBlocks(std::uint64_t address, std::size_t bytes) {
return generation != 0 ? generation : GuestMemory::CollectWritesUncached(address, bytes);
}
GuestTextureResource fmaskSnapshotResource(const GuestTextureResource& resource) {
Require(resource.format == static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt) && resource.fmaskCmaskAddress != 0u, "FMASK image format read semantics are not implemented");
Require(resource.dimension == TextureDimension::k2D || resource.dimension == TextureDimension::k2DArray, "FMASK image dimension is not implemented");
Require(resource.tileMode == TextureTileMode::kZ64KBX && (resource.pipeBankXor & ~0xcf00u) == 0u, "FMASK image swizzle or pipe/bank XOR is not implemented");
Require(resource.width != 0u && resource.width <= 16384u && resource.height != 0u && resource.height <= 16384u && resource.depthOrLastArray < 8192u && resource.baseArray <= resource.depthOrLastArray, "invalid FMASK image extent or layer range");
Require(resource.dimension == TextureDimension::k2DArray || (resource.depthOrLastArray == 0u && resource.baseArray == 0u), "FMASK 2D image has an array range");
Require(resource.mipCount == 1u && resource.allocatedMipCount == 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.minLod == 0u, "FMASK image mip fields are not implemented");
Require(resource.fmaskCmaskAddress == 0u || resource.dccPipeAligned, "FMASK image CMASK must be pipe aligned");
for (const auto selector : {resource.dstSelX, resource.dstSelY, resource.dstSelZ, resource.dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK image selects an unverified channel");
auto snapshot = resource;
snapshot.format = static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt);
snapshot.tileMode = TextureTileMode::kLinear;
snapshot.pipeBankXor = 0u;
snapshot.dccAddress = 0u;
snapshot.fmaskCmaskAddress = 0u;
snapshot.dccAlphaOnMsb = false;
snapshot.dccPipeAligned = false;
return snapshot;
}
GuestTextureResource fmaskResource(const ColorTarget& target) {
GuestTextureResource resource{};
resource.baseAddress = target.fmaskAddress;
resource.width = target.extent.width;
resource.height = target.extent.height;
resource.mipCount = 1u;
resource.allocatedMipCount = 1u;
resource.tileMode = TextureTileMode::kZ64KBX;
resource.dimension = TextureDimension::k2D;
resource.format = static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt);
resource.dstSelX = 4u;
resource.pipeBankXor = target.fmaskPipeBankXor;
resource.fmaskCmaskAddress = target.cmaskAddress;
resource.dccPipeAligned = true;
return resource;
}
bool sameTarget(const ColorTarget& a, const ColorTarget& b) {
return a.surfaceAddress == b.surfaceAddress && a.format == b.format && a.extent.width == b.extent.width && a.extent.height == b.extent.height && a.samples == b.samples;
}
@@ -240,6 +279,28 @@ public:
recorder.Keep(shared_from_this(), static_cast<std::size_t>(storable.bytes));
}
std::shared_ptr<Texture> FmaskSampled(const GuestTextureResource& resource, VkComponentMapping components) {
const auto snapshot = fmaskSnapshotResource(resource);
Require(target.samples == 8u && target.dccAddress != 0u && fmaskRange.has_value(), "native FMASK sampling requires a tracked eight-fragment DCC target");
Require(target.tileMode == ColorTileMode::RenderTarget && target.depth == 1u && target.depthSlice == 0u && target.layers == 1u && target.arrayLastSlice == 0u && target.arraySlice == 0u && target.mip == 0u && target.mipCount == 1u, "native FMASK target layout is not implemented");
Require(target.fmaskAddress == resource.baseAddress && target.fmaskPipeBankXor == resource.pipeBankXor && target.cmaskAddress == resource.fmaskCmaskAddress, "native FMASK descriptor does not match its resident target");
Require(target.extent.width == resource.width && target.extent.height == resource.height && resource.baseArray == 0u && resource.depthOrLastArray == 0u, "native FMASK extent or subresource does not match its resident target");
auto* recorder = Recorder::Active();
Require(recorder != nullptr && recorder->Device() == context.device && !Recorder::InCompletion(), "native FMASK sampling requires its device's active recorder outside completion");
ApplyFastClear(target);
Keep();
const std::array<std::uint32_t, 5> key{static_cast<std::uint32_t>(components.r), static_cast<std::uint32_t>(components.g), static_cast<std::uint32_t>(components.b), static_cast<std::uint32_t>(components.a), static_cast<std::uint32_t>(resource.dimension)};
auto& texture = fmaskTextures[key];
if (texture == nullptr) {
const auto geometry = DescribeSurface(snapshot);
std::vector<std::byte> bytes(geometry.guestBytes);
constexpr std::uint32_t expanded = 0x76543210u;
for (std::size_t offset = 0; offset < bytes.size(); offset += sizeof(expanded)) std::memcpy(bytes.data() + offset, &expanded, sizeof(expanded));
texture = std::make_unique<Texture>(context, *context.detiler, snapshot, components, bytes);
}
return {shared_from_this(), texture.get()};
}
void Keep() {
if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->Device() == context.device) {
recorder->Keep(shared_from_this());
@@ -269,6 +330,7 @@ public:
colorFill = MultisampledColorFill(use);
fromTexture = false;
dccRanges.clear();
fmaskRange.reset();
metadataValid = std::make_shared<std::atomic<bool>>(true);
const auto initial = initialDccClear(use);
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
@@ -450,6 +512,8 @@ private:
VkImageView storageView = VK_NULL_HANDLE;
bool storageWritten = false;
VkDeviceSize allocationBytes = 0;
std::optional<GuestMemory::UnchangedQuery> fmaskRange;
std::map<std::array<std::uint32_t, 5>, std::unique_ptr<Texture>> fmaskTextures;
std::map<std::array<std::uint32_t, 5>, std::shared_ptr<Texture>> textures;
std::vector<std::shared_ptr<Texture>> retiredTextures;
std::vector<GuestMemory::UnchangedQuery> dccRanges;
@@ -505,18 +569,29 @@ private:
Require(a.address >= b.address + b.bytes || b.address >= a.address + a.bytes, "overlapping multisampled DCC color or metadata ranges are not implemented");
}
}
if (use.samples == 8u && use.fmaskAddress != 0u && use.cmaskAddress != 0u) {
const auto resource = fmaskResource(use);
static_cast<void>(fmaskSnapshotResource(resource));
fmaskRange = GuestMemory::UnchangedQuery{use.fmaskAddress, static_cast<std::size_t>(DescribeSurface(resource).guestBytes), 0};
auto& range = *fmaskRange;
GuestMemory::CheckRange(reinterpret_cast<const void*>(range.address), range.bytes, 65536u);
Require(GuestMemory::Watched(range.address, range.bytes), "native FMASK contents require watched guest memory");
range.generation = GuestMemory::CollectWritesUncached(range.address, range.bytes);
Require(range.generation != 0, "native FMASK contents have no write generation");
for (const auto& other : dccRanges) Require(range.address >= other.address + other.bytes || other.address >= range.address + range.bytes, "native FMASK overlaps its color or metadata planes");
}
std::vector<std::byte> dcc(dccRanges[1].bytes);
GuestMemory::Read(use.dccAddress, dcc);
std::vector<std::byte> cmask(use.cmaskBytes);
if (!cmask.empty()) GuestMemory::Read(use.cmaskAddress, cmask);
const auto clear = MultisampledDccClear(use, dcc, cmask);
Require(GuestMemory::UnchangedSinceAll(dccRanges), "initial multisampled DCC contents changed during import");
Require(GuestMemory::UnchangedSinceAll(dccRanges) && (!fmaskRange || GuestMemory::UnchangedSinceAll(std::span(&*fmaskRange, 1u))), "initial multisampled DCC contents changed during import");
return clear;
}
void requireDccRange(bool valid, const GuestMemory::UnchangedQuery& range, const char* reason) const {
if (valid) return;
const auto* plane = range.address == target.address ? "color" : range.address == target.dccAddress ? "DCC" : "CMASK";
const auto* plane = range.address == target.address ? "color" : range.address == target.dccAddress ? "DCC" : range.address == target.fmaskAddress ? "FMASK" : "CMASK";
char text[384];
std::snprintf(text, sizeof(text), "%s (%s range 0x%llx+0x%zx, %u samples)", reason, plane, static_cast<unsigned long long>(range.address), range.bytes, target.samples);
Require(false, text);
@@ -537,9 +612,18 @@ private:
requireDccRange(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation) || !GuestMemory::StoredOver(range.address, range.bytes, range.generation), range, "writes to a resident multisampled DCC target or its metadata are not implemented");
if (auto* recorder = Recorder::Active(); recorder != nullptr) requireDccRange(!recorder->PendingWriteOverlaps(range.address, range.bytes), range, "pending writes to a resident multisampled DCC target or its metadata are not implemented");
}
if (fmaskRange) {
Require(use.fmaskAddress == target.fmaskAddress && use.fmaskPipeBankXor == target.fmaskPipeBankXor, "native FMASK target layout changed");
const auto& range = *fmaskRange;
requireDccRange(GuestMemory::Watched(range.address, range.bytes), range, "native FMASK sampling requires watched guest memory");
GuestMemory::CollectWritesUncached(range.address, range.bytes);
requireDccRange(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation) || !GuestMemory::StoredOver(range.address, range.bytes, range.generation), range, "writes to a resident native FMASK are not implemented");
if (auto* recorder = Recorder::Active(); recorder != nullptr) requireDccRange(!recorder->PendingWriteOverlaps(range.address, range.bytes), range, "pending writes to a resident native FMASK are not implemented");
}
}
void release() noexcept {
fmaskTextures.clear();
textures.clear();
retiredTextures.clear();
for (const auto& [format, resolvePass] : resolvePasses) context.Function<PFN_vkDestroyRenderPass>("vkDestroyRenderPass")(context.device, resolvePass, nullptr);
@@ -647,6 +731,7 @@ void guard(const Context& context, const ColorTarget& target, const std::shared_
if (target.dccAddress != 0) {
guarded().push_back({context.device, target.dccAddress, MultisampledDccBytes(target), target.samples, active, metadataValid});
if (target.cmaskAddress != 0) guarded().push_back({context.device, target.cmaskAddress, target.cmaskBytes, target.samples, active, metadataValid});
if (target.samples == 8u && target.fmaskAddress != 0u && target.cmaskAddress != 0u) guarded().push_back({context.device, target.fmaskAddress, DescribeSurface(fmaskResource(target)).guestBytes, target.samples, active, metadataValid});
}
anyGuarded().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
@@ -1166,6 +1251,20 @@ void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32
}
}
std::shared_ptr<Texture> MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components) {
std::lock_guard lock(targetsMutex());
discardRetiredTargets();
std::shared_ptr<MultisampleTarget> owner;
for (const auto& candidate : targets()) {
if (!candidate->active->load(std::memory_order_acquire) || candidate->context.device != context.device) continue;
const auto& target = candidate->target;
if (target.fmaskAddress != resource.baseAddress && (resource.fmaskCmaskAddress == 0u || target.cmaskAddress != resource.fmaskCmaskAddress)) continue;
Require(owner == nullptr, "FMASK descriptor refers to more than one resident target");
owner = candidate;
}
return owner != nullptr ? owner->FmaskSampled(resource, components) : nullptr;
}
void ClearMultisampleTargets(VkDevice device) {
std::lock_guard lock(targetsMutex());
std::erase_if(targets(), [&](const auto& target) { return target->context.device == device; });
@@ -112,6 +112,10 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp
}
return;
}
for (const auto& shader : shaders) {
if (shader.stage != ShaderRecompiler::ShaderStage::Fragment || shader.program == nullptr) continue;
Require(std::none_of(shader.program->fragmentParameters.begin(), shader.program->fragmentParameters.end(), [](const ShaderRecompiler::FragmentParameter& parameter) { return parameter.flatP20; }), "v_interp_mov p20 of a flat input with the last provoking vertex is not measured");
}
if (BarycentricPatches(state, shaders)) {
for (const auto& shader : shaders) {
if (shader.stage != ShaderRecompiler::ShaderStage::Fragment || shader.program == nullptr) continue;
@@ -259,6 +263,8 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
appendKey(keys.fragmentOutput, state.perSampleShading);
appendKey(keys.fragmentShader, state.mergedCoverage);
appendKey(keys.fragmentOutput, state.mergedCoverage);
appendKey(keys.fragmentShader, state.truncatedCoverage);
appendKey(keys.fragmentOutput, state.truncatedCoverage);
const auto sampleLocationsEnabled = ValidateSampleLocations(context, state);
appendKey(keys.fragmentShader, sampleLocationsEnabled);
appendKey(keys.fragmentOutput, sampleLocationsEnabled);
@@ -341,11 +347,12 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
Require(!state.negativeOneToOne || context.depthClipControl, "negative-one-to-one depth clipping requires VK_EXT_depth_clip_control with depthClipControl enabled");
Require(!state.perSampleShading || context.sampleRateShading, "per-sample pixel shading requires the sampleRateShading device feature");
Require(!state.mergedCoverage || (context.mergedCoverageSampleCounts & state.samples) != 0, "an overrasterized draw (a multisampled rasterizer into one-sample color targets) requires VK_NV_framebuffer_mixed_samples with merged coverage reduction");
Require(!state.truncatedCoverage || (context.truncatedCoverageSampleCounts & state.samples) != 0, "one-sample color targets beside a multisampled depth target require VK_AMD_mixed_attachment_samples or VK_NV_framebuffer_mixed_samples with truncated coverage reduction");
Require(state.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || context.conservativeRasterization, "conservative rasterization requires VK_EXT_conservative_rasterization with at most 1/256 pixel of overestimation and degenerate triangles rasterized");
ValidateProvokingVertex(context, state, shaders);
if (state.rectList) Require(context.tessellationShader && context.limits.maxTessellationPatchSize >= 4, "rect-list requires tessellation with four output control points");
const bool barycentricPatches = BarycentricPatches(state, shaders);
if (barycentricPatches) Require(context.tessellationShader && state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST && !state.primitiveRestart, "emulated barycentrics through tessellation require a triangle list without primitive restart");
if (barycentricPatches) Require(context.tessellationShader && (state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP) && !state.primitiveRestart, "emulated barycentrics through tessellation require a triangle list or strip without primitive restart");
if (std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.stage == ShaderRecompiler::ShaderStage::Geometry; })) Require(context.geometryShader, "device does not support geometry shaders");
if (state.stages.tessellation) {
Require(context.tessellationShader, "device does not support tessellation shaders");
@@ -403,7 +410,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
for (std::uint32_t index = 0; index < state.colors.size(); ++index) {
VkAttachmentDescription color{};
color.format = state.colors[index].format;
color.samples = state.mergedCoverage ? VK_SAMPLE_COUNT_1_BIT : static_cast<VkSampleCountFlagBits>(state.samples);
color.samples = state.mergedCoverage || state.truncatedCoverage ? VK_SAMPLE_COUNT_1_BIT : static_cast<VkSampleCountFlagBits>(state.samples);
color.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
@@ -440,7 +447,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
VkAttachmentSampleCountInfoNV attachmentSamples{VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV};
attachmentSamples.colorAttachmentCount = static_cast<std::uint32_t>(mergedColorSamples.size());
attachmentSamples.pColorAttachmentSamples = mergedColorSamples.empty() ? nullptr : mergedColorSamples.data();
if (state.mergedCoverage) rendering.pNext = &attachmentSamples;
if (state.truncatedCoverage) attachmentSamples.depthStencilAttachmentSamples = static_cast<VkSampleCountFlagBits>(state.samples);
if (state.mergedCoverage || state.truncatedCoverage) rendering.pNext = &attachmentSamples;
if (state.depth) {
rendering.depthAttachmentFormat = HostDepthFormat(context, state.depth->format);
if (HasStencil(state.depth->format)) rendering.stencilAttachmentFormat = rendering.depthAttachmentFormat;
@@ -514,8 +522,8 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
sampleLocations.sampleLocationsInfo = {VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT, nullptr, samples.rasterizationSamples, grid, state.samples * grid.width * grid.height, locations.data()};
if (sampleLocationsEnabled) samples.pNext = &sampleLocations;
VkPipelineCoverageReductionStateCreateInfoNV coverageReduction{VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_REDUCTION_STATE_CREATE_INFO_NV};
coverageReduction.coverageReductionMode = VK_COVERAGE_REDUCTION_MODE_MERGE_NV;
if (state.mergedCoverage) {
coverageReduction.coverageReductionMode = state.truncatedCoverage ? VK_COVERAGE_REDUCTION_MODE_TRUNCATE_NV : VK_COVERAGE_REDUCTION_MODE_MERGE_NV;
if (state.mergedCoverage || (state.truncatedCoverage && !context.mixedAttachmentSamples)) {
coverageReduction.pNext = samples.pNext;
samples.pNext = &coverageReduction;
}
@@ -893,6 +901,7 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
append(key, state.samples);
append(key, state.perSampleShading);
append(key, state.mergedCoverage);
append(key, state.truncatedCoverage);
append(key, state.customSampleLocations);
if (state.customSampleLocations) {
append(key, state.sampleLocationGrid);
@@ -35,7 +35,7 @@ static_assert(sizeof(VkStencilOpState) == 28, "VkStencilOpState changed: update
static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindingDescription changed: update the recipe codec");
static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec");
#if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__)
static_assert(sizeof(State) == 784, "State changed: update CanonicalPipelineState and the recipe codec");
static_assert(sizeof(State) == 808, "State changed: update CanonicalPipelineState and the recipe codec");
static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec");
static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec");
static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec");
@@ -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 = 6u;
constexpr std::uint32_t FileFormat = 7u;
constexpr std::uint32_t ModuleRecord = 1u;
constexpr std::uint32_t GraphicsRecord = 2u;
constexpr std::uint32_t ComputeRecord = 3u;
@@ -217,6 +217,7 @@ void encodeState(Writer& writer, const State& state) {
writer.Bool(state.sampleLocationGrid);
writer.Bool(state.perSampleShading);
writer.Bool(state.mergedCoverage);
writer.Bool(state.truncatedCoverage);
for (const auto mask : state.quadSampleMask) writer.Raw(mask);
writer.U32(state.depthRemap.mode);
}
@@ -278,6 +279,7 @@ void decodeState(Reader& reader, State& state) {
state.sampleLocationGrid = reader.Bool();
state.perSampleShading = reader.Bool();
state.mergedCoverage = reader.Bool();
state.truncatedCoverage = reader.Bool();
for (auto& mask : state.quadSampleMask) mask = reader.Raw<std::uint16_t>();
state.depthRemap.mode = reader.U32();
}
@@ -1051,6 +1053,7 @@ State CanonicalPipelineState(const State& state) {
canonical.sampleLocationGrid = state.sampleLocationGrid;
canonical.perSampleShading = state.perSampleShading;
canonical.mergedCoverage = state.mergedCoverage;
canonical.truncatedCoverage = state.truncatedCoverage;
canonical.quadSampleMask = state.quadSampleMask;
canonical.depthRemap.mode = state.depthRemap.mode;
return canonical;
@@ -255,10 +255,24 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
}
if (partsOnly) return VK_NULL_HANDLE;
PerformanceTimer timing("Vulkan.GraphicsPipelineLink");
std::optional<std::pair<std::vector<VkSampleCountFlagBits>, VkSampleCountFlagBits>> sampleCounts;
for (auto* next = static_cast<const VkBaseInStructure*>(rendering.pNext); next != nullptr; next = next->pNext) {
if (next->sType != VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV) continue;
const auto& counts = *reinterpret_cast<const VkAttachmentSampleCountInfoNV*>(next);
sampleCounts.emplace(std::vector<VkSampleCountFlagBits>(counts.pColorAttachmentSamples, counts.pColorAttachmentSamples + counts.colorAttachmentCount), counts.depthStencilAttachmentSamples);
}
const auto attachmentSamples = [](const std::pair<std::vector<VkSampleCountFlagBits>, VkSampleCountFlagBits>& counts) {
return VkAttachmentSampleCountInfoNV{VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV, nullptr, static_cast<std::uint32_t>(counts.first.size()), counts.first.empty() ? nullptr : counts.first.data(), counts.second};
};
auto linkedSamples = sampleCounts ? attachmentSamples(*sampleCounts) : VkAttachmentSampleCountInfoNV{};
VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR};
linked.libraryCount = static_cast<std::uint32_t>(libraries.size());
linked.pLibraries = libraries.data();
VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &linked};
if (sampleCounts) {
linkedSamples.pNext = create.pNext;
create.pNext = &linkedSamples;
}
create.layout = info.layout;
VkPipeline pipeline = VK_NULL_HANDLE;
lock.unlock();
@@ -277,12 +291,17 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
const auto cache = context.pipelineCache;
const auto cacheSynchronization = context.pipelineCacheSynchronization;
const auto libraryLayout = fullLayout;
std::function<void()> relink = [slot, createPipelines, cache, cacheSynchronization, libraryLayout, libraries, &store, devicePointer = &device] {
std::function<void()> relink = [slot, createPipelines, cache, cacheSynchronization, libraryLayout, libraries, sampleCounts, attachmentSamples, &store, devicePointer = &device] {
if (slot->released.load()) return;
auto linkedSamples = sampleCounts ? attachmentSamples(*sampleCounts) : VkAttachmentSampleCountInfoNV{};
VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR};
linked.libraryCount = static_cast<std::uint32_t>(libraries.size());
linked.pLibraries = libraries.data();
VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &linked};
if (sampleCounts) {
linkedSamples.pNext = create.pNext;
create.pNext = &linkedSamples;
}
create.flags = VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT;
create.layout = libraryLayout;
VkPipeline result = VK_NULL_HANDLE;
@@ -4,6 +4,7 @@
#include "ThreadOwned.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
@@ -682,6 +683,14 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
logLookup({target.get(), resource, 0, DccKeys::Uncompressed, 0, nullptr, true});
return target;
}
if (resource.fmaskCmaskAddress != 0u) {
Require(!depthCompare, "FMASK depth comparison sampling is not implemented");
Require(resource.baseArray == 0u && resource.depthOrLastArray == 0u, "native FMASK array sampling is not implemented");
CompletePendingCpuWrites(context, resource.fmaskCmaskAddress, CmaskBytes(resource.width, resource.height));
auto native = MultisampleTargetFmaskTexture_nid_no_patch(context, resource, components);
Require(native != nullptr, "compressed FMASK sampling without a matching resident native target is not implemented");
return native;
}
CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast<unsigned long long>(resource.baseAddress), resource.width, resource.height, static_cast<unsigned long long>(resource.dccAddress));
RequireNoMultisampleTarget(resource.baseAddress, guestBytes != 0 ? static_cast<std::size_t>(guestBytes) : 1u, "a texture read");
if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes();
@@ -659,7 +659,7 @@ std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders,
const bool geometry = state.stages.path == ShaderPath::Vertex && !state.rectList && shaders.size() == 3u;
const bool patches = BarycentricPatches(state, shaders) && shaders.back().program != nullptr && shaders.back().program->barycentricEmulation.active;
Require(geometry || shaders.size() == (tessellation || state.rectList || patches ? 4u : 2u), "incorrect graphics stage count");
if (patches) Require(state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, "emulated barycentrics through tessellation require a triangle list");
if (patches) Require(state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, "emulated barycentrics through tessellation require a triangle list or strip");
if (geometry) {
Require(shaders.back().program != nullptr && shaders.back().program->barycentricEmulation.active, "a geometry stage is generated only for emulated barycentrics");
Require(state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, "emulated barycentrics require triangle primitives");
@@ -355,7 +355,6 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx
const bool opValReference = opValExport && StencilOperationValueReplaces(cx);
zero(cx, 0x000, 0x0000101cu, "depth copy, resummarize or decompress draws (DB_RENDER_CONTROL)");
const bool depthClear = (read(cx, 0x000) & 1u) != 0;
const bool stencilClear = (read(cx, 0x000) & 2u) != 0;
const auto view = read(cx, 0x002);
zero(cx, 0x010, 0x00001000u, "partially resident depth (DB_Z_INFO)");
const auto viewMip = (view >> 26u) & 0xfu;
@@ -363,6 +362,7 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx
zero(cx, 0x011, 0x00001000u, "partially resident stencil (DB_STENCIL_INFO)");
const auto zFormat = read(cx, 0x010) & 3u;
const bool stencil = (read(cx, 0x011) & 1u) != 0;
const bool stencilClear = stencil && (read(cx, 0x000) & 2u) != 0;
Require(zFormat != 2, "Z_24 depth is unsupported");
if (zFormat == 0) depthControl &= ~6u;
if (!stencil) depthControl &= ~1u;
@@ -372,7 +372,7 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx
};
const bool depthReadOnly = (view & 0x01000000u) != 0;
const bool stencilReadOnly = (view & 0x02000000u) != 0;
Require(!stencilClear || (stencil && !stencilReadOnly), "stencil clear requires a writable stencil plane");
Require(!stencilClear || !stencilReadOnly, "stencil clear requires a writable stencil plane");
Require(!stencilClear || !testValExport, "stencil clear with a pixel shader that exports the stencil test value is unsupported");
Require(!depthClear || (zFormat != 0 && !depthReadOnly), "depth clear requires a writable depth plane");
DepthTarget depth{};
@@ -929,8 +929,8 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number,
if (number == unorm) return {VK_FORMAT_R32_UINT, 4, 0xe4u, ShaderRecompiler::ColorExportPacking::Unorm10_11_11};
return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4};
case 7:
if (swap != 2 || number != floating) return fail();
return {VK_FORMAT_R32_UINT, 4, 0xc6u, ShaderRecompiler::ColorExportPacking::Float11_11_10};
if ((swap != 0 && swap != 2) || number != floating) return fail();
return {VK_FORMAT_R32_UINT, 4, static_cast<std::uint8_t>(swap == 2 ? 0xc6u : 0xe4u), ShaderRecompiler::ColorExportPacking::Float11_11_10};
case 9:
// COLOR_2_10_10_10 keeps red in the low bits, the Vulkan A2B10G10R10 packing.
if (number != unorm) return fail();
@@ -1334,8 +1334,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
result.sampleLocationGrid = false;
}
if (result.samples > 1 && (read(cx, 0x292) & 1u) == 0) {
const bool filled = result.topology != VK_PRIMITIVE_TOPOLOGY_POINT_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
Require(filled && !result.alphaToCoverage, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points, lines or alpha-to-coverage");
Require(RasterPrimitiveOf(result) != ShaderRecompiler::RasterPrimitive::Point && !result.alphaToCoverage, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage");
if (const auto eqaa = find(cx, 0x201); result.depth && eqaa != cx.end() && (eqaa->second & 0x0f0c0000u) != 0) throw std::runtime_error(zeroMessage(0x201, eqaa->second, "a multisampled depth target rasterized without MSAA_ENABLE with Z interpolation or overrasterization (DB_EQAA)"));
if (const auto control = find(cx, 0x203); result.depth && control != cx.end() && (control->second & 0x100u) != 0) throw std::runtime_error(zeroMessage(0x203, control->second, "a multisampled depth target rasterized without MSAA_ENABLE with a sample-mask export"));
result.customSampleLocations = true;
@@ -1436,8 +1435,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
}
const bool merged = color.samples == 1u && !result.depth && !result.perSampleShading && (result.colors.empty() || result.mergedCoverage) && overrasterized(cx, result.samples);
if (!merged && color.samples == 1 && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result);
Require((color.samples == result.samples && !result.mergedCoverage) || merged, "a color target's sample count differs from the rasterizer's");
const bool truncated = color.samples == 1u && result.samples > 1u && result.depth && result.depth->samples == result.samples && (result.colors.empty() || result.truncatedCoverage) && !overrasterized(cx, result.samples);
Require((color.samples == result.samples && !result.mergedCoverage && !result.truncatedCoverage) || merged || truncated, "a color target's sample count differs from the rasterizer's");
result.mergedCoverage = merged;
result.truncatedCoverage = truncated;
APS5_LOG_OUT_DEBUG("Color %u address=0x%llx extent=%ux%u bytes=%llu VkFormat=%u", slot, static_cast<unsigned long long>(color.address), color.extent.width, color.extent.height, static_cast<unsigned long long>(color.bytes), static_cast<unsigned>(color.format));
if (result.colors.empty()) {
result.renderExtent = color.extent;
@@ -1877,6 +1878,14 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
if (color.samples == 1) GuestMemory::CheckRange(reinterpret_cast<const void*>(cmask), totalCmaskBytes, CmaskLayout::Alignment, true);
}
color.cmaskFastClear = (info & 0x2000u) != 0;
if (const auto fmask = find(cx, 0x321 + stride); color.samples > 1u && fmask != cx.end()) {
const auto high = find(cx, 0x3a0 + slot);
const auto fmaskHigh = high == cx.end() ? 0u : high->second;
Require((fmaskHigh & ~0xffu) == 0u, "invalid FMASK address extension");
const auto address = (static_cast<std::uint64_t>(fmaskHigh) << 40u) | (static_cast<std::uint64_t>(fmask->second) << 8u);
color.fmaskPipeBankXor = static_cast<std::uint32_t>(address & 0xffffu);
color.fmaskAddress = address - color.fmaskPipeBankXor;
}
}
if ((info & 0x10000000u) != 0) {
if (maxMip == 0) {
@@ -2049,10 +2058,11 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
if (value(cx, 0x200, word)) {
const auto renderControl = find(cx, 0x000);
const bool depthClear = renderControl != cx.end() && (renderControl->second & 1u) != 0;
const bool stencilClear = renderControl != cx.end() && (renderControl->second & 2u) != 0;
std::uint32_t stencilInfo = 0;
const bool stencilClear = renderControl != cx.end() && (renderControl->second & 2u) != 0 && !(value(cx, 0x011, stencilInfo) && (stencilInfo & 1u) == 0);
if (stencilClear && !depthPlanesAbsent(cx)) {
std::uint32_t stencil = 0, view = 0;
if ((value(cx, 0x011, stencil) && (stencil & 1u) == 0) || (value(cx, 0x002, view) && (view & 0x02000000u) != 0)) return require(false, "stencil clear requires a writable stencil plane");
std::uint32_t view = 0;
if (value(cx, 0x002, view) && (view & 0x02000000u) != 0) return require(false, "stencil clear requires a writable stencil plane");
}
if (depthClear && !depthPlanesAbsent(cx)) {
std::uint32_t z = 0, view = 0;
@@ -5,6 +5,12 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int CAPTURE_UNAVAILABLE = static_cast<int>(0x8A6C1000);
}
extern "C" {
bool APS5_VABI sceAgcDriverIsCaptureInProgress(void) {
@@ -21,18 +27,15 @@ bool APS5_VABI sceAgcDriverIsSubmitValidationEnabled(void) {
int APS5_VABI sceAgcDriverRequestCaptureStart(const char* path) {
(void)path;
NotImplemented_nid_no_patch(__func__);
return 0;
return CAPTURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverRequestCaptureStop() {
NotImplemented_nid_no_patch(__func__);
return 0;
return CAPTURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverTriggerCapture() {
NotImplemented_nid_no_patch(__func__);
return 0;
return CAPTURE_UNAVAILABLE;
}
}
+14 -10
View File
@@ -57,6 +57,17 @@ int APS5_VABI sceAgcDriverGetHsOffchipParam(uint16_t* first, uint16_t* second) {
return 0;
}
int APS5_VABI sceAgcDriverNopDataWritePacket(void* destination, uint32_t address, const void* data, uint32_t bytes) {
const auto alignedBytes = (static_cast<std::uint64_t>(bytes) + 3) & ~std::uint64_t{3};
const auto totalBytes = alignedBytes + 8;
std::memset(destination, 0, static_cast<std::size_t>(totalBytes));
const auto header = (static_cast<std::uint32_t>(totalBytes) << 14) + 0x3ffe0000U | 0xc0001000U;
std::memcpy(destination, &header, sizeof(header));
std::memcpy(static_cast<std::byte*>(destination) + 4, &address, sizeof(address));
if (data != nullptr && bytes != 0) std::memcpy(static_cast<std::byte*>(destination) + 8, data, bytes);
return data == nullptr || bytes == 0 ? 2 : static_cast<int>(totalBytes / 4);
}
int APS5_VABI sceAgcDriverSetValidationDisabled() {
return DEBUG_UNAVAILABLE;
@@ -154,9 +165,8 @@ int APS5_VABI sceAgcDriverAcquireHDRScopesACQ() {
return 0;
}
int APS5_VABI sceAgcDriverGetCoredumpAddress() {
NotImplemented_nid_no_patch(__func__);
return 0;
void* APS5_VABI sceAgcDriverGetCoredumpAddress() {
return nullptr;
}
int APS5_VABI sceAgcDriverHp3dLockEnd() {
@@ -174,19 +184,13 @@ int APS5_VABI sceAgcDriverIsEsvmProcess() {
return 0;
}
int APS5_VABI sceAgcDriverNopDataWritePacket() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverReleaseHDRScopesACQ() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSetTargetRingForDiag() {
NotImplemented_nid_no_patch(__func__);
return 0;
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverSetValueToSharedGds() {
+127 -15
View File
@@ -766,10 +766,12 @@ void msaaEnableClearOneSampleTargetTests() {
expectFailure([&] { DecodeState(queue); }, refused);
queue = drawC();
queue.context[0x2dc] = 0x0000aa01u;
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines");
queue = drawC();
queue.userConfig[0x242] = 2;
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines");
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points or alpha-to-coverage");
for (const auto primitive : {2u, 3u}) {
queue = drawC();
queue.userConfig[0x242] = primitive;
requireOneSample(queue, "a line list or strip into PPSA18216's draw C target");
}
const auto twoTargets = [&](std::uint32_t first, std::uint32_t second) {
auto pair = drawC();
for (const auto offset : {0x318u, 0x31bu, 0x31cu}) pair.context[offset + 0xfu] = pair.context.at(offset);
@@ -854,9 +856,11 @@ void msaaEnableClearOneSampleTargetTests() {
queue = depthTarget(color);
queue.context[0x2dc] = 0x0000aa01u;
expectFailure([&] { DecodeState(queue); }, depthRefused);
queue = depthTarget(color);
queue.userConfig[0x242] = 2;
expectFailure([&] { DecodeState(queue); }, depthRefused);
for (const auto primitive : {2u, 3u}) {
queue = depthTarget(color);
queue.userConfig[0x242] = primitive;
expectFailure([&] { DecodeState(queue); }, depthRefused);
}
for (const auto eqaa : {0x00050002u, 0x00090002u, 0x01010002u, 0x08010002u}) {
queue = depthTarget(color);
queue.context[0x201] = eqaa;
@@ -2396,10 +2400,12 @@ void DepthStencilTests() {
queue.context[0x000] = 0x22u | control;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DB_RENDER_CONTROL");
}
queue.context[0x000] = 0x22;
queue.context[0x000] = 0x23;
queue.context[0x011] = 0;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "writable stencil plane");
Require(AgcDriver::Graphics::DrawRejection(queue, false).find("writable stencil plane") != std::string::npos, "precheck accepted clear without a stencil plane");
state = AgcDriver::Graphics::DecodeState(queue);
Require(state.depthWrite && state.depthCompare == VK_COMPARE_OP_ALWAYS && !state.stencilTest && state.colors.size() == 2, "a depth and stencil clear without a stencil plane does not clear depth alone");
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "precheck rejected a stencil clear without a stencil plane");
queue.context[0x000] = 0x22;
queue.context[0x010] = 0;
state = AgcDriver::Graphics::DecodeState(queue);
Require(!state.depth && !state.depthTest && !state.depthWrite && !state.stencilTest && state.colors.size() == 2, "clear flags without depth/stencil planes changed color-only rendering");
@@ -3349,6 +3355,41 @@ void msaaEnableClearDepthTests() {
state = DecodeState(queue);
Require(state.colors.empty(), "a depth-only 4x draw without MSAA_ENABLE decoded a color target");
requireCentered(state, "a 4x depth target rasterized without MSAA_ENABLE");
for (const auto primitive : {2u, 3u}) {
queue = drawB();
queue.userConfig[0x242] = primitive;
state = DecodeState(queue);
Require(state.topology == (primitive == 2u ? VK_PRIMITIVE_TOPOLOGY_LINE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_STRIP) && state.colors.size() == 1 && state.colors[0].samples == 4, "PPSA18216's line list or strip into its 4x depth and color pair lost its topology or color target");
requireCentered(state, "a line list or strip into PPSA18216's 4x depth and color pair without MSAA_ENABLE");
Require(!state.sampleLocationGrid && !state.perSampleShading, "a line list or strip without MSAA_ENABLE kept a sample location grid or sample shading");
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck refused a line list or strip into PPSA18216's 4x depth and color pair");
}
queue = drawB();
queue.userConfig[0x242] = 1;
queue.context[0x2d5] = 0x2030;
queue.userConfig[0x25b] = 0x6633;
queue.context[0x1ff] = 255;
queue.context[0x2ce] = 5;
queue.context[0x29b] = 2;
queue.context[0x2ab] = 4;
queue.shader[0x8a] = 3u << 29u;
queue.shader[0x8b] = 3u << 16u;
state = DecodeState(queue);
Require(state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST && state.stages.mesh && state.stages.mesh->outputPrimitiveSize == 3u && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Triangle && state.colors.size() == 1 && state.colors[0].samples == 4, "a point list into a geometry program that outputs triangle strips into PPSA18216's 4x depth and color pair did not decode as triangles");
requireCentered(state, "triangles a geometry program makes from a point list into PPSA18216's 4x depth and color pair without MSAA_ENABLE");
for (const auto primitive : {4u, 2u, 1u}) {
queue = drawB();
queue.userConfig[0x242] = primitive;
queue.context[0x2d5] = 0x2030;
queue.userConfig[0x25b] = 0x6633;
queue.context[0x1ff] = 255;
queue.context[0x2ce] = 5;
queue.context[0x29b] = 0;
queue.context[0x2ab] = 4;
queue.shader[0x8a] = 3u << 29u;
queue.shader[0x8b] = 3u << 16u;
expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage");
}
for (const auto eqaa : {0x00050002u, 0x00090002u, 0x01010002u, 0x08010002u}) {
queue = drawB();
queue.context[0x201] = eqaa;
@@ -3360,7 +3401,7 @@ void msaaEnableClearDepthTests() {
expectFailure([&] { DecodeState(queue); }, "a multisampled depth target rasterized without MSAA_ENABLE with a sample-mask export");
queue = drawB();
queue.context[0x2dc] = 0x0000aa01u;
expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points, lines or alpha-to-coverage");
expectFailure([&] { DecodeState(queue); }, "multisampled targets rasterized without MSAA_ENABLE are unsupported for points or alpha-to-coverage");
queue = drawB();
queue.context[0x010] = 0xa2900987;
expectFailure([&] { DecodeState(queue); }, "the depth target's sample count differs from the rasterizer's");
@@ -3667,7 +3708,7 @@ void multisampleTests() {
const auto aliased = AgcDriver::Graphics::SampleLocations(state);
for (std::uint32_t sample = 0; sample < state.samples; ++sample) Require(aliased[sample].x == 0.5f && aliased[sample].y == 0.5f, "a 2x target rasterized without MSAA_ENABLE did not place every sample at the pixel center");
queue.context[0x2dc] = 0x0000aa01u;
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points, lines or alpha-to-coverage");
expectFailure([&] { DecodeState(queue); }, "without MSAA_ENABLE are unsupported for points or alpha-to-coverage");
queue = multisampled();
queue.context[0x292] = 2;
queue.context[0x302] = 0x44cc;
@@ -4110,6 +4151,29 @@ void multisampledDepthTests() {
Require(!DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE requested sample shading for a single-sample scan");
queue = singleSampleScan();
queue.context[0x31d] = 0;
state = DecodeState(queue);
Require(state.truncatedCoverage && !state.mergedCoverage && state.colors.size() == 1 && state.colors[0].samples == 1, "a single-sample scan into a 4x depth target and a one-sample color target did not take the color from sample 0");
requireCentered(state, "a single-sample scan into a 4x depth target and a one-sample color target");
const auto truncatedTwoSample = [&] {
auto queue = singleSampleScan();
queue.context[0x2f8] = 0x00100001;
queue.context[0x292] = 3;
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0xcc44;
queue.context[0x010] = 0x22900987;
queue.context[0x31d] = 0;
return queue;
};
queue = truncatedTwoSample();
state = DecodeState(queue);
Require(state.samples == 2 && state.depth && state.depth->samples == 2 && state.colors.size() == 1 && state.colors[0].samples == 1 && state.truncatedCoverage && !state.mergedCoverage && !state.perSampleShading, "a one-sample color target beside a 2x depth target under a 2x rasterizer with MSAA_ENABLE (PPSA02225's registers) did not take the color from sample 0");
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck refused a one-sample color target beside a 2x depth target");
queue = truncatedTwoSample();
queue.context[0x293] = 0x10000u;
queue.context[0x1b3] = queue.context[0x1b4] = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveSample) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::FrontFace);
state = DecodeState(queue);
Require(state.truncatedCoverage && state.perSampleShading && state.samples == 2 && state.colors[0].samples == 1, "PS_ITER_SAMPLE into a one-sample color target beside a 2x depth target (PPSA02225's registers) did not run per sample with sample 0's color");
queue = truncatedTwoSample();
queue.context[0x31d] = 0x12000;
expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's");
queue = singleSampleScan();
queue.context[0x010] = 0x22900983;
@@ -7927,9 +7991,11 @@ void barycentricEmulationTests() {
Require(BarycentricPatches(state, patchShaders) && !BarycentricPatches(state, shaders), "generated tessellation stages were not recognised as barycentric patches");
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
ValidateShaders(patchShaders, state, subgroup, false);
for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_POINT_LIST}) {
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
ValidateShaders(patchShaders, state, subgroup, false);
for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_POINT_LIST}) {
state.topology = topology;
expectFailure([&] { ValidateShaders(patchShaders, state, subgroup, false); }, "require a triangle list");
expectFailure([&] { ValidateShaders(patchShaders, state, subgroup, false); }, "require a triangle list or strip");
}
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
const std::array<CompiledShader, 4> plainPatchShaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::TessellationControl, &patches.control, 0}, {ShaderStage::TessellationEvaluation, &patches.evaluation, 0}, {ShaderStage::Fragment, &plain, 0}}};
@@ -9169,6 +9235,14 @@ void ProvokingVertexTests() {
const std::array<CompiledShader, 1> flatP10Shaders{{{ShaderRecompiler::ShaderStage::Fragment, &flatP10Pixel, 0}}};
ValidateProvokingVertex(supported, last, flatP10Shaders);
expectFailure([&] { ValidateProvokingVertex(supported, first, flatP10Shaders); }, "v_interp_mov p10 of a flat input with the first provoking vertex is not measured");
const std::array<std::uint32_t, 7> flatP20Color{0xc8120001u, 0xc8160101u, 0xc81a0201u, 0xc81e0301u, 0xf800180fu, 0x07060504u, 0xbf810000u};
const auto flatP20Pixel = recompilePixel({0x400u}, flatP20Color);
const auto p20Inputs = locatedInputs(flatP20Pixel.spirv.Words());
Require(p20Inputs.size() == 1 && p20Inputs[0].location == 0 && p20Inputs[0].flat && !p20Inputs[0].perVertex, "flat v_interp_mov P20 did not compile to a flat input");
Require(flatP20Pixel.fragmentParameters.size() == 1 && flatP20Pixel.fragmentParameters[0].flatP20 && !flatP20Pixel.fragmentParameters[0].flatP10 && !flatP20Pixel.fragmentParameters[0].perVertex && !flatP10Pixel.fragmentParameters[0].flatP20, "flat v_interp_mov P20 was not recorded apart from P10");
const std::array<CompiledShader, 1> flatP20Shaders{{{ShaderRecompiler::ShaderStage::Fragment, &flatP20Pixel, 0}}};
ValidateProvokingVertex(supported, first, flatP20Shaders);
expectFailure([&] { ValidateProvokingVertex(supported, last, flatP20Shaders); }, "v_interp_mov p20 of a flat input with the last provoking vertex is not measured");
auto lastStrip = last;
lastStrip.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
auto lastList = last;
@@ -9533,7 +9607,12 @@ void float11_11_10TargetTests() {
auto changed = queue;
changed.context[0x31c] &= ~0x40000u;
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "rounding (ROUND_MODE 0) 11_11_10 float color targets");
for (const auto swap : {0u, 1u, 3u}) {
changed = queue;
changed.context[0x31c] &= ~0x1800u;
const auto standard = AgcDriver::Graphics::DecodeState(changed);
Require(standard.color.format == VK_FORMAT_R32_UINT && standard.color.componentMapping == 0xe4u && standard.color.packing == ColorExportPacking::Float11_11_10, "an 11_11_10 float target with the standard component order did not decode");
Require(AgcDriver::Graphics::ExportMappings(standard)[0] == 0xe4u, "export 0 reordered its components into the standard-order 11_11_10 float target");
for (const auto swap : {1u, 3u}) {
changed = queue;
changed.context[0x31c] = (changed.context[0x31c] & ~0x1800u) | (swap << 11u);
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 7 number type 7 component swap " + std::to_string(swap));
@@ -10719,6 +10798,39 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) {
const PipelineSetLayouts decodedLayouts{layouts.layouts.main, layouts.layouts.fragment, decoded.splitSets, decoded.mainLayoutKey, decoded.fragmentLayoutKey};
BuildPipelineStoreKey(replayed, context, decoded.state, decoded.vertexInput, decodedLayouts, shaders, decoded.attachmentLayout);
Require(!original.empty() && original == canonical && original == replayed, "the canonical or the decoded state keys another pipeline");
auto fmaskState = state;
fmaskState.samples = 8;
fmaskState.color.samples = 8;
fmaskState.color.fmaskAddress = 0x30000;
fmaskState.color.fmaskPipeBankXor = 0x1200;
for (auto& target : fmaskState.colors) {
target.samples = 8;
target.fmaskAddress = 0x40000;
target.fmaskPipeBankXor = 0x2300;
}
const auto fmaskRecipe = MakeGraphicsPipelineRecipe(fmaskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
Require(fmaskRecipe.state.color.fmaskAddress == 0 && fmaskRecipe.state.color.fmaskPipeBankXor == 0, "the canonical state kept the primary target's FMASK location");
for (const auto& canonicalColor : fmaskRecipe.state.colors) {
Require(canonicalColor.fmaskAddress == 0 && canonicalColor.fmaskPipeBankXor == 0, "the canonical state kept an attachment's FMASK location");
}
const auto fmaskBytes = EncodeGraphicsPipelineRecipe(fmaskRecipe);
GraphicsPipelineRecipe fmaskDecoded;
Require(DecodeGraphicsPipelineRecipe(fmaskBytes, fmaskDecoded) && EncodeGraphicsPipelineRecipe(fmaskDecoded) == fmaskBytes, "the FMASK pipeline recipe did not round-trip byte for byte");
std::vector<std::byte> fmaskKey, fmaskReplayedKey;
BuildPipelineStoreKey(fmaskKey, context, fmaskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
BuildPipelineStoreKey(fmaskReplayedKey, context, fmaskDecoded.state, fmaskDecoded.vertexInput, layouts.View(), shaders, fmaskDecoded.attachmentLayout);
Require(fmaskKey != original && fmaskKey == fmaskReplayedKey, "the decoded FMASK state keys another pipeline");
auto relocatedFmask = fmaskState;
relocatedFmask.color.fmaskAddress += 0x10000;
relocatedFmask.color.fmaskPipeBankXor ^= 0x100;
for (auto& target : relocatedFmask.colors) {
target.fmaskAddress += 0x20000;
target.fmaskPipeBankXor ^= 0x200;
}
std::vector<std::byte> relocatedFmaskKey;
BuildPipelineStoreKey(relocatedFmaskKey, context, relocatedFmask, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
Require(relocatedFmaskKey == fmaskKey, "the FMASK resource location changed the pipeline key");
Require(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(relocatedFmask, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)) == fmaskBytes, "the FMASK resource location changed the journal recipe");
auto gridState = state;
gridState.samples = 2;
gridState.customSampleLocations = true;
@@ -95,6 +95,52 @@ void rejectFields(const Fields& f, std::string_view reason) {
}
static void RunR128TextureTests() {
constexpr std::array<std::uint32_t, 8> streamed{0xa0c18800u, 0xcad00000u, 0x01ffc1ffu, 0x905b2facu, 0u, 0u, 0u, 0u};
const auto view = DecodeTextureResource(streamed);
Require(view.baseAddress == 0xa0c1880000ull && view.format == 173u && view.tileMode == TextureTileMode::kStandard4KB && view.width == 512u && view.height == 512u && view.baseLevel == 0u && view.lastLevel == 9u && view.mipCount == 10u && view.allocatedMipCount == 10u && view.minLod == 0u,
"a 128-bit BC3 T# (2048x2048, levels 2..11, words 4-7 zero) must read as the 512x512 chain of its 10 resident levels, got " + std::to_string(view.width) + "x" + std::to_string(view.height) + " levels " + std::to_string(view.baseLevel) + ".." + std::to_string(view.lastLevel) + " of " + std::to_string(view.mipCount));
auto chainWords = streamed;
chainWords[3] &= ~(0xfu << 12u);
chainWords[5] = 11u << 4u;
const auto full = DecodeTextureResource(chainWords);
Require(full.width == 2048u && full.mipCount == 12u, "the same surface with MAX_MIP 11 must be the 12-level 2048x2048 chain");
const auto chain = DescribeSurface(full);
const auto resident = DescribeSurface(view);
Require(chain.mips.size() == 12u && resident.mips.size() == 10u, "the chain and the resident levels must have 12 and 10 levels");
for (std::uint32_t level = 0; level < 10u; ++level) {
const auto& a = chain.mips[level + 2u];
const auto& b = resident.mips[level];
Require(a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY, "resident level " + std::to_string(level) + " must be level " + std::to_string(level + 2u) + " of the 12-level chain");
}
Require(resident.guestBytes <= chain.mips[1].tiledOffset && chain.mips[0].tiledOffset > chain.mips[1].tiledOffset, "the resident levels must end before the chain's levels 1 and 0, which come last in a GFX10 swizzled chain");
auto levelZero = streamed;
levelZero[3] &= ~(0xfu << 12u);
const auto single = DecodeTextureResource(levelZero);
Require(single.mipCount == 1u && single.lastLevel == 0u, "a T# with words 4-7 zero that starts at level 0 must stay one level");
auto tailed = streamed;
tailed[5] = 4u << 20u;
reject([&] { DecodeTextureResource(tailed); }, "would move the surface's own");
constexpr std::array<std::uint32_t, 8> tall{0xa0c1de00u, 0xcad00000u, 0x01ffc0ffu, 0x905b4facu, 0u, 0u, 0u, 0u};
const auto tallView = DecodeTextureResource(tall);
Require(tallView.width == 64u && tallView.height == 128u && tallView.lastLevel == 7u && tallView.mipCount == 8u, "a 128-bit 1024x2048 T# of levels 4..11 must read as its 64x128 chain of 8 levels");
Fields clamped;
clamped.base40 = 0x55ea000ull;
clamped.format = 20;
clamped.width = 1024;
clamped.height = 1024;
clamped.tileModeRaw = 0x1b;
clamped.baseLevel = 2;
clamped.lastLevel = 10;
clamped.minLod = 2u * 256u + 64u;
const auto clampedView = DecodeTextureResource(pack(clamped));
Require(clampedView.width == 256u && clampedView.mipCount == 9u && clampedView.minLod == 64u, "reading a 128-bit T# from its resident levels must move its MIN_LOD clamp with the base level");
clamped.minLod = 256u;
Require(DecodeTextureResource(pack(clamped)).minLod == 0u, "a MIN_LOD clamp below the base level must vanish when read from the resident levels");
}
void RunGuestTextureResourceTests() {
Fields base;
const auto baseWords = pack(base);
@@ -123,6 +169,15 @@ void RunGuestTextureResourceTests() {
fmask.dstSelY = fmask.dstSelZ = fmask.dstSelW = 0u;
const auto fmaskResource = DecodeTextureResource(pack(fmask));
Require(fmaskResource.format == 20u && fmaskResource.dimension == TextureDimension::k2D && fmaskResource.tileMode == TextureTileMode::kZ64KBX, "uncompressed 32-bit FMASK did not use an integer Z surface");
auto compressedFmask = fmask;
compressedFmask.metaCompress = compressedFmask.metaPipeAligned = true;
compressedFmask.metaAddr = 0x123456789aull;
const auto compressedResource = DecodeTextureResource(pack(compressedFmask));
Require(compressedResource.fmaskCmaskAddress == compressedFmask.metaAddr << 8u && compressedResource.dccAddress == 0u && compressedResource.dccPipeAligned, "compressed FMASK CMASK was interpreted as color DCC");
compressedFmask.metaPipeAligned = false;
rejectFields(compressedFmask, "FMASK CMASK metadata must be pipe aligned");
compressedFmask.metaPipeAligned = compressedFmask.writeCompress = true;
rejectFields(compressedFmask, "FMASK CMASK compression is not implemented");
Fields fmaskArray = fmask;
fmaskArray.typeRaw = 13u;
fmaskArray.depth = 2u;
@@ -620,6 +675,7 @@ void RunGuestTextureResourceTests() {
pastUntailed.maxMip = 0;
pastUntailed.baseLevel = 1;
pastUntailed.lastLevel = 1;
pastUntailed.perfMod = 4;
rejectFields(pastUntailed, "would move the surface's own");
auto untailedChain = tailView;
untailedChain.mipCount = 1;
@@ -627,6 +683,8 @@ void RunGuestTextureResourceTests() {
untailedChain.mipCount = 2;
Require(untailedAllocated == 0xff0000u && DescribeSurface(untailedChain).guestBytes == 0x1470000u, "a second level must grow a 1920x1080 64 bpp SW_64KB_R_X surface from addrlib's 0xff0000 to 0x1470000 bytes");
RunR128TextureTests();
std::array<std::uint32_t, 4> shortWords{};
reject([&] { DecodeTextureResource(shortWords); }, "8 dwords");
@@ -0,0 +1,38 @@
#include "SceTypes.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
extern "C" int APS5_VABI sceAgcDriverNopDataWritePacket(void* destination, uint32_t address, const void* data, uint32_t bytes);
int main() {
std::array<std::byte, 24> storage;
storage.fill(std::byte{0xaa});
const std::array<std::byte, 5> data{std::byte{0x11}, std::byte{0x22}, std::byte{0x33}, std::byte{0x44}, std::byte{0x55}};
const auto words = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x12345678, data.data(), static_cast<std::uint32_t>(data.size()));
std::uint32_t header = 0;
std::uint32_t address = 0;
std::memcpy(&header, storage.data() + 4, sizeof(header));
std::memcpy(&address, storage.data() + 8, sizeof(address));
if (words != 4 || header != 0xc0021000U || address != 0x12345678U) return 1;
if (!std::equal(data.begin(), data.end(), storage.begin() + 12)) return 2;
if (storage[17] != std::byte{0} || storage[18] != std::byte{0} || storage[19] != std::byte{0}) return 3;
if (storage[3] != std::byte{0xaa} || storage[20] != std::byte{0xaa}) return 4;
storage.fill(std::byte{0xaa});
const auto emptyWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x87654321, nullptr, 5);
std::memcpy(&header, storage.data() + 4, sizeof(header));
std::memcpy(&address, storage.data() + 8, sizeof(address));
if (emptyWords != 2 || header != 0xc0021000U || address != 0x87654321U) return 5;
if (storage[12] != std::byte{0} || storage[19] != std::byte{0} || storage[20] != std::byte{0xaa}) return 6;
storage.fill(std::byte{0xaa});
const auto zeroBytesWords = sceAgcDriverNopDataWritePacket(storage.data() + 4, 0x01020304, data.data(), 0);
std::memcpy(&header, storage.data() + 4, sizeof(header));
std::memcpy(&address, storage.data() + 8, sizeof(address));
if (zeroBytesWords != 2 || header != 0xc0001000U || address != 0x01020304U) return 7;
if (storage[12] != std::byte{0xaa}) return 8;
return 0;
}
@@ -4804,11 +4804,14 @@ void idlePublishWaitTests(const Device& device, Recorder& recorder) {
recorder.Sync();
Require(words[2 * unit] == 0x44 && words[4 * unit - 1] == 0x45, "a CPU store made after the idle flush returned was overwritten by its publish");
Require(words[2 * unit + 1] == 0x22 && words[3 * unit - 1] == 0x22 && words[3 * unit] == 0x33 && words[1 * unit] == 0x11 && words[4 * unit] == 0x11, "the idle publish's bytes are wrong");
words[5 * unit] = 0x11;
CollectWritesUncached(address, bytes);
retile(address + 5 * unit, 0x55);
StorageTexture::FlushAllPending("label");
Require(recorder.PendingWriteOverlaps(address + 5 * unit, unit), "a publish of a flush other than the idle one was waited");
const bool pending = recorder.PendingWriteOverlaps(address + 5 * unit, unit);
recorder.Sync();
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land");
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land: the unit read as written after its retile, so the publish dropped it as stale");
Require(pending, "a publish of a flush other than the idle one was waited");
std::cout << "idle publish wait verified\n";
}
@@ -0,0 +1,19 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
#include <cstdint>
#include <vector>
namespace AgcTest {
inline std::vector<std::uint32_t> SavedExecMaskLoop(std::uint32_t rounds) {
std::vector<std::uint32_t> code{0xbea2047eu, 0xbea40380u};
for (std::uint32_t step = 0; step < rounds; ++step) code.insert(code.end(), {0xbefe0422u, 0x7c080080u, 0x8aa26a7eu});
const auto branch = 0xbf850000u | static_cast<std::uint16_t>(2 - static_cast<int>(code.size() + 3));
code.insert(code.end(), {0x80248124u, 0xbf0a8424u, branch, 0xbefe0422u});
return code;
}
}
#endif
@@ -21,9 +21,9 @@ void require(bool condition, const char* message) {
void setScreenshotDirectory(const std::filesystem::path& directory) {
#ifdef _WIN32
require(_putenv_s("ANYPS5_SCREENSHOTS", directory.string().c_str()) == 0, "set ANYPS5_SCREENSHOTS");
require(_wputenv_s(L"ANYPS5_SCREENSHOTS", directory.wstring().c_str()) == 0, "set ANYPS5_SCREENSHOTS");
#else
require(::setenv("ANYPS5_SCREENSHOTS", directory.string().c_str(), 1) == 0, "set ANYPS5_SCREENSHOTS");
require(::setenv("ANYPS5_SCREENSHOTS", directory.c_str(), 1) == 0, "set ANYPS5_SCREENSHOTS");
#endif
}
@@ -73,7 +73,7 @@ void rejectsInvalidFrames() {
int main() try {
const auto root = std::filesystem::temp_directory_path() / ("anyps5_screenshot-" + std::to_string(std::random_device{}()));
const auto directory = root / "nested";
const auto directory = root / std::filesystem::path(u8"nested-a\u00e7\u00e3o-\u65e5\u672c");
std::filesystem::remove_all(root);
setScreenshotDirectory(directory);
requestsMerge();
@@ -20,6 +20,7 @@
#include "IntermediateRepresentation/IrBuilder.hpp"
#include "IntermediateRepresentation/IrProgram.hpp"
#include "SpirvBackend/SpirvAnalysis.hpp"
#include "SavedExecMaskLoop.hpp"
#if ANYPS5_ENABLE_SPIRV_TOOLS
#include "SpirvBackend/SpirvOptimizer.hpp"
#endif
@@ -4088,6 +4089,18 @@ void verifyHalfWaveReduction() {
{0u, 0x38000000u, 0x88000000u, spv::OpGroupNonUniformBitwiseOr, "half-wave reduction: the wave64 OR scan did not read lane 31 as a subgroup OR on 32 lanes"},
}};
for (const auto& reduction : scans) require(reductions(pixel(scan(reduction.identity, reduction.vector, reduction.scalar), 32u, true), reduction.reduce) == 1u, reduction.message);
auto loopScan = scan(0xffffffffu, 0x26000000u, 0x83800000u);
const auto savedMaskLoop = AgcTest::SavedExecMaskLoop(8u);
loopScan.insert(loopScan.begin(), savedMaskLoop.begin(), savedMaskLoop.end());
auto helperLoop = loopScan;
helperLoop[0] = 0xbea204c1u;
expectFailure([&] { static_cast<void>(pixel(helperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: a loop initialized with helper lanes was proven to exclude them");
auto upperHelperLoop = loopScan;
upperHelperLoop[0] = 0xbea2037eu;
upperHelperLoop.insert(upperHelperLoop.begin() + 1, 0xbea303c1u);
expectFailure([&] { static_cast<void>(pixel(upperHelperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: the low-word loop proof was reused for helper-inclusive upper lanes");
require(reductions(pixel(loopScan, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: a loop-carried saved EXEC mask exhausted the entry-lane proof on 32 lanes");
require(halves(pixel(loopScan, 64u, true), spv::OpGroupNonUniformUMin, 0xffffffffu), "half-wave reduction: a loop-carried saved EXEC mask did not preserve the two half-wave UMin reductions");
auto comparedScan = scan(0u, 0x38000000u, 0x88000000u);
*std::find(comparedScan.begin(), comparedScan.end(), 0xbe98047eu) = 0x8a986a7eu;
comparedScan.insert(comparedScan.begin(), 0x7c080080u);
@@ -7587,6 +7600,24 @@ void verifyDynamicStorageMipChain() {
require(ResourceMaterializer::TryRuntimeImageMode(image, allLevels, modes).mode.has_value(), "a dynamic-mip storage image with sixteen levels was rejected");
}
void verifyArrayVolumeStoreMode() {
using namespace ShaderRecompiler;
const DescriptorValue descriptor{{0x11800100u, 0xc3800000u, 0x0003c003u, 0xd0900facu, 0u, 0x00700000u, 0u, 0u}, 8u};
ImageResource store{};
store.resourceClass = ImageResourceClass::Storage;
store.dimension = RdnaImageDimension::Dim3D;
store.written = true;
store.fmaskCompatible = false;
const auto modes = ResourceMaterializer::RuntimeImageModes(store);
require(modes[ResourceMaterializer::RuntimeImageMode(store, descriptor, modes)].dimension == RdnaImageDimension::Dim2DArray, "3D image store on a 2D-array T# did not select an array runtime mode");
store.read = true;
expectFailure([&] { static_cast<void>(ResourceMaterializer::RuntimeImageMode(store, descriptor, ResourceMaterializer::RuntimeImageModes(store))); }, "incompatible with the static runtime image interface", "3D image load and store on a 2D-array T# was accepted");
store.read = false;
store.atomic = true;
const DescriptorValue uintDescriptor{{0x11800100u, 0xc1400000u, 0x0003c003u, 0xd0900facu, 0u, 0x00700000u, 0u, 0u}, 8u};
expectFailure([&] { static_cast<void>(ResourceMaterializer::RuntimeImageMode(store, uintDescriptor, ResourceMaterializer::RuntimeImageModes(store))); }, "incompatible with the static runtime image interface", "3D image atomic on a 2D-array T# was accepted");
}
void verifySrgbDepthCompareModes() {
using namespace ShaderRecompiler;
for (const auto decodeFormats : {0u, 1u, 2u, 3u}) {
@@ -7761,6 +7792,7 @@ int main(int argc, char** argv) {
verifyFmaskImageMode();
verifyCubeDepthCompareMode();
verifyVolumeArrayLoadMode();
verifyArrayVolumeStoreMode();
verifyVolumeBiasTableRejection();
verifyDynamicStorageMipChain();
verifySrgbDepthCompareModes();
@@ -1,11 +1,16 @@
#include "IntermediateRepresentation/IrBuilder.hpp"
#include "IntermediateRepresentation/IrProgram.hpp"
#include "Optimization/ShaderStageInputInfo.hpp"
#include "Optimization/TessellationLowering.hpp"
#include "SpirvBackend/SpirvEmitter.hpp"
#include <spirv/unified1/spirv.hpp>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <functional>
#include <stdexcept>
#include <string>
#include <unordered_map>
using namespace ShaderRecompiler;
@@ -135,6 +140,52 @@ bool RefusedBecause(IrProgram& program, const char* reason) {
return !outcome.lowered && outcome.reason.find(reason) != std::string::npos;
}
std::uint32_t EmittedEvaluationControlPoint(std::uint32_t address) {
constexpr std::uint32_t ControlStride = 16u;
IrProgram program;
IrBuilder builder{program};
program.SetWaveSize(64);
program.Resources().stage = IrShaderStage::TessellationEvaluation;
program.Resources().srtPlanComplete = true;
program.Resources().resourceTrackingComplete = true;
program.Metadata().shaderInfoComplete = true;
program.Metadata().bindingLayoutComplete = true;
program.Metadata().tessellationControlStride = ControlStride;
auto& block = program.CreateBlock();
program.SetEntryBlock(block);
program.BlockOrder().push_back(&block);
BlockInfo info;
info.id = block.Id();
program.Metadata().blockInfo.push_back(info);
builder.SetInsertionPoint(block);
auto& read = builder.Emit(IrOpcode::GetTessellationAttribute, IrType::U32, {&builder.Constant(static_cast<std::uint32_t>(TessellationAttribute::EvaluationInput)), &builder.Constant(address), &builder.ConstantBool(true)});
static_cast<void>(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&read}));
ShaderVertexInputInfo vertex;
vertex.tess.inputControlPoints = InputControlPoints;
vertex.tess.outputControlPoints = InputControlPoints;
vertex.tess.domain = 1u;
vertex.tess.partitioning = 2u;
vertex.tess.outputTopology = 2u;
ShaderStageInputInfo inputs;
inputs.vertex = &vertex;
CompiledBindingLayout bindings;
bindings.layout = program.Metadata().bindings;
SpirvTargetOptions target{};
target.vulkanVersion = 0x00401000;
target.spirvVersion = 0x00010300;
target.subgroupSize = 64;
const auto words = SpirvEmitter().Emit(program, inputs, bindings, target);
std::unordered_map<std::uint32_t, std::uint32_t> constants;
for (std::size_t at = 5; at < words.size(); at += words[at] >> 16u) {
const auto count = words[at] >> 16u;
if (count == 0u || at + count > words.size()) throw std::runtime_error("the domain program compiled to a malformed SPIR-V instruction");
const auto opcode = words[at] & 0xffffu;
if (opcode == spv::OpConstant && count == 4u) constants[words[at + 2u]] = words[at + 3u];
if (opcode == spv::OpAccessChain && count == 7u) return constants.at(words[at + 4u]);
}
throw std::runtime_error("the domain program reads no control point attribute");
}
bool run(const char* name, const std::function<bool()>& body) {
bool passed = false;
try {
@@ -206,5 +257,9 @@ int main() {
return patch->Opcode() == IrOpcode::IMul32 && patch->Argument(1)->Resolve()->ImmediateU32() == PatchStride;
});
passed &= run("a domain read at an immediate address past the last control point reads the last control point", [] {
return EmittedEvaluationControlPoint(InputControlPoints * 16u) == InputControlPoints - 1u;
});
return passed ? 0 : 1;
}
@@ -0,0 +1,10 @@
#include "SceTypes.hpp"
extern "C" void* APS5_VABI sceAgcDriverGetCoredumpAddress();
extern "C" int APS5_VABI sceAgcDriverSetTargetRingForDiag();
int main() {
if (sceAgcDriverGetCoredumpAddress() != nullptr) return 1;
if (sceAgcDriverSetTargetRingForDiag() != static_cast<int>(0x8A6D0001)) return 2;
return 0;
}
@@ -120,7 +120,7 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) {
device.WaitIdle();
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const float u = std::bit_cast<float>(Input[tid * 4u + 0u]);
const float layer = std::clamp(std::nearbyint(std::bit_cast<float>(Input[tid * 4u + 1u])), 0.0f, static_cast<float>(Layers - 1u));
const float layer = std::clamp(std::nearbyint(std::bit_cast<float>(Input[tid * 4u + 1u])), 0.0f, static_cast<float>(LastArray - baseArray));
const auto x = static_cast<std::uint32_t>(std::clamp(static_cast<int>(std::floor(u * static_cast<float>(Width))), 0, static_cast<int>(Width) - 1));
Expect(tid, "image_sample_lz on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u], Texel(x, baseArray + static_cast<std::uint32_t>(layer)));
Expect(tid, "image_load on a window of a 1D array past maxImageArrayLayers", &Output[tid * 8u + 4u], Texel(Input[tid * 4u + 2u], baseArray + Input[tid * 4u + 3u]));
@@ -0,0 +1,173 @@
#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 <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 Size = 4;
constexpr std::uint32_t Depth = 2;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t Type2DArray = 13;
constexpr std::uint32_t IdentitySwizzle = 0xfacu;
constexpr std::size_t TexelBytes = 16384;
constexpr std::size_t BlockBytes = 65536;
alignas(256) std::array<std::uint32_t, Threads * 4> Input{};
alignas(256) constexpr std::array<std::uint32_t, 10> StoreCode{
0x34020084, 0xe0381000, 0x80001401, 0xbf8c3f70, 0xf0201110, 0x00011714, 0x7e2c0282, 0xf0201110,
0x00011714, 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 volume through array: 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;
};
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::uint32_t Value(std::uint32_t tid) {
return (tid + 1u) * 0x9e3779b1u;
}
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::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> ArrayDescriptor(const void* data) {
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) | (Format32UInt << 20u) | (((Size - 1u) & 3u) << 30u),
((Size - 1u) >> 2u) | ((Size - 1u) << 14u),
IdentitySwizzle | (Type2DArray << 28u),
Depth - 1u,
0u,
0u,
0u,
};
}
void Dispatch(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::size_t bytes) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::array<std::uint32_t, 4> words{tid % Size, (tid / Size) % Size, tid / (Size * Size), Value(tid)};
std::copy(words.begin(), words.end(), Input.begin() + tid * 4u);
}
const auto initial = Initial();
std::copy(initial.begin(), initial.end(), texels);
std::vector<std::uint32_t> userData(16, 0u);
const auto buffer = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto texture = ArrayDescriptor(texels);
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(StoreCode);
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), bytes, nullptr, "test");
device.WaitIdle();
}
void Check(const std::uint8_t* texels) {
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(ArrayDescriptor(texels)));
const auto& mip = geometry.mips.at(0);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto x = tid % Size;
const auto y = (tid / Size) % Size;
const auto z = tid / (Size * Size);
std::uint32_t actual = 0;
std::memcpy(&actual, texels + geometry.GuestLayerOffset(z) + mip.tiledOffset + static_cast<std::uint64_t>(y) * mip.pitchBytes + x * 4u, 4u);
Require(actual == Value(tid), "image_store 3d through a 2D-array T#: texel (" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ") is " + Hex(actual) + ", expected " + Hex(Value(tid)));
}
const auto initial = Initial();
Require(std::equal(texels + geometry.guestBytes, texels + TexelBytes, initial.begin() + static_cast<std::ptrdiff_t>(geometry.guestBytes)), "image_store 3d through a 2D-array T#: a slice past the array changed bytes after it");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
GuestBlock block;
auto* texels = block.Data();
const auto geometry = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(ArrayDescriptor(texels)));
Require(geometry.guestBytes <= TexelBytes, "image store volume through array: the array does not fit the texel storage");
Dispatch(*device, texels, geometry.guestBytes);
Check(texels);
std::puts("image store volume through array tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -0,0 +1,330 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.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 <array>
#include <bit>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iostream>
#include <memory>
#include <span>
#include <string>
#include <vector>
namespace AgcDriver {
struct VulkanDeviceTestAccess {
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
};
}
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Side = 16;
constexpr std::uint32_t Samples = 4;
constexpr std::uint32_t SampleShift = 2;
constexpr std::size_t BlockBytes = 65536;
constexpr std::uint32_t WaveSize = 64;
constexpr std::uint32_t Float16x4Info = 0x0730u;
constexpr std::uint32_t MultisampledAttrib = (SampleShift << 12u) | (SampleShift << 15u);
constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u;
constexpr std::uint32_t AaConfig4x = 0x0020c002u;
constexpr std::uint32_t MsaaEnableClear = 2;
constexpr std::uint32_t StandardLocations4x = 0x622ae6aeu;
constexpr std::uint32_t Format16161616Float = 71;
constexpr std::uint32_t Type2DMsaa = 14;
constexpr std::uint32_t TileRenderTarget = 0x1b;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Words = 32;
constexpr std::uint32_t LoadResult = 12;
constexpr std::array<std::uint32_t, 2> Primitives{2u, 3u};
constexpr std::array<float, 4> Drawn{0.25f, 0.5f, 0.125f, 1.0f};
constexpr std::array<std::byte, 4> DrawnBytes{std::byte{64}, std::byte{128}, std::byte{32}, std::byte{255}};
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 11> PixelCode{
0x7e0802ff, 0x3e800000, 0x7e0a02ff, 0x3f000000, 0x7e0c02ff, 0x3e000000, 0x7e0e02ff, 0x3f800000, 0xf800180f, 0x07060504, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 11> Unorm8PixelCode{
0x7e0802ff, std::bit_cast<std::uint32_t>(64.0f / 255.0f), 0x7e0a02ff, std::bit_cast<std::uint32_t>(128.0f / 255.0f), 0x7e0c02ff, std::bit_cast<std::uint32_t>(32.0f / 255.0f), 0x7e0e02ff, std::bit_cast<std::uint32_t>(1.0f), 0xf800180f, 0x07060504, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 26> LoadCode{
0x34020087, 0xe0301000, 0x80000201, 0xe0301004, 0x80000301, 0xe0301008, 0x80000401, 0xe030100c,
0x80000501, 0xe0301010, 0x80000601, 0xe0301014, 0x80000701, 0xbf8c3f70, 0xf0001f30, 0x00011005,
0xbf8c3f70, 0xe0701030, 0x80001001, 0xe0701034, 0x80001101, 0xe0701038, 0x80001201, 0xe070103c,
0x80001301, 0xbf810000,
};
alignas(256) constexpr std::array<std::array<float, 4>, 4> Lines{{
{-0.828125f, -0.4375f, 0.5f, 1.0f}, {0.90625f, 0.140625f, 0.5f, 1.0f},
{-0.265625f, 0.90625f, 0.5f, 1.0f}, {0.34375f, -0.921875f, 0.5f, 1.0f},
}};
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
alignas(256) std::array<std::array<std::byte, 4096>, Primitives.size()> LinePixels{};
struct Block {
explicit Block(std::size_t bytes) : bytes(bytes) {
#ifdef _WIN32
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
#endif
Require(data != nullptr, "cannot allocate the multisampled color target");
std::memset(data, 0, bytes);
GuestAllocations::Mutation mutation;
mutation.Add(data, bytes, true, true, true);
}
~Block() {
{
GuestAllocations::Mutation mutation;
mutation.Remove(data);
}
#ifdef _WIN32
VirtualFree(data, 0, MEM_RELEASE);
#else
std::free(data);
#endif
}
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
std::size_t bytes;
std::uint8_t* data = nullptr;
};
AgcDriver::QueueState LineQueue(const Block& block, std::uint32_t primitive) {
const auto half = std::bit_cast<std::uint32_t>(static_cast<float>(Side) / 2.0f);
const auto extent = (Side << 16u) | Side;
AgcDriver::QueueState queue;
queue.userConfig[0x242] = primitive;
queue.context = {
{0x2d5, 0x2000}, {0x1b6, 0}, {0x207, 0}, {0x200, 0}, {0x203, 0x800},
{0x2dc, 0xaa00}, {0x2f8, AaConfig4x}, {0x292, MsaaEnableClear}, {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},
{0x318, static_cast<std::uint32_t>(block.Address() >> 8u)}, {0x390, static_cast<std::uint32_t>(block.Address() >> 40u)},
{0x31b, 0}, {0x31c, Float16x4Info}, {0x31d, MultisampledAttrib},
{0x3b0, ((Side - 1u) << 14u) | (Side - 1u)}, {0x3b8, TiledAttrib3}, {0x1e0, 0},
{0xc, 0}, {0xd, extent}, {0x81, 0x80000000}, {0x82, extent},
{0x90, 0x80000000}, {0x91, extent}, {0x94, 0x80000000}, {0x95, extent},
{0x10f, half}, {0x110, half}, {0x111, std::bit_cast<std::uint32_t>(-static_cast<float>(Side) / 2.0f)}, {0x112, half},
{0x113, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0}, {0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)}
};
for (std::uint32_t offset = 0x2fe; offset <= 0x30d; ++offset) queue.context[offset] = (offset - 0x2feu) % 4u == 0 ? StandardLocations4x : 0u;
queue.shader[0x008] = 0x100;
queue.shader[0x009] = 0;
return queue;
}
AgcDriver::Graphics::State DecodeLines(const Block& block, std::uint32_t primitive) {
const auto state = AgcDriver::Graphics::DecodeState(LineQueue(block, primitive));
const auto& color = state.color;
Require(state.topology == (primitive == 2u ? VK_PRIMITIVE_TOPOLOGY_LINE_LIST : VK_PRIMITIVE_TOPOLOGY_LINE_STRIP), "VGT_PRIMITIVE_TYPE " + std::to_string(primitive) + " did not decode as a line list or strip");
Require(state.colors.size() == 1 && color.address == block.Address() && color.format == VK_FORMAT_R16G16B16A16_SFLOAT && color.samples == Samples && color.extent.width == Side && color.extent.height == Side, "the target did not decode as a 16x16 RGBA16F surface of 4 samples");
Require(state.samples == Samples && !state.depth && !state.perSampleShading && !state.alphaToCoverage, "the lines did not decode as a 4x draw without depth, sample shading or alpha-to-coverage");
const auto locations = AgcDriver::Graphics::SampleLocations(state);
for (std::uint32_t sample = 0; sample < Samples; ++sample) Require(locations[sample].x == 0.5f && locations[sample].y == 0.5f, "the decoded 4x line state does not put every sample at the pixel center");
Require(state.viewport.x == 0.0f && state.viewport.y == static_cast<float>(Side) && state.viewport.width == static_cast<float>(Side) && state.viewport.height == -static_cast<float>(Side) && state.scissor.offset.x == 0 && state.scissor.offset.y == 0 && state.scissor.extent.width == Side && state.scissor.extent.height == Side, "the decoded viewport or scissor does not cover the 16x16 target");
return state;
}
AgcDriver::Graphics::State OneSample(AgcDriver::Graphics::State state, std::span<std::byte> pixels, const AgcDriver::Graphics::ColorTargetLayout& linear) {
state.color = {reinterpret_cast<std::uintptr_t>(pixels.data()), {Side, Side}, VK_FORMAT_R8G8B8A8_UNORM, linear.Bytes(), 0xe4u};
state.colors = {state.color};
state.samples = 1;
state.customSampleLocations = false;
state.sampleLocations.fill(0);
state.sampleLocationGrid = false;
return state;
}
void DrawLines(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::State& state, std::span<const std::uint32_t> pixelCode) {
const auto target = device.Target();
std::vector<std::uint32_t> vertexUserData(4, 0u);
const auto address = reinterpret_cast<std::uintptr_t>(Lines.data());
const std::array<std::uint32_t, 4> vertexBuffer{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (16u << 16u), static_cast<std::uint32_t>(Lines.size()), 0x01016facu};
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
ShaderRecompiler::RecompileRequest vertex{
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
{WaveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
target,
{0, 0, 0, 64}
};
vertex.useCache = false;
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
ShaderRecompiler::ShaderPixelStageInfo pixel{};
pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
pixel.posX = true;
pixel.posY = true;
pixel.targetOutputMode[0] = 9;
pixel.targetExportMapping.fill(0xe4u);
const std::vector<std::uint32_t> pixelUserData(8, 0u);
const auto codeAddress = reinterpret_cast<std::uintptr_t>(pixelCode.data());
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}};
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, codeAddress, pixelCode, 0, {}},
{WaveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
target,
PixelPushLayout(vertexPush, target)
};
fragment.useCache = false;
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
{ShaderStage::Vertex, &vertexResult, 0},
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)}
}};
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Lines.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
device.WaitIdle();
}
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const Block& block) {
const auto address = block.Address();
const std::array<std::uint32_t, 8> texture{
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format16161616Float << 20u) | (((Side - 1u) & 3u) << 30u),
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
0xfacu | (SampleShift << 16u) | (TileRenderTarget << 20u) | (Type2DMsaa << 28u),
0u,
SampleShift << 4u,
0u,
0u,
};
const auto bufferAddress = reinterpret_cast<std::uintptr_t>(Buffer.data());
const std::array<std::uint32_t, 4> buffer{static_cast<std::uint32_t>(bufferAddress), static_cast<std::uint32_t>((bufferAddress >> 32u) & 0xffffu), static_cast<std::uint32_t>(Buffer.size() * 4u), 0x31016facu};
std::vector<std::uint32_t> userData(16, 0u);
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>(LoadCode.data()), std::as_bytes(std::span(LoadCode))}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(LoadCode.data()), LoadCode, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto program = ShaderRecompiler::Recompile(request);
std::vector<std::array<float, 4>> loaded(Side * Side * Samples);
for (std::uint32_t first = 0; first < loaded.size(); first += Threads) {
Buffer.fill(0xdeadbeefu);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto index = first + tid;
const auto pixel = index / Samples;
const std::array<std::uint32_t, 6> words{0u, 0u, 0u, pixel % Side, pixel / Side, index % Samples};
std::copy(words.begin(), words.end(), &Buffer[tid * Words]);
}
device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(LoadCode.data()));
device.WaitIdle();
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (std::uint32_t component = 0; component < 4u; ++component) loaded[first + tid][component] = std::bit_cast<float>(Buffer[tid * Words + LoadResult + component]);
}
}
return loaded;
}
bool NearEndpoint(std::uint32_t x, std::uint32_t y) {
return std::any_of(Lines.begin(), Lines.end(), [&](const std::array<float, 4>& vertex) {
const double px = (static_cast<double>(vertex[0]) + 1.0) * 0.5 * Side;
const double py = Side - (static_cast<double>(vertex[1]) + 1.0) * 0.5 * Side;
return std::fabs(x + 0.5 - px) < 1.5 && std::fabs(y + 0.5 - py) < 1.5;
});
}
void CompareLines(AgcDriver::VulkanDevice& device, const Block& block, std::uint32_t primitive, std::span<std::byte> pixels) {
const auto decoded = DecodeLines(block, primitive);
DrawLines(device, decoded, PixelCode);
const auto loaded = LoadSamples(device, block);
const AgcDriver::Graphics::ColorTargetLayout linear(Side, Side, AgcDriver::Graphics::ColorTileMode::Linear, 4u);
Require(linear.Bytes() <= pixels.size(), "the one-sample line target does not fit its memory");
std::fill(pixels.begin(), pixels.end(), std::byte{0});
DrawLines(device, OneSample(decoded, pixels, linear), Unorm8PixelCode);
std::uint32_t covered = 0;
std::uint32_t single = 0;
std::uint32_t compared = 0;
std::uint32_t differing = 0;
for (std::uint32_t y = 0; y < Side; ++y) {
for (std::uint32_t x = 0; x < Side; ++x) {
char where[80];
std::snprintf(where, sizeof(where), "VGT_PRIMITIVE_TYPE %u: pixel (%u, %u)", primitive, x, y);
std::uint32_t drawn = 0;
for (std::uint32_t sample = 0; sample < Samples; ++sample) {
const auto& texel = loaded[(y * Side + x) * Samples + sample];
Require(texel == Drawn || texel == std::array<float, 4>{}, std::string(where) + " of the 4x target holds neither the line color nor the cleared value");
drawn += texel == Drawn ? 1u : 0u;
}
Require(drawn == 0u || drawn == Samples, std::string(where) + " has " + std::to_string(drawn) + " of its 4 samples covered by a line with every sample at the pixel center");
const auto bytes = pixels.begin() + static_cast<std::ptrdiff_t>(linear.Offset(x, y));
const bool oneSample = std::equal(DrawnBytes.begin(), DrawnBytes.end(), bytes);
Require(oneSample || std::all_of(bytes, bytes + 4, [](std::byte value) { return value == std::byte{0}; }), std::string(where) + " of the one-sample target holds neither the line color nor zero");
covered += drawn == Samples ? 1u : 0u;
single += oneSample ? 1u : 0u;
if (NearEndpoint(x, y)) continue;
++compared;
differing += (drawn == Samples) != oneSample ? 1u : 0u;
Require(!oneSample || drawn == Samples, std::string(where) + " is drawn by the one-sample line but not covered in the 4x target");
}
}
Require(covered >= 20u && covered <= 2u * single && compared >= 200u, "VGT_PRIMITIVE_TYPE " + std::to_string(primitive) + ": the 4x lines covered " + std::to_string(covered) + " pixels and the one-sample lines " + std::to_string(single) + ", with " + std::to_string(compared) + " pixels compared");
std::printf("VGT_PRIMITIVE_TYPE %u: 4x lines with every sample at the pixel center covered %u pixels in full and none in part; the one-sample lines drew %u; %u of the %u pixels away from the endpoints differ\n", primitive, covered, single, differing, compared);
}
}
int main() {
try {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (!AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).storageImageMultisample) {
std::puts("skipped, the device has no shaderStorageImageMultisample to load the samples of a 4x target");
return VulkanTestSkipped;
}
if ((AgcDriver::VulkanDeviceTestAccess::ContextOf(*device).sampleLocationSampleCounts & Samples) == 0u) {
std::puts("skipped, the device has no VK_EXT_sample_locations at 4 samples to put every sample at the pixel center");
return VulkanTestSkipped;
}
Block listTarget(4u * BlockBytes);
Block stripTarget(4u * BlockBytes);
struct ReleaseDevice {
std::unique_ptr<AgcDriver::VulkanDevice>& value;
~ReleaseDevice() { value.reset(); }
} releaseDevice{device};
const std::array<const Block*, Primitives.size()> targets{&listTarget, &stripTarget};
for (std::size_t index = 0; index < Primitives.size(); ++index) CompareLines(*device, *targets[index], Primitives[index], LinePixels[index]);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -95,7 +95,9 @@ private:
#endif
};
int RunNativeClear(AgcDriver::VulkanDevice& device) {
enum class ClearConsumer { Owner, Texture, Attachment };
int RunNativeClear(AgcDriver::VulkanDevice& device, ClearConsumer consumer) {
using namespace ShaderRecompiler;
using namespace AgcDriver::Graphics;
const std::lock_guard lock(Memory::GpuMutex());
@@ -161,6 +163,22 @@ int RunNativeClear(AgcDriver::VulkanDevice& device) {
const auto generation = Memory::TrackerGeneration();
userData[4] = 0xfffffff0u;
device.Dispatch(compile(), groups, 1u, 1u, {}, reinterpret_cast<std::uintptr_t>(code.data()), nullptr, nullptr, htileClear);
std::puts("Native full BDA clear: checking sampled owner before explicit WaitIdle");
Require(context.recorder->PendingWriteOverlaps(target.htileAddress, metadataBytes), "the recognized HTILE clear is missing its pending write range");
switch (consumer) {
case ClearConsumer::Owner:
Require(DepthSurfaceHolds(context, DecodeTextureResource(words), sampled.get()), "a pending full BDA clear lost its native sampled owner");
break;
case ClearConsumer::Texture:
Require(CachedSampledTexture(context, words) == sampled, "a pending full BDA clear lost its native sampled view");
break;
case ClearConsumer::Attachment:
Require(DepthSurfaceView(context, target) == view, "a pending full BDA clear lost its native attachment");
break;
}
std::printf("Native full BDA clear: consumer=%u settled the recognized clear before WaitIdle\n", static_cast<unsigned>(consumer));
Require(!context.recorder->PendingWriteOverlaps(target.htileAddress, metadataBytes), "the native HTILE consumer did not settle its clear's pending write");
Require(Memory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation), "the native HTILE consumer did not complete its recognized clear");
device.WaitIdle();
for (std::size_t offset = 0; offset < BlockBytes; offset += 4u) {
std::uint32_t value = 0;
@@ -168,6 +186,7 @@ int RunNativeClear(AgcDriver::VulkanDevice& device) {
Require(value == (offset < metadataBytes ? 0xfffffff0u : 0u), "the native GPU clear did not write its exact metadata range");
}
std::printf("Native full BDA clear: exact %zu-byte metadata readback passed; complete=%d\n", metadataBytes, Memory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation));
Require(Memory::StoredAllSince_nid_no_patch(target.htileAddress, metadataBytes, generation), "the completed recognized native clear did not record exact coverage");
Require(CachedSampledTexture(context, words) == sampled, "a completed full BDA clear lost its native sampled view");
Require(DepthSurfaceView(context, target) == view, "a completed full BDA clear replaced its native attachment");
device.WaitIdle();
@@ -182,7 +201,11 @@ int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
return RunNativeClear(*device);
for (const auto consumer : {ClearConsumer::Owner, ClearConsumer::Texture, ClearConsumer::Attachment}) {
const auto result = RunNativeClear(*device, consumer);
if (result != 0) return result;
}
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
@@ -601,7 +601,8 @@ void CheckDrawFeedback() {
Require(device != nullptr, "native depth draw feedback requires Vulkan");
constexpr VkExtent2D extent{5u, 3u};
constexpr std::uint32_t samples = 4u;
TargetMemory memory(static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples))), metadata(65536u);
const auto depthBytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples));
TargetMemory memory(depthBytes), metadata(65536u);
std::memset(metadata.Data(), 0, 65536u);
DepthTarget depth{memory.Address(), 0u, extent, VK_FORMAT_D32_SFLOAT, 0.0f, 0u, metadata.Address(), false, samples};
depth.depthSwizzle = 24u;
@@ -634,6 +635,9 @@ void CheckDrawFeedback() {
const std::array<CompiledShader, 2> initial{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0u}, {ShaderRecompiler::ShaderStage::Fragment, &producer, pixelOffset}}};
device->Draw(state, draw, initial);
device->WaitIdle();
std::array<std::byte, 1> probe{};
TargetRefuses([&] { AgcDriver::GuestMemory::Read(depth.address, probe); }, "sample-preserving guest publication");
Require(DepthSurfaceAt(depth.address), "a refused guest read retired a native depth surface a draw wrote");
reader.spirv = DepthFeedbackFragmentShader(binding, true);
reader.variantId = 3u;
const auto words = DepthSampleWords(depth);
@@ -650,7 +654,22 @@ void CheckDrawFeedback() {
const std::array<CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0u}, {ShaderRecompiler::ShaderStage::Fragment, &reader, pixelOffset}}};
TargetRefuses([&] { device->Draw(state, draw, shaders); }, "sampling a native multisampled depth target the draw attaches");
device->WaitIdle();
std::puts("Native depth feedback refused through the draw resource path");
TargetMemory testedMemory(depthBytes), testedMetadata(65536u);
std::memset(testedMetadata.Data(), 0, 65536u);
const auto guestByte = [](std::size_t offset) { return static_cast<std::byte>(offset * 7u + 3u); };
for (std::size_t offset = 0u; offset < depthBytes; ++offset) testedMemory.Data()[offset] = guestByte(offset);
auto tested = depth;
tested.address = testedMemory.Address();
tested.htileAddress = testedMetadata.Address();
state.depth = tested;
state.depthWrite = false;
device->Draw(state, draw, initial);
device->WaitIdle();
Require(DepthSurfaceAt(tested.address), "a depth-test-only draw left no resident native depth surface");
AgcDriver::GuestMemory::Read(tested.address + depthBytes - 1u, probe);
Require(probe[0] == guestByte(depthBytes - 1u), "a guest read after a depth-test-only draw did not return its guest bytes");
Require(!DepthSurfaceAt(tested.address), "a guest read left a native depth surface only depth-tested resident");
std::puts("Native depth feedback refused through the draw resource path; a written surface refused and a depth-tested one retired on guest read");
}
enum class ClearFault { None, PartialStore, CpuWrite, DepthWrite, BadMetadata, UnprovenDispatch, PendingWrite, Overlap, PossibleStore, CompletedPossibleStore, DriverDispatch, ImportWindowWrite };
@@ -732,6 +751,7 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
}
const auto view = DepthSurfaceView(context, target);
Require(DepthSurfaceView(context, target) == view, "native depth attachment did not reuse its image");
NoteDepthSurfaceWrite(context, target);
std::array<std::byte, 1> beforeSampling{};
TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address + bytes - 1u, beforeSampling); }, "sample-preserving guest publication");
TargetRefuses([&] { RequireNoMultisampleTarget(target.htileAddress + metadataBytes - 1u, 1u, "a DMA copy"); }, "sample-preserving guest publication");
@@ -872,7 +892,15 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
});
if (fault == ClearFault::CpuWrite) metadata.Data()[metadataBytes - 1u] ^= std::byte{0x01};
if (fault == ClearFault::DepthWrite) memory.Data()[bytes - 1u] ^= std::byte{0x01};
if (fault == ClearFault::PendingWrite) QueueTargetByteWrite(context, recorder, target.htileAddress, std::byte{0});
if (fault == ClearFault::PendingWrite) {
QueueTargetByteWrite(context, recorder, target.htileAddress, std::byte{0});
std::string pendingRefusal;
try { CachedSampledTexture(context, words); }
catch (const std::exception& error) { pendingRefusal = error.what(); }
const bool refused = pendingRefusal.find("external GPU writes") != std::string::npos;
if (!refused) std::fprintf(stderr, "Pending HTILE texture refusal: %s\n", pendingRefusal.c_str());
Require(refused, "an unrecognized pending HTILE write was accepted or refused for another reason");
}
}
Require(DepthSurfaceHolds(context, DecodeTextureResource(words), texture.get()), "completed native depth clear lost its sampled view");
recorder.Sync();
@@ -936,6 +964,59 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
std::printf("Native depth format=%u samples=%u clear=%u iterate256=%u: imported, rasterized twice, sampled and refused conflicts\n", static_cast<std::uint32_t>(format), fragments, one, iterate256);
}
void CheckUnwrittenGuestAccess(Context context, VkFormat format, std::uint32_t fragments) {
const VkExtent2D extent{5u, 3u};
const auto bytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments));
const auto metadataBytes = static_cast<std::size_t>(HtileMetadataBytes(extent));
TargetMemory memory(bytes), metadata(65536u), unrelated(65536u);
const auto guestByte = [](std::size_t offset) { return static_cast<std::byte>(offset * 7u + 3u); };
for (std::size_t offset = 0u; offset < bytes; ++offset) memory.Data()[offset] = guestByte(offset);
std::memset(metadata.Data(), 0, metadataBytes);
DepthTarget target{memory.Address(), 0u, extent, format, 0.0f, 0u, metadata.Address(), false, fragments};
target.depthSwizzle = 24u;
target.iterate256 = target.htilePipeAligned = target.htileDepthOnly = true;
Recorder recorder(context);
recorder.Activate();
struct Cleanup {
Recorder& recorder;
Context context;
~Cleanup() { recorder.Sync(); ClearCachedTextures(context.device); ClearDepthSurfaces(context.device); }
} cleanup{recorder, context};
const auto resident = [&] { return HasResidentDepthGuards_nid_no_patch() && DepthSurfaceAt(target.address); };
auto view = DepthSurfaceView(context, target);
Require(view != VK_NULL_HANDLE && resident(), "unwritten native depth fixture has no resident owner");
auto otherDevice = context;
otherDevice.device = VK_NULL_HANDLE;
NoteDepthSurfaceWrite(otherDevice, target);
auto unattached = target;
unattached.address += 65536u;
NoteDepthSurfaceWrite(context, unattached);
std::array<std::byte, 1> probe{};
AgcDriver::GuestMemory::Read(unrelated.Address(), probe);
Require(resident(), "an unrelated guest read retired a native depth surface");
for (const bool readMetadata : {false, true}) {
if (readMetadata) RequireNoMultisampleTarget(target.htileAddress + metadataBytes - 1u, 1u, "a DMA copy");
else {
AgcDriver::GuestMemory::Read(target.address + bytes - 1u, probe);
Require(probe[0] == guestByte(bytes - 1u), "a guest read over an unwritten native depth surface did not return its guest bytes");
}
Require(!resident(), "a guest access left an unwritten native depth surface resident");
RequireNoResidentDepthRead_nid_no_patch(target.address, bytes);
RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes);
recorder.Sync();
const auto rebound = DepthSurfaceView(context, target);
Require(rebound != VK_NULL_HANDLE && rebound != view && resident(), "rebinding after a guest access did not import a new native depth surface");
view = rebound;
}
NoteDepthSurfaceWrite(context, target);
TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address, probe); }, "sample-preserving guest publication");
TargetRefuses([&] { RequireNoMultisampleTarget(target.htileAddress, 1u, "a DMA copy"); }, "sample-preserving guest publication");
Require(resident(), "a refused guest access retired a written native depth surface");
for (std::size_t offset = 0u; offset < bytes; ++offset) Require(memory.Data()[offset] == guestByte(offset), "retiring an unwritten native depth surface changed its guest bytes");
Require(std::all_of(metadata.Data(), metadata.Data() + metadataBytes, [](std::byte value) { return value == std::byte{0}; }), "retiring an unwritten native depth surface changed its HTILE");
std::printf("Native depth format=%u samples=%u: guest access retired the unwritten surface and refused the written one\n", static_cast<std::uint32_t>(format), fragments);
}
void CheckHtileRouting(Context context, VkFormat format, std::uint32_t fragments) {
const VkExtent2D extent{5u, 3u};
const auto bytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments));
@@ -1124,6 +1205,7 @@ void CheckRetirement(Context context, VkFormat format, std::uint32_t samples, st
const auto words = DepthSampleWords(target);
const auto resource = DecodeTextureResource(words);
const auto firstView = DepthSurfaceView(context, target);
NoteDepthSurfaceWrite(context, target);
auto firstTexture = DepthSurfaceTexture(context, words, resource, VkComponentMapping{});
Require(firstTexture != nullptr, "retirement fixture has no native sampled view");
ColorTarget color{colorMemory.Address(), extent, VK_FORMAT_R8G8B8A8_UNORM, extent.width * extent.height * 4u, 0u};
@@ -1279,6 +1361,7 @@ int main(int argc, char** argv) {
for (const bool iterate256 : {true, false}) { CheckDepthTarget(context, format, samples, one, false, std::nullopt, ClearFault::None, lock, iterate256); ++checked; }
}
CheckHtileRouting(context, format, samples);
CheckUnwrittenGuestAccess(context, format, samples);
}
}
CheckEligibility(context);
@@ -45,6 +45,7 @@ constexpr std::uint32_t Height = 128;
constexpr std::uint32_t ScissorWidth = 128;
constexpr std::size_t BlockBytes = 65536;
constexpr std::uint32_t Float11_11_10Info = 0x4171cu;
constexpr std::uint32_t StandardOrderInfo = Float11_11_10Info & ~0x1800u;
constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u;
constexpr std::uint32_t LinearAttrib3 = 0x09000000u;
constexpr std::uint32_t Float32Export = 9;
@@ -58,7 +59,7 @@ constexpr std::array<std::array<float, 4>, 3> Triangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
}};
alignas(256) std::array<std::array<std::uint32_t, 64>, 32> Programs{};
alignas(256) std::array<std::array<std::uint32_t, 64>, 64> Programs{};
std::size_t programCount = 0;
std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) {
@@ -78,7 +79,8 @@ std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) {
return (static_cast<std::uint32_t>(exponent - 1 + 15) << mantissaBits) | static_cast<std::uint32_t>(std::floor((fraction * 2.0 - 1.0) * scale));
}
std::uint32_t Packed(std::array<float, 3> rgb) {
std::uint32_t Packed(std::array<float, 3> rgb, bool standardOrder) {
if (standardOrder) return SmallFloat(rgb[0], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[2], 6) << 21u);
return SmallFloat(rgb[2], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[0], 6) << 21u);
}
@@ -170,17 +172,17 @@ struct Block {
std::uint8_t* data = nullptr;
};
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3) {
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3, bool standardOrder) {
AgcDriver::Registers cx;
cx[0x318] = static_cast<std::uint32_t>(block.Address() >> 8u);
cx[0x31b] = 0;
cx[0x31c] = Float11_11_10Info;
cx[0x31c] = standardOrder ? StandardOrderInfo : Float11_11_10Info;
cx[0x31d] = 0;
cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u);
cx[0x3b8] = attrib3;
cx[0x390] = static_cast<std::uint32_t>(block.Address() >> 40u);
auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == 0xc6u && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface");
Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == (standardOrder ? 0xe4u : 0xc6u) && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface");
return color;
}
@@ -290,10 +292,10 @@ std::string Describe(std::array<float, 3> rgb) {
return text;
}
void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind) {
void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind, bool standardOrder) {
const ColorTargetLayout layout(Width, Height, mode, 4);
Require(layout.Bytes() == block.bytes, "the test surface size disagrees with its layout");
const auto color = DecodeTarget(block, attrib3);
const auto color = DecodeTarget(block, attrib3, standardOrder);
auto before = Fill(block);
const float nan = std::numeric_limits<float>::quiet_NaN();
const float infinity = std::numeric_limits<float>::infinity();
@@ -311,12 +313,12 @@ void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t at
}};
for (const auto& rgb : cases) {
Draw(device, color, Float32Export, Float32Program(rgb));
ExpectSurface(device, block, layout, before, Packed(rgb), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target");
ExpectSurface(device, block, layout, before, Packed(rgb, standardOrder), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target");
before = ReadBack(device, block);
}
const std::array<float, 3> halves{0.5f, 0.75f, 0.125f};
Draw(device, color, Float16Export, Float16Program(halves));
ExpectSurface(device, block, layout, before, Packed(halves), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target");
ExpectSurface(device, block, layout, before, Packed(halves, standardOrder), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target");
}
}
@@ -327,8 +329,10 @@ int main() {
if (!device) return VulkanTestSkipped;
Block tiled(ColorTargetLayout(Width, Height, ColorTileMode::RenderTarget, 4).Bytes());
Block linear(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 4).Bytes());
TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, "tiled");
TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, "linear");
for (const bool standardOrder : {false, true}) {
TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, standardOrder ? "tiled standard-order" : "tiled", standardOrder);
TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, standardOrder ? "linear standard-order" : "linear", standardOrder);
}
std::puts("11_11_10 float color target tests passed");
return 0;
} catch (const std::exception& error) {
@@ -11,6 +11,7 @@
#include <cstdlib>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
@@ -75,16 +76,29 @@ void AppendLabel(std::vector<std::uint32_t>& words, std::uint32_t* target, std::
}
struct Blocker {
std::atomic<bool> release{false};
struct State {
std::atomic<bool> release{false};
std::atomic<bool> done{false};
};
std::shared_ptr<State> state = std::make_shared<State>();
std::uint32_t* target = nullptr;
~Blocker() { release = true; }
~Blocker() { Release(); }
void Release() { state->release = true; }
void Enqueue(std::vector<DrawPipeline::Range> writes = {}) {
DrawPipeline::Queue0().Enqueue([this] {
while (!release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
DrawPipeline::Queue0().Enqueue([state = state, target = target] {
while (!state->release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (target != nullptr) std::atomic_ref<std::uint32_t>(*target).store(1);
std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex());
{ std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); }
state->done = true;
}, std::move(writes));
}
void Join() const {
const auto start = std::chrono::steady_clock::now();
while (!state->done.load()) {
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the blocker never ran");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
};
void SubmitBehind(Blocker& blocker, std::vector<std::uint32_t>& words) {
@@ -107,7 +121,7 @@ void ReleaseWhen(Blocker& blocker, std::size_t reached, std::size_t passed) {
Require(now - start < std::chrono::seconds(20), "the queue worker never reached the fill");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
blocker.release = true;
blocker.Release();
}
void Finish() {
@@ -118,6 +132,11 @@ void Finish() {
std::_Exit(1);
}
idle.get();
const auto start = std::chrono::steady_clock::now();
while (DrawPipeline::Queue0().Outstanding() != 0) {
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the draw pipeline kept an item after the queue went idle");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Output.data()), sizeof(Output));
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Scratch.data()), sizeof(Scratch));
}
@@ -135,6 +154,7 @@ void InputsAfterQueuedLabels() {
SubmitBehind(blocker, words);
ReleaseWhen(blocker, 4, 5);
Finish();
blocker.Join();
for (std::size_t i = 0; i < Output.size(); ++i) {
const auto expected = i < 128 ? 0xababababu : 0xcafebabeu;
Require(Output[i] == expected, "a fill read its control or pattern before the queued labels that write them, at " + std::to_string(i));
@@ -156,6 +176,7 @@ void FillIntoFullPipeline() {
SubmitBehind(blocker, words);
ReleaseWhen(blocker, depth, depth + 1);
Finish();
blocker.Join();
for (std::size_t i = 0; i + 2 < depth; ++i) Require(Labels[i] == i + 1, "a queued label was not stored");
for (std::size_t i = 0; i < Output.size(); ++i) Require(Output[i] == (i < 256 ? 0x5a5a5a5au : 0xcafebabeu), "a fill behind a full draw pipeline did not land at " + std::to_string(i));
}
@@ -180,8 +201,9 @@ void SatisfiedWaitKeepsUnrelatedQueuedWork() {
while (DrawPipeline::Queue0().Outstanding() < 2 && std::chrono::steady_clock::now() < deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
const bool keptQueued = DrawPipeline::Queue0().Outstanding() >= 2;
blocker.release = true;
blocker.Release();
Finish();
blocker.Join();
Require(keptQueued, "a satisfied WAIT_REG_MEM drained unrelated queued work");
Require(marker == 1, "a label after a satisfied WAIT_REG_MEM did not execute");
}
@@ -211,11 +233,12 @@ void SatisfiedWaitDrainsOverlappingQueuedWork() {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
const bool drained = DrawPipeline::DrainRequestsByOpcode(0x3c) > drainsBefore;
const bool labelQueuedEarly = DrawPipeline::Queue0().Outstanding() >= 2;
blocker.release = true;
blocker.Release();
Require(submitted.wait_for(std::chrono::seconds(20)) == std::future_status::ready,
"WAIT_REG_MEM submission did not finish after the overlapping write");
submitted.get();
Finish();
blocker.Join();
Require(drained && !labelQueuedEarly, "a satisfied WAIT_REG_MEM skipped an overlapping queued write");
Require(marker == 1, "a label after an overlapping WAIT_REG_MEM did not execute");
}
@@ -57,6 +57,14 @@ std::array<std::array<float, 4>, 3> ClipPositions() {
return positions;
}
constexpr std::array<Vertex, 4> StripCorners{{{-0.8f, -0.8f, 1.0f}, {0.8f, -0.8f, 1.0f}, {-0.8f, 0.8f, 1.0f}, {0.8f, 0.8f, 1.0f}}};
std::array<std::array<float, 4>, 4> StripPositions() {
std::array<std::array<float, 4>, 4> positions{};
for (std::size_t i = 0; i < StripCorners.size(); ++i) positions[i] = {StripCorners[i].x, StripCorners[i].y, 0.5f, 1.0f};
return positions;
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
@@ -67,11 +75,13 @@ enum class Emulation {
Tessellation,
};
ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulation emulation, std::span<const std::uint32_t> pixelCode, std::uint32_t inputs, VkCullModeFlags cullMode = VK_CULL_MODE_NONE, bool flat = false, VkProvokingVertexModeEXT provoking = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT) {
ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulation emulation, std::span<const std::uint32_t> pixelCode, std::uint32_t inputs, VkCullModeFlags cullMode = VK_CULL_MODE_NONE, bool flat = false, VkProvokingVertexModeEXT provoking = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, bool strip = false) {
Pixels.fill(Unwritten);
auto target = device.Target();
target.fragmentShaderBarycentricEnabled = false;
static const auto positions = ClipPositions();
static const auto trianglePositions = ClipPositions();
static const auto stripPositions = StripPositions();
const auto positions = strip ? std::span<const std::array<float, 4>>(stripPositions) : std::span<const std::array<float, 4>>(trianglePositions);
std::vector<std::uint32_t> vertexUserData(4, 0u);
const auto vertexBuffer = BufferDescriptor(positions.data(), 16u, static_cast<std::uint32_t>(positions.size()));
@@ -125,7 +135,7 @@ ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, Emulatio
state.colors = {state.color};
state.hasColorTarget = true;
state.renderExtent = {Width, Height};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.topology = strip ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.negativeOneToOne = false;
state.scissor = {{0, 0}, {Width, Height}};
@@ -282,6 +292,87 @@ void CheckWinding(AgcDriver::VulkanDevice& device, Emulation emulation, const st
}
}
struct StripPixel {
bool inside;
std::array<std::size_t, 3> order;
std::array<double, 3> weights;
};
StripPixel StripBarycentrics(std::uint32_t x, std::uint32_t y, bool snapped) {
std::array<std::array<double, 2>, 4> window{};
for (std::size_t i = 0; i < StripCorners.size(); ++i) {
window[i] = {(StripCorners[i].x + 1.0) * Width / 2.0, (1.0 - StripCorners[i].y) * Height / 2.0};
if (snapped) {
for (auto& coordinate : window[i]) coordinate = std::round(coordinate * 256.0) / 256.0;
}
}
const double px = x + 0.5;
const double py = y + 0.5;
const auto edge = [](const std::array<double, 2>& a, const std::array<double, 2>& b, double cx, double cy) { return (b[0] - a[0]) * (cy - a[1]) - (b[1] - a[1]) * (cx - a[0]); };
for (const std::array<std::size_t, 3> order : {std::array<std::size_t, 3>{0, 1, 2}, std::array<std::size_t, 3>{1, 3, 2}}) {
const auto& a = window[order[0]];
const auto& b = window[order[1]];
const auto& c = window[order[2]];
const double area = edge(a, b, c[0], c[1]);
const std::array<double, 3> weights{edge(b, c, px, py) / area, edge(c, a, px, py) / area, edge(a, b, px, py) / area};
if (std::min({weights[0], weights[1], weights[2]}) > 0.02) return {true, order, weights};
}
return {false, {}, {}};
}
void CheckStrip(AgcDriver::VulkanDevice& device, Emulation emulation, const std::string& name) {
Draw(device, emulation, BarycentricPixelCode, PixelInputBit(PixelInput::LinearCenter), VK_CULL_MODE_NONE, false, VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, true);
std::array<std::uint32_t, 2> checked{};
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto expected = StripBarycentrics(x, y, false);
if (!expected.inside) continue;
const auto snapped = StripBarycentrics(x, y, true);
for (std::uint32_t channel = 0; channel < 4; ++channel) {
const auto reference = static_cast<float>(expected.weights[1u + (channel & 1u)]);
const auto snappedReference = snapped.inside && snapped.order == expected.order ? static_cast<float>(snapped.weights[1u + (channel & 1u)]) : reference;
Require(MatchesEither(Pixel(x, y, channel), reference, snappedReference), name + " triangle strip: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected " + std::to_string(reference));
}
++checked[expected.order[0]];
}
}
Require(checked[0] > 200u && checked[1] > 200u, name + " triangle strip: too few pixels of each triangle were checked");
static const auto positions = StripPositions();
Draw(device, emulation, VertexValuePixelCode, PixelInputBit(PixelInput::PerspectiveCenter), VK_CULL_MODE_NONE, false, VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT, true);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto expected = StripBarycentrics(x, y, false);
if (!expected.inside) continue;
const auto first = positions[expected.order[0]][0];
const std::array<float, 3> values{positions[expected.order[1]][0] - first, positions[expected.order[2]][0] - first, first};
for (std::uint32_t channel = 0; channel < 3; ++channel) {
Require(Pixel(x, y, channel) == values[channel], name + " triangle strip v_interp_mov: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected " + std::to_string(values[channel]));
}
}
}
std::printf("barycentric emulation through the %s passed for a triangle strip: %u and %u pixels of its even and odd triangle\n", name.c_str(), checked[0], checked[1]);
}
void CheckStripFlatLast(AgcDriver::VulkanDevice& device, Emulation emulation, const std::string& name) {
Draw(device, emulation, FlatBarycentricPixelCode, PixelInputBit(PixelInput::PerspectiveCenter), VK_CULL_MODE_NONE, true, VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT, true);
const auto positions = StripPositions();
std::array<std::uint32_t, 2> checked{};
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto expected = StripBarycentrics(x, y, false);
if (!expected.inside) continue;
const auto triangle = expected.order[0];
const auto provoking = triangle == 0u ? 2u : 3u;
for (std::uint32_t channel = 2; channel < 4; ++channel) {
Require(Pixel(x, y, channel) == positions[provoking][0], name + " flat last-provoking strip: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") flat channel " + std::to_string(channel) + " is " + std::to_string(Pixel(x, y, channel)) + ", expected vertex " + std::to_string(provoking) + " value " + std::to_string(positions[provoking][0]));
}
++checked[triangle];
}
}
Require(checked[0] > 200u && checked[1] > 200u, name + " flat last-provoking strip: too few pixels of each triangle were checked");
std::printf("flat last-provoking strip through %s passed: %u and %u pixels of its even and odd triangle\n", name.c_str(), checked[0], checked[1]);
}
void RunEmulation(AgcDriver::VulkanDevice& device, Emulation emulation) {
const std::string name = emulation == Emulation::Geometry ? "geometry stage" : "tessellation stages";
const auto perspective = PixelInputBit(PixelInput::PerspectiveCenter);
@@ -305,8 +396,10 @@ void RunEmulation(AgcDriver::VulkanDevice& device, Emulation emulation) {
if (emulation == Emulation::Tessellation) {
Draw(device, emulation, FlatBarycentricPixelCode, perspective, VK_CULL_MODE_NONE, true, VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT);
CheckFlat(name + " flat input, last provoking vertex", 2u);
CheckStripFlatLast(device, emulation, name);
}
std::printf("barycentric emulation through the %s passed: %u perspective and %u linear I/J pixels, raw vertex values, host interpolation, face culling and flat inputs\n", name.c_str(), smooth, noPerspective);
CheckStrip(device, emulation, name);
std::printf("barycentric emulation through the %s passed: %u perspective and %u linear I/J pixels, raw vertex values, host interpolation, face culling, flat inputs and triangle strips\n", name.c_str(), smooth, noPerspective);
}
}
@@ -20,6 +20,7 @@
#include <algorithm>
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@@ -30,6 +31,14 @@
#include <string_view>
#include <vector>
namespace AgcDriver {
struct VulkanDeviceTestAccess {
static Graphics::Context Context(const VulkanDevice& device) { return device.graphicsContext(); }
};
}
namespace {
using AgcDriver::Graphics::Require;
@@ -54,7 +63,12 @@ constexpr std::array<std::array<float, 4>, 3> HalfTriangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {1.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 1.0f, 0.5f, 1.0f}
}};
alignas(256) std::array<std::array<std::uint32_t, 16>, 4> Programs{};
constexpr std::array<std::array<float, 4>, 3> OtherHalfTriangle{{
{1.0f, -1.0f, 0.5f, 1.0f}, {1.0f, 1.0f, 0.5f, 1.0f}, {-1.0f, 1.0f, 0.5f, 1.0f}
}};
alignas(256) std::array<std::array<std::uint32_t, 16>, 8> Programs{};
alignas(256) std::array<std::byte, 256 * 1024> DepthMemory{};
std::size_t programCount = 0;
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
@@ -76,6 +90,22 @@ std::span<const std::uint32_t> Program(std::uint32_t coverageVgpr) {
return std::span<const std::uint32_t>(code.data(), at);
}
std::span<const std::uint32_t> SampleIndexProgram() {
Require(programCount < Programs.size(), "too many test programs");
auto& code = Programs[programCount++];
std::size_t at = 0;
code[at++] = 0xd5480018u;
code[at++] = 0x02111100u;
code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 24u);
code[at++] = 0x7e000280u | (21u << 17u);
code[at++] = 0x7e000280u | (22u << 17u);
code[at++] = 0x7e0002f2u | (23u << 17u);
code[at++] = 0xf800180fu;
code[at++] = 0x17161514u;
code[at++] = 0xbf810000u;
return std::span<const std::uint32_t>(code.data(), at);
}
struct Block {
explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) {
#ifdef _WIN32
@@ -150,7 +180,7 @@ AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t
return color;
}
void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, std::span<const std::uint32_t> pixelCode, std::span<const std::array<float, 4>, 3> triangle = Triangle, std::uint32_t rasterSamples = 1) {
void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, std::span<const std::uint32_t> pixelCode, std::span<const std::array<float, 4>, 3> triangle = Triangle, std::uint32_t rasterSamples = 1, const AgcDriver::Graphics::DepthTarget* depth = nullptr, bool perSample = false) {
const auto target = device.Target();
constexpr std::uint32_t waveSize = 64;
std::vector<std::uint32_t> vertexUserData(4, 0u);
@@ -171,6 +201,8 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge
pixel.inputAddr = inputAddr;
pixel.ancillary = (inputAddr & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary)) != 0u;
pixel.hasPerspectiveCenterVgpr = (inputAddr & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCenter)) != 0u;
pixel.sampleShading = perSample;
pixel.iteratesSamples = perSample;
pixel.targetOutputMode.at(0) = Float32AbgrExport;
pixel.targetExportMapping.fill(0xe4u);
const std::vector<std::uint32_t> pixelUserData(8, 0u);
@@ -207,7 +239,15 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge
state.blend = blend;
state.blendConstants = {};
state.samples = rasterSamples;
state.mergedCoverage = rasterSamples > 1;
state.mergedCoverage = rasterSamples > 1 && depth == nullptr;
state.truncatedCoverage = rasterSamples > 1 && depth != nullptr;
state.perSampleShading = perSample;
if (depth != nullptr) {
state.depth = *depth;
state.depthTest = true;
state.depthWrite = false;
state.depthCompare = VK_COMPARE_OP_ALWAYS;
}
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(triangle.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
}
@@ -273,6 +313,67 @@ bool ExpectMerged(AgcDriver::VulkanDevice& device) {
return true;
}
std::string ExpectTruncated(AgcDriver::VulkanDevice& device) {
if ((AgcDriver::VulkanDeviceTestAccess::Context(device).limits.framebufferDepthSampleCounts & VK_SAMPLE_COUNT_2_BIT) == 0) return "the device has no two-sample depth targets (framebufferDepthSampleCounts)";
constexpr std::size_t bytes = std::size_t{Width} * Height * 16u;
AgcDriver::Graphics::DepthTarget depth{reinterpret_cast<std::uintptr_t>(DepthMemory.data()), 0u, {Width, Height}, VK_FORMAT_D32_SFLOAT, 0.0f, 0};
depth.samples = 2;
std::array<std::uint32_t, 4> written{};
std::uint32_t untouched = 0;
for (const auto* triangle : {&HalfTriangle, &OtherHalfTriangle}) {
Block block(bytes);
std::fill(block.data, block.data + bytes, std::uint8_t{0xff});
const auto color = DecodeTarget(block, Rgba32FloatInfo);
try {
Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage), Program(0u), *triangle, 2u, &depth);
} catch (const std::exception& error) {
if (std::string_view(error.what()).find("truncated coverage reduction") == std::string_view::npos) throw;
return "the device lacks VK_AMD_mixed_attachment_samples and VK_NV_framebuffer_mixed_samples with truncated coverage reduction";
}
const auto stored = ReadBack(device, block.Address(), bytes);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto* texel = stored.data() + (static_cast<std::size_t>(y) * Width + x) * 16u;
if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) {
++untouched;
continue;
}
std::array<float, 4> value{};
std::memcpy(value.data(), texel, sizeof(value));
const auto mask = static_cast<std::uint32_t>(value[0]);
Require(value[0] == static_cast<float>(mask) && mask >= 1u && mask <= 3u && value[1] == 0.0f && value[2] == 0.0f && value[3] == 1.0f, "truncated coverage: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + ")");
++written[mask];
}
}
}
Require(written[2] == 0, "truncated coverage: " + std::to_string(written[2]) + " pixels that cover only sample 1 were written into the one-sample target");
Require(written[1] != 0 && written[3] != 0 && untouched != 0, "truncated coverage: two half-covering triangles wrote " + std::to_string(written[3]) + " fully covered and " + std::to_string(written[1]) + " sample-0-only pixels and left " + std::to_string(untouched));
std::uint32_t perSampleWritten = 0;
std::uint32_t perSampleUntouched = 0;
for (const auto* triangle : {&HalfTriangle, &OtherHalfTriangle}) {
Block block(bytes);
std::fill(block.data, block.data + bytes, std::uint8_t{0xff});
const auto color = DecodeTarget(block, Rgba32FloatInfo);
Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary), SampleIndexProgram(), *triangle, 2u, &depth, true);
const auto stored = ReadBack(device, block.Address(), bytes);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto* texel = stored.data() + (static_cast<std::size_t>(y) * Width + x) * 16u;
if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) {
++perSampleUntouched;
continue;
}
std::array<float, 4> value{};
std::memcpy(value.data(), texel, sizeof(value));
Require(value == std::array<float, 4>{0.0f, 0.0f, 0.0f, 1.0f}, "truncated coverage per sample: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + "), not sample 0's output");
++perSampleWritten;
}
}
}
Require(perSampleWritten == written[1] + written[3] && perSampleUntouched == untouched, "truncated coverage per sample: the draws wrote " + std::to_string(perSampleWritten) + " pixels where one invocation per pixel wrote " + std::to_string(written[1] + written[3]));
return {};
}
}
int main() {
@@ -285,6 +386,7 @@ int main() {
Expect(*device, PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::SampleCoverage), 2u, "SAMPLE_COVERAGE after the perspective center");
Expect(*device, PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::SampleCoverage), 1u, "SAMPLE_COVERAGE after ANCILLARY");
if (!ExpectMerged(*device)) std::puts("merged coverage not tested: the device lacks VK_NV_framebuffer_mixed_samples with merged coverage reduction");
if (const auto skipped = ExpectTruncated(*device); !skipped.empty()) std::printf("truncated coverage not tested: %s\n", skipped.c_str());
std::puts("pixel sample coverage tests passed");
return 0;
} catch (const std::exception& error) {
@@ -9,6 +9,9 @@
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "Recompiler.hpp"
#include "Optimization/ResourceProgram.hpp"
#include "VulkanTestDevice.hpp"
@@ -17,6 +20,8 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
@@ -120,14 +125,31 @@ constexpr std::array<std::array<float, 4>, 3> Triangle{{
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
struct Block {
explicit Block(std::size_t bytes) : bytes(bytes) {
explicit Block(std::size_t bytes, bool watched = false) : bytes(bytes), watched(watched) {
#ifdef _WIN32
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (watched) {
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
} else {
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
}
#else
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
if (watched) {
void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(raw != MAP_FAILED, "cannot map the color target");
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
data = reinterpret_cast<std::uint8_t*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
} else {
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
}
#endif
Require(data != nullptr, "cannot allocate the multisampled color target");
Require(data != nullptr, "cannot allocate the color target");
std::memset(data, 0, bytes);
Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched");
GuestAllocations::Mutation mutation;
mutation.Add(data, bytes, true, true, true);
}
@@ -137,15 +159,26 @@ struct Block {
mutation.Remove(data);
}
#ifdef _WIN32
VirtualFree(data, 0, MEM_RELEASE);
if (watched) {
GuestArena::GuestArenaReset_nid_postfix(data, bytes);
GuestArena::GuestArenaRelease_nid_postfix(data, bytes);
} else {
VirtualFree(data, 0, MEM_RELEASE);
}
#else
std::free(data);
if (watched) {
munmap(data, bytes);
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes);
} else {
std::free(data);
}
#endif
}
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
std::size_t bytes;
bool watched;
std::uint8_t* data = nullptr;
};
@@ -214,7 +247,7 @@ AgcDriver::Graphics::State TargetState(const AgcDriver::Graphics::ColorTarget& c
state.blends = {blend};
state.blend = blend;
state.blendConstants = {};
state.samples = Samples;
state.samples = color.samples;
return state;
}
@@ -349,19 +382,19 @@ void RequireStoreRefused(AgcDriver::VulkanDevice& device, const std::array<std::
Require(refusal.find(expected) != std::string::npos, what + " was not refused: " + refusal);
}
std::vector<std::array<float, 4>> RunLoad(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, bool arrayed) {
std::vector<std::array<float, 4>> RunLoad(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, bool arrayed, std::uint32_t fragments) {
std::vector<std::uint32_t> userData;
const auto program = CompileLoad(device, texture, userData, arrayed);
std::vector<std::array<float, 4>> loaded(Side * Side * Samples);
std::vector<std::array<float, 4>> loaded(Side * Side * fragments);
const auto codeAddress = reinterpret_cast<std::uintptr_t>((arrayed ? ArrayLoadCode : LoadCode).data());
for (std::uint32_t first = 0; first < loaded.size(); first += Threads) {
Buffer.fill(0xdeadbeefu);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto index = first + tid;
const auto pixel = index / Samples;
const auto pixel = index / fragments;
const std::array<std::uint32_t, 6> words = arrayed
? std::array<std::uint32_t, 6>{0u, 0u, pixel % Side, pixel / Side, 0u, index % Samples}
: std::array<std::uint32_t, 6>{0u, 0u, 0u, pixel % Side, pixel / Side, index % Samples};
? std::array<std::uint32_t, 6>{0u, 0u, pixel % Side, pixel / Side, 0u, index % fragments}
: std::array<std::uint32_t, 6>{0u, 0u, 0u, pixel % Side, pixel / Side, index % fragments};
std::copy(words.begin(), words.end(), &Buffer[tid * Words]);
}
device.Dispatch(program, 1, 1, 1, {}, codeAddress);
@@ -373,9 +406,9 @@ std::vector<std::array<float, 4>> RunLoad(AgcDriver::VulkanDevice& device, const
return loaded;
}
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture) {
auto loaded = RunLoad(device, texture, false);
const auto loadedArray = RunLoad(device, texture, true);
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, std::uint32_t fragments = Samples) {
auto loaded = RunLoad(device, texture, false, fragments);
const auto loadedArray = RunLoad(device, texture, true, fragments);
Require(loadedArray == loaded, "Dim2DMsaaArray image_load disagreed with Dim2DMsaa image_load on the multisampled color target");
return loaded;
}
@@ -643,12 +676,210 @@ int NativeVertexInput(std::unique_ptr<AgcDriver::VulkanDevice>& device) {
return 0;
}
alignas(256) constexpr std::array<std::uint32_t, 10> NativeFmaskCode{0x34020084u, 0xe0381000u, 0x80000201u, 0xbf8c3f70u, 0xf0001f08u, 0x00020602u, 0xbf8c3f70u, 0xe0781000u, 0x80010601u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, Threads * 4u> NativeFmaskInput{};
alignas(256) std::array<std::uint32_t, Threads * 4u + 4u> NativeFmaskOutput{};
std::array<std::uint32_t, 8> NativeFmaskDescriptor(const Block& fmask, const Block& cmask, std::uint32_t pipeXor) {
const auto address = fmask.Address() | pipeXor;
const auto metadata = cmask.Address();
return {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>(address >> 40u) | (166u << 20u) | (((Side - 1u) & 3u) << 30u), ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), 4u | (1u << 3u) | (4u << 6u) | (24u << 20u) | (9u << 28u), 0u, 0u, (1u << 19u) | (1u << 21u) | (static_cast<std::uint32_t>(metadata >> 8u) << 24u), static_cast<std::uint32_t>(metadata >> 16u)};
}
ShaderRecompiler::RecompileResult CompileNativeFmask(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& image, bool registration) {
std::vector<std::uint32_t> userData(16u);
const auto buffer = [](const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return std::array<std::uint32_t, 4>{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) & 0xffffu, bytes, 0x31016facu};
};
const auto input = buffer(NativeFmaskInput.data(), sizeof(NativeFmaskInput));
const auto output = buffer(NativeFmaskOutput.data(), Threads * 16u);
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4u);
std::copy(image.begin(), image.end(), userData.begin() + 8u);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data()), std::as_bytes(std::span(NativeFmaskCode))}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1u, 1u}, 0u, {false, false, false}, false, 1u};
auto target = device.Target();
target.registrationModules = registration;
ShaderRecompiler::RecompileRequest request{{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data()), NativeFmaskCode, 0u, {}}, {32u, 0u, userData, compute, std::nullopt, std::nullopt, memory}, target, {0u, 0u, 0u, 128u}};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
void DispatchNativeFmask(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& image, bool registration) {
device.Dispatch(CompileNativeFmask(device, image, registration), 1u, 1u, 1u);
device.WaitIdle();
}
void NativeFmask(std::unique_ptr<AgcDriver::VulkanDevice>& device) {
using namespace AgcDriver::Graphics;
Block colorMemory(4u * BlockBytes, true);
Block dcc(BlockBytes, true);
Block cmask(BlockBytes, true);
Block fmask(BlockBytes, true);
Block unrelated(BlockBytes, true);
struct ReleaseDevice {
std::unique_ptr<AgcDriver::VulkanDevice>& value;
~ReleaseDevice() { value.reset(); }
} releaseDevice{device};
constexpr std::uint32_t sentinel = 0xdeadbeefu;
for (const auto pipeXor : {0u, 0xf00u}) {
std::memset(colorMemory.data, 0, colorMemory.bytes);
std::memset(dcc.data, 0, dcc.bytes);
std::memset(cmask.data, 0xcc, cmask.bytes);
std::memset(fmask.data, 0x11, fmask.bytes);
AgcDriver::Registers cx{{0x318, static_cast<std::uint32_t>(colorMemory.Address() >> 8u)}, {0x31b, 0u}, {0x31c, Float16x4Info | 0x10006000u}, {0x31d, (3u << 12u) | (3u << 15u)}, {0x31e, 0u}, {0x31f, static_cast<std::uint32_t>(cmask.Address() >> 8u)}, {0x321, static_cast<std::uint32_t>((fmask.Address() | pipeXor) >> 8u)}, {0x325, static_cast<std::uint32_t>(dcc.Address() >> 8u)}, {0x390, static_cast<std::uint32_t>(colorMemory.Address() >> 40u)}, {0x398, static_cast<std::uint32_t>(cmask.Address() >> 40u)}, {0x3a0, static_cast<std::uint32_t>(fmask.Address() >> 40u)}, {0x3a8, static_cast<std::uint32_t>(dcc.Address() >> 40u)}, {0x3b0, ((Side - 1u) << 14u) | (Side - 1u)}, {0x3b8, TiledAttrib3}};
const auto color = DecodeColorBuffer(cx, 0u);
Require(color.samples == 8u && color.cmaskAddress == cmask.Address(), "native FMASK fixture lost its decoded target association");
std::array<std::array<float, 4>, 8> expected{};
for (std::uint32_t sample = 0u; sample < 8u; ++sample) {
expected[sample] = {static_cast<float>(sample + 1u) / 8.0f, 0.25f, 0.5f, 1.0f};
auto pixel = PixelCode;
pixel[1] = std::bit_cast<std::uint32_t>(expected[sample][0]);
pixel[3] = std::bit_cast<std::uint32_t>(expected[sample][1]);
pixel[5] = std::bit_cast<std::uint32_t>(expected[sample][2]);
pixel[7] = std::bit_cast<std::uint32_t>(expected[sample][3]);
auto state = TargetState(color);
state.quadSampleMask.fill(static_cast<std::uint16_t>(1u << sample));
Draw(*device, state, 0u, 2u, pixel);
}
const auto native = LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u);
std::vector<std::uint32_t> covered;
for (std::uint32_t pixel = 0; pixel < Side * Side; ++pixel) {
bool all = true;
for (std::size_t sample = 0; sample < 8u; ++sample) all = all && native[pixel * 8u + sample] == expected[sample];
if (all) covered.push_back(pixel);
}
Require(!covered.empty(), "native FMASK fixture did not render eight distinct sample colors");
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
const auto pixel = covered[lane % covered.size()];
NativeFmaskInput[lane * 4u] = pixel % Side;
NativeFmaskInput[lane * 4u + 1u] = pixel / Side;
NativeFmaskInput[lane * 4u + 2u] = 0u;
NativeFmaskInput[lane * 4u + 3u] = 0u;
}
const auto image = NativeFmaskDescriptor(fmask, cmask, pipeXor);
const auto check = [&] {
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
const std::array<std::uint32_t, 4> mapping{0x76543210u, 1u, 0x76543210u, 0u};
for (std::size_t component = 0; component < 4u; ++component) Require(NativeFmaskOutput[lane * 4u + component] == mapping[component], "native FMASK load did not preserve its expanded sample indices and selectors");
for (std::uint32_t sample = 0; sample < 8u; ++sample) {
const auto fragment = NativeFmaskOutput[lane * 4u] >> (4u * sample) & 15u;
const auto pixel = NativeFmaskInput[lane * 4u + 1u] * Side + NativeFmaskInput[lane * 4u];
Require(native[pixel * 8u + fragment] == expected[sample], "native FMASK remapped a sampled color to another fragment");
}
}
Require(std::all_of(NativeFmaskOutput.begin() + Threads * 4u, NativeFmaskOutput.end(), [](auto word) { return word == sentinel; }), "native FMASK wrote beyond its output");
};
for (const auto registration : {false, true}) {
for (std::uint32_t repeat = 0; repeat < 2u; ++repeat) {
NativeFmaskOutput.fill(sentinel);
DispatchNativeFmask(*device, image, registration);
check();
}
const auto program = CompileNativeFmask(*device, image, registration);
const auto codeAddress = reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data());
std::shared_ptr<const AgcDriver::Recipe> recipe;
NativeFmaskOutput.fill(sentinel);
device->Dispatch(program, 1u, 1u, 1u, {}, codeAddress, nullptr, &recipe);
device->WaitIdle();
check();
Require(recipe == nullptr, "native FMASK emitted a reusable recipe without an ownership proof");
const auto prepared = device->PrepareDispatch(program, {}, Threads, codeAddress);
Require(prepared != nullptr, "native FMASK did not prepare a deferred dispatch");
NativeFmaskOutput.fill(sentinel);
device->Dispatch(program, 1u, 1u, 1u, {}, codeAddress, prepared);
device->WaitIdle();
check();
}
Require(LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u) == native, "FMASK sampling changed the native samples");
Require(std::all_of(fmask.data, fmask.data + fmask.bytes, [](auto byte) { return byte == 0x11u; }) && std::all_of(cmask.data, cmask.data + cmask.bytes, [](auto byte) { return byte == 0xccu; }), "native FMASK sampling published or changed guest metadata");
const auto refused = [&](const auto& descriptor, std::string_view reason) {
NativeFmaskOutput.fill(sentinel);
std::string error;
try { DispatchNativeFmask(*device, descriptor, false); } catch (const std::runtime_error& e) { error = e.what(); }
Require(error.find(reason) != std::string::npos, "native FMASK missing refusal: " + error);
Require(std::all_of(NativeFmaskOutput.begin(), NativeFmaskOutput.end(), [sentinel](auto word) { return word == sentinel; }), "rejected native FMASK wrote output");
};
refused(NativeFmaskDescriptor(unrelated, cmask, pipeXor), "descriptor does not match");
refused(NativeFmaskDescriptor(fmask, unrelated, pipeXor), "descriptor does not match");
refused(NativeFmaskDescriptor(fmask, cmask, pipeXor ^ 0x100u), "descriptor does not match");
auto resized = image;
resized[2] += 1u;
refused(resized, "extent or subresource does not match");
const auto deferredProgram = CompileNativeFmask(*device, image, false);
const auto codeAddress = reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data());
const auto preparedBeforeStore = device->PrepareDispatch(deferredProgram, {}, Threads, codeAddress);
Require(preparedBeforeStore != nullptr, "native FMASK did not prepare its metadata invalidation probe");
std::array<std::byte, 4> same{};
same.fill(std::byte{0x11});
AgcDriver::GuestMemory::Write(fmask.Address(), same);
refused(image, "writes to a resident native FMASK");
NativeFmaskOutput.fill(sentinel);
std::string deferredError;
try {
device->Dispatch(deferredProgram, 1u, 1u, 1u, {}, codeAddress, preparedBeforeStore);
device->WaitIdle();
} catch (const std::runtime_error& error) {
deferredError = error.what();
}
Require(deferredError.find("writes to a resident native FMASK") != std::string::npos, "deferred native FMASK missed its metadata store: " + deferredError);
Require(std::all_of(NativeFmaskOutput.begin(), NativeFmaskOutput.end(), [](auto word) { return word == sentinel; }), "rejected deferred native FMASK wrote output");
device->SubmitRecorded(true);
device->WaitIdle();
ClearMultisampleTargets(device->Device());
ClearCachedTextures(device->Device());
cx[0x31c] &= ~0x2000u;
cx[0x31f] = 0u;
cx[0x398] = 0u;
const auto withoutCmask = DecodeColorBuffer(cx, 0u);
Require(withoutCmask.cmaskAddress == 0u, "the no-CMASK control retained metadata");
Draw(*device, TargetState(withoutCmask));
const auto uncompressed = LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u);
Require(std::find(uncompressed.begin(), uncompressed.end(), Drawn) != uncompressed.end(), "tracking native FMASK broke ordinary DCC rendering without CMASK");
device->SubmitRecorded(true);
device->WaitIdle();
ClearMultisampleTargets(device->Device());
ClearCachedTextures(device->Device());
Draw(*device, TargetState(color));
auto recreated = color;
recreated.pipeBankXor ^= 0x4000u;
recreated.fmaskAddress = 0u;
recreated.fmaskPipeBankXor = 0u;
Draw(*device, TargetState(recreated));
std::array<std::byte, 4> formerFmask{};
formerFmask.fill(std::byte{0x22});
AgcDriver::GuestMemory::Write(fmask.Address(), formerFmask);
Draw(*device, TargetState(recreated));
auto recreatedDescriptor = TargetDescriptor(colorMemory, 3u);
recreatedDescriptor[0] |= recreated.pipeBankXor >> 8u;
const auto recreatedSamples = LoadSamples(*device, recreatedDescriptor, 8u);
Require(std::find(recreatedSamples.begin(), recreatedSamples.end(), Drawn) != recreatedSamples.end(), "former FMASK metadata affected the recreated native target");
device->SubmitRecorded(true);
device->WaitIdle();
ClearMultisampleTargets(device->Device());
ClearCachedTextures(device->Device());
std::printf("native FMASK: XOR=0x%x, eight distinct samples, both registration modes, cached reuse, association/extent/write refusals and unchanged guest metadata passed\n", pipeXor);
}
}
}
int main(int argc, char** argv) {
try {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (argc == 2 && std::string_view(argv[1]) == "--native-fmask") {
{
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device);
if (!context.storageImageMultisample || (context.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_8_BIT) == 0) {
std::puts("native FMASK skipped, the device does not render or store 8-sample color");
return VulkanTestSkipped;
}
}
NativeFmask(device);
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--native-vertex-input") return NativeVertexInput(device);
Block block(4u * BlockBytes);
Block other(BlockBytes);
@@ -76,6 +76,41 @@ void ClosingJoinsPreparation() {
Require(ranHere && RegistrationPreparationThreads() == 0, "a closed preparation pool restarted after being joined");
}
void ClosingDropsQueuedPrebuilds() {
using namespace AgcDriver::DriverDetail;
std::promise<void> started;
auto running = started.get_future();
std::promise<void> gate;
auto opened = gate.get_future().share();
bool queuedRan = false;
SubmitLibraryPrebuild([&] { started.set_value(); opened.wait(); });
for (int index = 0; index < 3; ++index) SubmitLibraryPrebuild([&] { queuedRan = true; });
running.wait();
auto entering = std::make_shared<std::promise<void>>();
auto entered = entering->get_future();
auto awaited = std::make_shared<std::promise<void>>();
auto woken = awaited->get_future();
std::thread([entering, awaited] {
entering->set_value();
AwaitLibraryPrebuilds();
awaited->set_value();
}).detach();
entered.wait();
const bool waiting = woken.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout;
auto closing = std::async(std::launch::async, [] { CloseRegistrationPreparation(); });
const bool waited = closing.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout;
gate.set_value();
const bool returned = closing.wait_for(std::chrono::seconds(10)) == std::future_status::ready;
const bool wokeUp = woken.wait_for(std::chrono::seconds(10)) == std::future_status::ready;
closing.get();
Require(waiting, "a waiter for the library prebuilds returned while one was still running");
Require(waited, "closing returned while a library prebuild was still running");
Require(returned, "closing did not return once the running library prebuild ended");
Require(wokeUp, "a waiter for the library prebuilds did not return after closing dropped the queued ones");
Require(!queuedRan, "closing ran the queued library prebuilds");
Require(RegistrationPreparationThreads() == 0, "closing the library prebuilds left their thread running");
}
void StoppingJoinsCompareHelpers() {
using namespace AgcDriver::Graphics;
const std::vector<std::byte> first(std::size_t{2} << 20u, std::byte{1});
@@ -168,6 +203,7 @@ int main(int argc, char** argv) {
try {
const std::string_view mode = argc > 1 ? argv[1] : "";
if (mode == "--joined") ClosingJoinsPreparation();
else if (mode == "--queued-prebuilds") ClosingDropsQueuedPrebuilds();
else if (mode == "--compare-helpers") StoppingJoinsCompareHelpers();
else if (mode == "--draw-committer") ShutdownJoinsDrawCommitter();
else if (mode == "--shader-cache-writer") ShutdownJoinsShaderCacheWriter();
@@ -22,6 +22,7 @@
#include <span>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -946,6 +947,28 @@ void RequireMergedHullWithoutSubgroups(const ShaderRecompiler::SpirvTarget& targ
}
}
void RequireMergedHullConstantIndices(const ShaderRecompiler::SpirvTarget& target) {
std::uint32_t cursor = 0;
const auto hull = Compile(target, ShaderStage::TessellationControl, MergedHullCode, cursor, MergedTriangles);
const auto words = hull.spirv.Words();
std::unordered_set<std::uint32_t> constants;
std::unordered_set<std::uint32_t> folded;
for (std::size_t at = 5; at + 1u < words.size(); at += words[at] >> 16u) {
const auto count = words[at] >> 16u;
Require(count != 0u && at + count <= words.size(), "the merged hull program compiled to a malformed SPIR-V instruction");
const auto opcode = words[at] & 0xffffu;
if (opcode == spv::OpConstant && count >= 3u) constants.insert(words[at + 2u]);
const bool integer = opcode == spv::OpIAdd || opcode == spv::OpIMul || opcode == spv::OpUDiv || opcode == spv::OpUMod || opcode == spv::OpShiftRightLogical || opcode == spv::OpBitwiseAnd;
const auto operand = [&](std::uint32_t id) { return constants.contains(id) || folded.contains(id); };
if (integer && count == 5u && operand(words[at + 3u]) && operand(words[at + 4u])) folded.insert(words[at + 2u]);
if (opcode == spv::OpAccessChain) {
for (std::uint32_t index = 4u; index < count; ++index) {
Require(!folded.contains(words[at + index]), "the merged hull program indexes a tessellation attribute with arithmetic on constants instead of a constant, which AMD's Windows driver reads wrongly");
}
}
}
}
void RequireRegisteredMergedStages() {
const auto low = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 8u); };
const auto high = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 40u); };
@@ -1003,6 +1026,7 @@ int main() {
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullOutsidePatch, "reads LDS outside its patch's control point records", MergedTriangles);
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullReadLane, "another lane's register", MergedTriangles);
RequireMergedHullWithoutSubgroups(target);
RequireMergedHullConstantIndices(target);
RequirePatches(*device, target, MergedHullCode, "merged hull program", MergedTriangles, MergedHullCode);
RequirePatches(*device, target, MergedHullRegisters, "merged hull program storing its local part's registers", MergedTriangles, MergedHullRegisters);
RequireCounterClockwise(*device, target);
@@ -62,6 +62,11 @@ if(BUILD_TESTING)
target_link_libraries(guest_ajm_vibration_tests PRIVATE libSceAjm.native libc)
configure_windows_unwind(guest_ajm_vibration_tests)
add_test(NAME guest_ajm_vibration COMMAND guest_ajm_vibration_tests)
add_test_executable(guest_ajm_wvorbis_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmWVorbis.cpp)
target_include_directories(guest_ajm_wvorbis_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ajm_wvorbis_tests PRIVATE libSceAjm.native libc)
configure_windows_unwind(guest_ajm_wvorbis_tests)
add_test(NAME guest_ajm_wvorbis COMMAND guest_ajm_wvorbis_tests)
if(UNIX)
add_test_executable(guest_ajm_teardown_tests ${LIBS_INCLUDE_DIR}/tests/GuestAjmTeardown.cpp)
target_include_directories(guest_ajm_teardown_tests PRIVATE ${LIBS_INCLUDE_DIR})
@@ -70,7 +75,7 @@ if(BUILD_TESTING)
add_test(NAME guest_ajm_teardown COMMAND guest_ajm_teardown_tests $<TARGET_FILE:libSceAjm.native>)
endif()
if(WIN32)
set_tests_properties(guest_ajm guest_ajm_vibration PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(guest_ajm guest_ajm_vibration guest_ajm_wvorbis PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAjm.native>")
endif()
endif()
+72 -2
View File
@@ -4,6 +4,7 @@
#include "prx/libc/include/General.hpp"
#include <atomic>
#include <cstdio>
#include <cstring>
// Entry points this library does not implement: reported before the unimplemented-call exception so an
// APS5_TRACE_AJM run shows which one a title reached.
@@ -43,6 +44,38 @@ static_assert(offsetof(WVorbisHeaderPacketParameter, setupPacket) == 32);
static_assert(sizeof(WVorbisSetupPacketParameter) == 56);
static_assert(offsetof(WVorbisSetupPacketParameter, input) == 16);
namespace {
constexpr int AJM_WVORBIS_ERROR_INVALID_PARAMETER = static_cast<int>(0x8A6E0001);
constexpr int AJM_WVORBIS_ERROR_INVALID_SIZE = static_cast<int>(0x8A6E0002);
constexpr int AJM_WVORBIS_ERROR_OUTPUT_TOO_SMALL = static_cast<int>(0x8A6E0003);
constexpr int AJM_WVORBIS_ERROR_INVALID_SETUP = static_cast<int>(0x8A6E0004);
constexpr std::size_t WVORBIS_LENGTH_TABLE_SIZE = 10;
constexpr std::size_t VORBIS_IDENTIFICATION_SIZE = 30;
constexpr std::size_t VORBIS_COMMENT_SIZE = 16;
constexpr std::size_t WVORBIS_SETUP_MIN_SIZE = 9;
constexpr std::size_t WVORBIS_SETUP_PART1_LENGTH_OFFSET = 7;
constexpr std::int32_t VORBIS_BLOCK_SIZE_MIN = 64;
constexpr std::int32_t VORBIS_BLOCK_SIZE_MAX = 8192;
constexpr std::uint8_t VORBIS_MAGIC[] {'v', 'o', 'r', 'b', 'i', 's'};
std::int32_t WVorbisBlockSize(std::int32_t log2) {
return static_cast<std::int32_t>(1u << (static_cast<std::uint32_t>(log2) & 31u));
}
std::uint8_t* PutLittle(std::uint8_t* out, std::uint32_t value, std::size_t bytes) {
for (std::size_t i = 0; i < bytes; ++i) out[i] = static_cast<std::uint8_t>(value >> (8 * i));
return out + bytes;
}
std::uint8_t* PutVorbisHeaderStart(std::uint8_t* out, std::uint8_t type) {
*out++ = type;
std::memcpy(out, VORBIS_MAGIC, sizeof(VORBIS_MAGIC));
return out + sizeof(VORBIS_MAGIC);
}
}
extern "C" {
int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const void* pcm_input, size_t pcm_input_size, void* bitstream_output, size_t bitstream_output_size, void* result) {
@@ -63,8 +96,45 @@ const char* APS5_VABI sceAjmStrError(int error) {
return nullptr;
}
int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* parameter) {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* param) {
if (param == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER;
if (param->size != sizeof(WVorbisHeaderPacketParameter)) return AJM_WVORBIS_ERROR_INVALID_SIZE;
if (param->bytesPerSecond < 0) return AJM_WVORBIS_ERROR_INVALID_PARAMETER;
if (WVorbisBlockSize(param->shortBlockExponent) < VORBIS_BLOCK_SIZE_MIN) return AJM_WVORBIS_ERROR_INVALID_PARAMETER;
if (param->longBlockExponent < param->shortBlockExponent || WVorbisBlockSize(param->longBlockExponent) > VORBIS_BLOCK_SIZE_MAX)
return AJM_WVORBIS_ERROR_INVALID_PARAMETER;
if (param->setupPacket == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER;
if (param->setupPacketSize < WVORBIS_SETUP_MIN_SIZE) return AJM_WVORBIS_ERROR_INVALID_SETUP;
if (param->output == nullptr) return AJM_WVORBIS_ERROR_INVALID_PARAMETER;
constexpr std::size_t headers = WVORBIS_LENGTH_TABLE_SIZE + VORBIS_IDENTIFICATION_SIZE + VORBIS_COMMENT_SIZE;
if (param->outputCapacity < headers || param->setupPacketSize > param->outputCapacity - headers) return AJM_WVORBIS_ERROR_OUTPUT_TOO_SMALL;
const auto* setup = static_cast<const std::uint8_t*>(param->setupPacket);
const auto part1 = static_cast<std::uint16_t>(setup[WVORBIS_SETUP_PART1_LENGTH_OFFSET] | setup[WVORBIS_SETUP_PART1_LENGTH_OFFSET + 1] << 8);
auto* out = static_cast<std::uint8_t*>(param->output);
out = PutLittle(out, 0, 2);
out = PutLittle(out, VORBIS_IDENTIFICATION_SIZE, 2);
out = PutLittle(out, VORBIS_COMMENT_SIZE, 2);
out = PutLittle(out, part1, 2);
out = PutLittle(out, static_cast<std::uint16_t>(param->setupPacketSize) - part1, 2);
out = PutVorbisHeaderStart(out, 1);
out = PutLittle(out, 0, 4);
out = PutLittle(out, static_cast<std::uint32_t>(param->channels), 1);
out = PutLittle(out, param->sampleRate, 4);
out = PutLittle(out, 0, 4);
out = PutLittle(out, static_cast<std::uint32_t>(param->bytesPerSecond) * 8u, 4);
out = PutLittle(out, 0, 4);
*out++ = static_cast<std::uint8_t>((param->shortBlockExponent & 0xF) | (param->longBlockExponent & 0xF) << 4);
*out++ = 1;
out = PutVorbisHeaderStart(out, 3);
out = PutLittle(out, 0, 4);
out = PutLittle(out, 0, 4);
*out++ = 1;
std::memcpy(out, setup, param->setupPacketSize);
param->outputSize = headers + param->setupPacketSize;
return 0;
}
+42 -28
View File
@@ -851,14 +851,15 @@ std::uint32_t ReadBigEndian(const std::uint8_t* data, std::size_t bytes) {
int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame);
void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame) {
void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame, int parseOfl) {
const std::size_t end = std::min<std::size_t>(streamSize, frame->frame_size);
if (end <= 4) return;
const std::size_t available = end - 4;
const std::size_t sideInfoStart = 4 + (((header >> 16u) & 1u) == 0 ? 2u : 0u);
if (end <= sideInfoStart) return;
const std::size_t available = end - sideInfoStart;
std::size_t bit = 0;
const auto readBits = [&](std::uint32_t count) {
std::uint32_t value = 0;
for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[4 + bit / 8] >> (7 - bit % 8)) & 1u : 0u);
for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[sideInfoStart + bit / 8] >> (7 - bit % 8)) & 1u : 0u);
return value;
};
const bool mpeg1 = ((header >> 19u) & 3u) == 3u;
@@ -872,11 +873,20 @@ void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint
}
}
bit += mainDataBits;
std::size_t at = 4 + std::min<std::size_t>((bit + 7) / 8, available);
std::size_t at = sideInfoStart + std::min<std::size_t>((bit + 7) / 8, available);
if (at + 8 <= end && (std::memcmp(stream + at, "Xing", 4) == 0 || std::memcmp(stream + at, "Info", 4) == 0)) {
const std::uint8_t flags = stream[at + 7];
std::size_t field = at + 8;
std::size_t tagAt = at;
if (tagAt + 8 > end || (std::memcmp(stream + tagAt, "Xing", 4) != 0 && std::memcmp(stream + tagAt, "Info", 4) != 0)) {
const std::size_t fixedOffset = mpeg1 ? (mono ? 21u : 36u) : (mono ? 13u : 21u);
tagAt = 0;
if (fixedOffset + 8 <= end && (std::memcmp(stream + fixedOffset, "Xing", 4) == 0 || std::memcmp(stream + fixedOffset, "Info", 4) == 0)) {
tagAt = fixedOffset;
}
}
if (tagAt != 0) {
const std::uint8_t flags = stream[tagAt + 7];
std::size_t field = tagAt + 8;
std::uint32_t frames = 0;
if (flags & 0x01u) {
if (field + 4 > end) return;
@@ -903,30 +913,34 @@ void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint
frame->encoder_delay = frame->samples_per_channel + delay + 529;
frame->ofl_type = MP3_OFL_LAME;
}
} else if (at != 36 && end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0) {
NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (VBRI header outside MPEG-1 stereo)");
} else if (at + 26 <= end && std::memcmp(stream + at, "VBRI", 4) == 0) {
frame->encoder_delay = ReadBigEndian(stream + at + 6, 2);
return;
}
const std::size_t vbriAt = end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0 ? 36 : at;
if (vbriAt + 26 <= end && std::memcmp(stream + vbriAt, "VBRI", 4) == 0) {
frame->encoder_delay = ReadBigEndian(stream + vbriAt + 6, 2);
frame->ofl_type = MP3_OFL_VBRI;
if (frame->frame_size <= streamSize) {
if (parseOfl == 1 && frame->frame_size <= streamSize) {
AjmDecMp3ParseFrame next{};
if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast<std::uint32_t>(frame->frame_size), 1, &next) == 0 && next.ofl_type == MP3_OFL_FGH) {
if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast<std::uint32_t>(frame->frame_size), 2, &next) == 0 && next.ofl_type == MP3_OFL_FGH) {
frame->encoder_delay += next.encoder_delay;
frame->total_samples = next.total_samples;
frame->ofl_type = MP3_OFL_VBRI_AND_FGH;
}
}
} else if (at + 10 <= end) {
while (at + 9 < end && stream[at] != 0xB4u) ++at;
if (at + 9 >= end) return;
std::uint8_t crc = 0xFF;
for (std::size_t i = 0; i < 9; ++i) {
for (int shift = 7; shift >= 0; --shift) {
const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u);
crc = static_cast<std::uint8_t>(same ? (crc << 1u) ^ 0x45u : crc << 1u);
}
return;
}
while (at + 9 < end && stream[at] != 0xB4u) ++at;
if (at + 9 >= end) return;
std::uint8_t crc = 0xFF;
for (std::size_t i = 0; i < 9; ++i) {
for (int shift = 7; shift >= 0; --shift) {
const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u);
crc = static_cast<std::uint8_t>(same ? (crc << 1u) ^ 0x45u : crc << 1u);
}
if (stream[at + 9] != crc) return;
}
if (stream[at + 9] == crc) {
frame->encoder_delay = ReadBigEndian(stream + at + 1, 2);
frame->total_samples = ReadBigEndian(stream + at + 3, 4);
frame->ofl_type = MP3_OFL_FGH;
@@ -944,13 +958,13 @@ int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int par
};
const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3];
const std::uint32_t version = (header >> 19u) & 3u;
const std::uint32_t layer = (header >> 17u) & 3u;
const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u];
const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u;
const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0;
const bool valid = (header >> 21u) == 0x7FFu && layer != 0 && sampleRate != 0 && bitrate != 0;
AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate);
if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER;
if (parseOfl && ((header >> 17u) & 3u) != 1u) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup outside layer III)");
if (parseOfl && ((header >> 16u) & 1u) == 0) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup in a CRC-protected frame)");
if (parseOfl && layer != 1u) return SCE_AJM_ERROR_INVALID_PARAMETER;
const bool mpeg1 = version == 3;
frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u);
frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u;
@@ -961,7 +975,7 @@ int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int par
frame->num_frames = 0;
frame->total_samples = 0;
frame->ofl_type = MP3_OFL_NONE;
if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame);
if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame, parseOfl);
return 0;
}
-15
View File
@@ -845,11 +845,6 @@ int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t id) {
return AmmSubmitted_nid_no_patch(id) ? 0 : SCE_KERNEL_ERROR_ESRCH;
}
int APS5_VABI _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
void APS5_VABI _Unlock_shared_ptr_spin_lock_nid_postfix(void) {
sharedPtrSpinLock.clear(std::memory_order_release);
}
@@ -858,21 +853,11 @@ int APS5_VABI _Cnd_init_with_name_nid_postfix(void** handle, const char*) {
return _Cnd_init_nid_postfix(handle);
}
int APS5_VABI _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNKSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateEP2tmPKcSE__nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
+2 -3
View File
@@ -313,9 +313,8 @@ int APS5_VABI sceAppContentRequestPatchInstall(void) {
return 0;
}
int APS5_VABI sceAppContentTemporaryDataMount(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAppContentTemporaryDataMount(AppContentMountPoint* mountPoint) {
return sceAppContentTemporaryDataMount2(TEMPORARY_DATA_OPTION_FORMAT, mountPoint);
}
}
@@ -22,6 +22,15 @@ 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_speaker_array_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dSpeakerArray.cpp)
target_include_directories(guest_audio3d_speaker_array_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_audio3d_speaker_array_tests PRIVATE libSceAudio3d libSceAudioOut libc)
configure_windows_unwind(guest_audio3d_speaker_array_tests)
add_test(NAME guest_audio3d_speaker_array COMMAND guest_audio3d_speaker_array_tests)
if(WIN32)
set_tests_properties(guest_audio3d_speaker_array 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})
+28 -15
View File
@@ -472,29 +472,42 @@ int APS5_VABI sceAudio3dReportUnregisterHandler(void) {
return 0;
}
int APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
size_t APS5_VABI sceAudioOut2GetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d, uint8_t is_ambisonics);
int APS5_VABI sceAudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, const void* vbap_params, const void* ambi_params);
int APS5_VABI sceAudioOut2SpeakerArrayDestroy(AudioOut2SpeakerArrayHandle handle);
int APS5_VABI sceAudioOut2GetSpeakerArrayCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients, uint8_t height_aware, float downmix_spread_radius);
struct Audio3dSpeakerArrayParameters {
const AudioOut2Position* positions;
std::uint32_t numSpeakers;
std::uint8_t is3d;
void* memory;
std::size_t memorySize;
};
static_assert(sizeof(Audio3dSpeakerArrayParameters) == 0x20);
size_t APS5_VABI sceAudio3dGetSpeakerArrayMemorySize(uint32_t num_speakers, uint8_t is_3d) {
return sceAudioOut2GetSpeakerArrayMemorySize(num_speakers, is_3d, 0);
}
int APS5_VABI sceAudio3dCreateSpeakerArray(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAudio3dCreateSpeakerArray(AudioOut2SpeakerArrayHandle* handle, const Audio3dSpeakerArrayParameters* parameters) {
if (!handle || !parameters) return AUDIO3D_ERROR_INVALID_PARAMETER;
unsigned char extended[0x28] = {};
std::memcpy(extended, parameters, sizeof(*parameters));
return sceAudioOut2SpeakerArrayCreate(handle, extended, nullptr);
}
int APS5_VABI sceAudio3dDeleteSpeakerArray(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAudio3dDeleteSpeakerArray(AudioOut2SpeakerArrayHandle handle) {
return sceAudioOut2SpeakerArrayDestroy(handle);
}
int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients) {
return sceAudioOut2GetSpeakerArrayCoefficients(handle, pos, spread, coefficients, num_coefficients, 0, 0.1f);
}
int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAudio3dGetSpeakerArrayMixCoefficients2(AudioOut2SpeakerArrayHandle handle, AudioOut2Position pos, float spread, float* coefficients, uint32_t num_coefficients, uint8_t height_aware, float downmix_spread_radius) {
return sceAudioOut2GetSpeakerArrayCoefficients(handle, pos, spread, coefficients, num_coefficients, height_aware, downmix_spread_radius);
}
}
+9 -11
View File
@@ -191,9 +191,9 @@ int32_t APS5_VABI sceAudioInAsyncOpen(int32_t userId, uint32_t type, uint32_t in
}
int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) {
(void)handle;
NotImplemented_nid_no_patch(__func__);
return 0;
std::lock_guard lock(g_mutex);
if (!find(handle)) return AUDIO_IN_ERROR_INVALID_HANDLE;
return 0;
}
int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) {
@@ -258,14 +258,14 @@ int APS5_VABI sceAudioInGetHandleStatusInfo(void) {
return 0;
}
int APS5_VABI sceAudioInOpenEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAudioInOpenEx(int user_id, uint32_t type, uint32_t index, uint32_t extra, uint32_t len, uint32_t freq, uint32_t param) {
(void)extra;
return sceAudioInOpen(user_id, type, index, len, freq, param);
}
int APS5_VABI sceAudioInHqOpenEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAudioInHqOpenEx(int user_id, uint32_t type, uint32_t index, uint32_t extra, uint32_t len, uint32_t freq, uint32_t param) {
(void)extra;
return sceAudioInOpen(user_id, type, index, len, freq, param);
}
int APS5_VABI sceAudioInDeviceOpenEx(void) {
@@ -289,12 +289,10 @@ int APS5_VABI sceAudioInSetSparkSideTone(void) {
}
int APS5_VABI sceAudioInSetCompressorPreGain(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioInSetNoiseGateThreshold(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
@@ -47,6 +47,21 @@ if(BUILD_TESTING)
endif()
endforeach()
add_test_executable(audio_out_buffer_size_tests tests/AudioOutBufferSize.cpp src/AudioOut.cpp src/AudioOut2PadMix.cpp src/AudioOutShutdown.cpp)
target_compile_definitions(audio_out_buffer_size_tests PRIVATE SDL_MAIN_HANDLED)
target_include_directories(audio_out_buffer_size_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(audio_out_buffer_size_tests PRIVATE libc libkernel libScePad)
configure_windows_unwind(audio_out_buffer_size_tests)
foreach(bufferSizeMode IN ITEMS pass_through wrap mix threshold)
set(bufferSizeTest "audio_out_buffer_size_${bufferSizeMode}")
add_test(NAME ${bufferSizeTest} COMMAND audio_out_buffer_size_tests ${bufferSizeMode})
set_tests_properties(${bufferSizeTest} PROPERTIES TIMEOUT 5)
if(WIN32)
set_tests_properties(${bufferSizeTest} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
add_test_executable(audio_out_vibration_tests tests/AudioOutVibration.cpp)
target_include_directories(audio_out_vibration_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(audio_out_vibration_tests PRIVATE libSceAudioOut libScePad libc)
@@ -22,6 +22,10 @@ int APS5_VABI sceAudioOut2LoInit(void) {
return sceAudioOut2Initialize();
}
int APS5_VABI sceAudioOut2LoTerminate(void) {
return 0;
}
int APS5_VABI sceAudioOut2LoContextQueryMemory(const AudioOut2ContextParam* params, size_t* memory_size) {
return sceAudioOut2ContextQueryMemory(params, memory_size);
}
@@ -22,6 +22,11 @@ int APS5_VABI sceAudioOut2MasteringInit(uint32_t flags) {
return 0;
}
int APS5_VABI sceAudioOut2MasteringInitEx(uint32_t flags) {
if ((flags & 0x7FFFFFFFu) != 0) NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2MasteringTerm(void) {
return 0;
}
@@ -68,7 +68,7 @@ extern "C" {
int APS5_VABI sceAudioOut2SpeakerArrayCreate(AudioOut2SpeakerArrayHandle* handle, const void* vbap_params, const void* ambi_params) {
const auto* params = static_cast<const GuestVbapParams*>(vbap_params);
if (handle == nullptr || params == nullptr || ambi_params == nullptr || params->positions == nullptr ||
if (handle == nullptr || params == nullptr || params->positions == nullptr ||
params->numSpeakers == 0 || params->numSpeakers > MaxSpeakers || params->memory == nullptr ||
params->memorySize < sizeof(SpeakerArray) || reinterpret_cast<std::uintptr_t>(params->memory) % alignof(SpeakerArray) != 0) {
APS5_INVALID_ARG_EX;
@@ -15,6 +15,11 @@ int APS5_VABI sceAudioOut2Initialize(void) {
return 0;
}
int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t param) {
(void)param;
return sceAudioOut2Initialize();
}
int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t output) {
if (output > MAX_SUPPORTED_3D_OUTPUT) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
if (userId != 0 && (userId != 0xFF || output == 0)) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
@@ -365,11 +365,6 @@ int APS5_VABI sceAudioOut2GetSystemStateEx(void) {
return 0;
}
int APS5_VABI sceAudioOut2MasteringInitEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2SetDebugHeadphoneMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -380,11 +375,6 @@ int APS5_VABI sceAudioOut2DebugStateCtrl(void) {
return 0;
}
int APS5_VABI sceAudioOut2LoTerminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2ContextBedWrite(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -395,11 +385,6 @@ int APS5_VABI sceAudioOut2PortCreateEx(void) {
return 0;
}
int APS5_VABI sceAudioOut2InitializeEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2Terminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -9,10 +9,12 @@
extern "C" {
int APS5_VABI sceAudioOut2Initialize();
int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t);
int APS5_VABI sceAudioOut2LoInit();
int APS5_VABI sceAudioOut2LoTerminate();
int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t);
int APS5_VABI sceAudioOut2MasteringInit(std::uint32_t);
int APS5_VABI sceAudioOut2MasteringInitEx(std::uint32_t);
int APS5_VABI sceAudioOut2MasteringTerm();
int APS5_VABI sceAudioOut2MasteringSetParam(const void*, std::uint32_t, std::uint32_t);
int APS5_VABI sceAudioOut2MasteringGetState(void*, std::uint32_t, std::uint64_t);
@@ -68,6 +70,10 @@ void TestSet3DLatency() {
void TestMasteringInit() {
Require(sceAudioOut2MasteringInit(0) == 0, "flags 0 must be accepted");
Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInit(1); }), "flags 1 must throw");
Require(sceAudioOut2MasteringInitEx(0) == 0, "InitEx flags 0 must be accepted");
Require(sceAudioOut2MasteringInitEx(0x80000000u) == 0, "InitEx flag bit 31 must be accepted");
Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInitEx(1); }), "InitEx flag bit 0 must throw");
Require(ThrowsRuntimeError([] { sceAudioOut2MasteringInitEx(0x40000000u); }), "InitEx flag bit 30 must throw");
}
void TestMasteringTerm() {
@@ -125,7 +131,9 @@ void TestEnableChat() {
void TestLowLatencyService() {
Require(sceAudioOut2LoInit() == 0, "starting the low-latency service must be accepted");
Require(sceAudioOut2LoInit() == 0, "starting the low-latency service again must be accepted");
Require(ThrowsRuntimeError([] { sceAudioOut2LoTerminate(); }), "stopping the low-latency service must throw");
Require(sceAudioOut2LoTerminate() == 0, "stopping the low-latency service returns 0");
Require(sceAudioOut2LoTerminate() == 0, "stopping a stopped low-latency service returns 0");
Require(sceAudioOut2InitializeEx(0) == 0 && sceAudioOut2InitializeEx(1) == 0, "the extended initialization succeeds");
}
}
@@ -0,0 +1,100 @@
#include "SDL.h"
#include "prx/libSceAudioOut/include/AudioOutSdl.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <string_view>
#include <vector>
extern "C" {
int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t);
int APS5_VABI sceAudioOutClose(int);
int APS5_VABI sceAudioOutOutput(int, const void*);
}
namespace {
constexpr SDL_AudioDeviceID device = 7;
int opened = 0;
int closed = 0;
int queued = 0;
std::uint32_t queuedBytes = 0;
const void* supplied = nullptr;
std::uint32_t suppliedBytes = 0;
void Require(bool value, const char* message) {
if (!value) throw std::runtime_error(message);
}
void RunPassThrough() {
std::vector<float> samples(512u * 8u);
for (std::size_t i = 0; i < samples.size(); ++i) samples[i] = static_cast<float>(i % 97u) / 97.0f;
const int handle = sceAudioOutOpen(0, 0, 0, 512, 48000, 5);
Require(handle > 0 && opened == 1, "a 512-frame F32 8-channel port did not open its device");
supplied = samples.data();
suppliedBytes = static_cast<std::uint32_t>(samples.size() * sizeof(float));
Require(sceAudioOutOutput(handle, samples.data()) == 512, "a 512-frame output did not return 512");
Require(queued == 1 && queuedBytes == 16384, "a 512-frame F32 8-channel output did not queue 16384 bytes");
Require(sceAudioOutClose(handle) == 0 && closed == 1, "the port did not close its device");
}
void RunOverflow(std::uint32_t frames, std::uint32_t format, std::uint32_t frameBytes) {
std::vector<std::uint8_t> frame(frameBytes);
const int handle = sceAudioOutOpen(0, 0, 0, frames, 48000, format);
Require(handle > 0 && opened == 1, "a port with an oversized length did not open its device");
supplied = frame.data();
suppliedBytes = frameBytes;
bool threw = false;
try {
static_cast<void>(sceAudioOutOutput(handle, frame.data()));
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw, "an output whose buffer size overflows did not throw");
Require(queued == 0, "an output whose buffer size overflows queued audio");
Require(sceAudioOutClose(handle) == 0 && closed == 1, "the port did not close after the refused output");
}
}
int InitializeSdlAudio_nid_no_patch() { return 0; }
extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; }
extern "C" int SDLCALL SDL_GetNumAudioDevices(int) { return 1; }
extern "C" const char* SDLCALL SDL_GetAudioDeviceName(int, int) { return "Main speakers"; }
extern "C" int SDLCALL SDL_GetAudioDeviceSpec(int, int, SDL_AudioSpec* spec) { *spec = {}; spec->channels = 2; return 0; }
extern "C" const char* SDLCALL SDL_GetCurrentAudioDriver() { return "dummy"; }
extern "C" const char* SDLCALL SDL_GetError() { return "test error"; }
extern "C" SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char* name, int capture, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int) {
Require(name == nullptr && capture == 0 && desired->callback == nullptr, "the main port did not open the default device");
++opened;
*obtained = *desired;
return device;
}
extern "C" void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID id, int) { Require(id == device, "wrong device paused"); }
extern "C" Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID id) { Require(id == device, "wrong queue queried"); return 0; }
extern "C" int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID id, const void* data, Uint32 bytes) {
Require(id == device, "wrong queue fed");
++queued;
queuedBytes = bytes;
if (bytes <= suppliedBytes) Require(std::memcmp(data, supplied, bytes) == 0, "the queued audio is not the supplied samples");
return 0;
}
extern "C" void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID id) { Require(id == device, "wrong queue cleared"); }
extern "C" void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID id) { Require(id == device, "wrong device closed"); ++closed; }
int main(int argc, char** argv) {
try {
Require(argc == 2, "invalid test arguments");
const std::string_view mode = argv[1];
if (mode == "pass_through") RunPassThrough();
else if (mode == "wrap") RunOverflow(0x80000000u, 5, 8u * sizeof(float));
else if (mode == "mix") RunOverflow(0x80000000u, 7, 8u * sizeof(float));
else if (mode == "threshold") RunOverflow(0x40000000u, 0, sizeof(std::int16_t));
else Require(false, "invalid buffer size mode");
std::puts("audio out buffer size tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}
@@ -5,7 +5,7 @@ configure_windows_unwind(libSceAudioPropagation)
if(BUILD_TESTING)
add_test_executable(guest_audio_propagation_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudioPropagation.cpp)
target_include_directories(guest_audio_propagation_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_audio_propagation_tests PRIVATE libSceAudioPropagation libc)
target_link_libraries(guest_audio_propagation_tests PRIVATE libSceAudioPropagation libc ${CMAKE_DL_LIBS})
configure_windows_unwind(guest_audio_propagation_tests)
add_test(NAME guest_audio_propagation COMMAND guest_audio_propagation_tests)
if(WIN32)
@@ -380,56 +380,6 @@ int APS5_VABI sceAudioPropagationSystemQueryInfo(AudioPropagationHandle system,
return 0;
}
int APS5_VABI sceAudioPropagationAudioPathInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationAudioPathPointInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationMaterialInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationPortalSettingsInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationRayInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSourceRenderInfoInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSourceSetAudioPathsParamInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSystem() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSystemMemoryInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSystemOptionInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationReportRegisterHandler(std::uint32_t category, ReportHandler handler, void* userData, ReportRegistration** registration) {
NotImplemented_nid_no_patch(__func__);
return 0;
+4 -4
View File
@@ -21,10 +21,10 @@ constexpr int CES_ERROR_UNASSIGNED_CODE = static_cast<int>(0x805C0020);
constexpr int CES_ERROR_INVALID_DST_BUFFER = static_cast<int>(0x805C0030);
constexpr int CES_ERROR_DST_BUFFER_END = static_cast<int>(0x805C0031);
constexpr uint8_t PROFILES[3] = {0, 1, 2};
constexpr const uint8_t* CP1252_PROFILE = &PROFILES[0];
constexpr const uint8_t* CP1251_PROFILE = &PROFILES[1];
constexpr const uint8_t* CP1250_PROFILE = &PROFILES[2];
constexpr uint8_t PROFILES[3][8] = {{0}, {1}, {2}};
constexpr const uint8_t* CP1252_PROFILE = PROFILES[0];
constexpr const uint8_t* CP1251_PROFILE = PROFILES[1];
constexpr const uint8_t* CP1250_PROFILE = PROFILES[2];
constexpr uint16_t CP1252_HIGH[32] = {
0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
@@ -248,6 +248,7 @@ inline bool ValidateCommonDialogBaseParam(const CommonDialogBaseParam& param) {
}
constexpr int COMMON_DIALOG_OK = 0;
constexpr int COMMON_DIALOG_ERROR_ALREADY_SYSTEM_INITIALIZED = static_cast<int>(0x80B80002);
constexpr int COMMON_DIALOG_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x80B80004);
#endif
+1 -1
View File
@@ -12,7 +12,7 @@ extern "C" {
int APS5_VABI sceCommonDialogInitialize(void) {
bool expected = false;
if (!g_initialized.compare_exchange_strong(expected, true)) {
return COMMON_DIALOG_ERROR_ALREADY_INITIALIZED;
return COMMON_DIALOG_ERROR_ALREADY_SYSTEM_INITIALIZED;
}
return COMMON_DIALOG_OK;
}
+5 -1
View File
@@ -154,8 +154,12 @@ std::vector<Font::SystemFontFile> Font::SystemFontCandidates(std::uint32_t fontS
}
std::filesystem::path Font::SystemFontDirectory() {
#ifdef _WIN32
const wchar_t* configured = _wgetenv(L"ANYPS5_SYSTEM_FONTS");
#else
const char* configured = std::getenv("ANYPS5_SYSTEM_FONTS");
if (!configured || configured[0] == '\0') return ExecutableDirectory() / "anyps5-fonts";
#endif
if (!configured || configured[0] == 0) return ExecutableDirectory() / "anyps5-fonts";
const std::filesystem::path directory(configured);
std::error_code error;
if (!std::filesystem::is_directory(directory, error)) throw std::runtime_error("libSceFont: ANYPS5_SYSTEM_FONTS is not a directory: " + directory.string());
+1 -2
View File
@@ -78,8 +78,7 @@ int APS5_VABI sceFontFtSupportTrueType(FontLibrary library) {
}
int APS5_VABI sceFontFtSupportTrueTypeGx(FontLibrary library) {
NotImplemented_nid_no_patch(__func__);
return 0;
return FontFt::SupportFormats(library, {"truetype"});
}
int APS5_VABI sceFontFtSupportType1(FontLibrary library) {
+40 -1
View File
@@ -12,7 +12,11 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#ifdef _WIN32
#include <windows.h>
#endif
#include <ft2build.h>
#include FT_FREETYPE_H
@@ -198,6 +202,41 @@ int APS5_VABI LibrarySupport(FontLibNative* library, std::uint32_t formats) {
return SCE_FONT_OK;
}
#ifdef _WIN32
void CloseMappedFile(FT_Stream stream) {
UnmapViewOfFile(stream->base);
delete stream;
}
FT_Error OpenFileFace(FT_Library library, const char* path, FT_Long subFontIndex, FT_Face* face) {
const HANDLE file = CreateFileW(std::filesystem::path(reinterpret_cast<const char8_t*>(path)).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) return FT_Err_Cannot_Open_Resource;
LARGE_INTEGER size{};
if (!GetFileSizeEx(file, &size) || size.QuadPart <= 0 || size.QuadPart > std::numeric_limits<long>::max()) {
CloseHandle(file);
return FT_Err_Cannot_Open_Stream;
}
const HANDLE mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
CloseHandle(file);
if (!mapping) return FT_Err_Cannot_Open_Stream;
void* view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(mapping);
if (!view) return FT_Err_Cannot_Open_Stream;
auto* stream = new FT_StreamRec{};
stream->base = static_cast<unsigned char*>(view);
stream->size = static_cast<unsigned long>(size.QuadPart);
stream->close = &CloseMappedFile;
FT_Open_Args args{};
args.flags = FT_OPEN_STREAM;
args.stream = stream;
return FT_Open_Face(library, &args, subFontIndex, face);
}
#else
FT_Error OpenFileFace(FT_Library library, const char* path, FT_Long subFontIndex, FT_Face* face) {
return FT_New_Face(library, path, subFontIndex, face);
}
#endif
int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const void* fontAddress, std::uint32_t fontSize, std::uint32_t subFontIndex, std::uint32_t uniqueWord, FontObj** inoutFontObj) {
(void)uniqueWord;
if (!library || !fontAddress || !inoutFontObj) return SCE_FONT_ERROR_INVALID_PARAMETER;
@@ -222,7 +261,7 @@ int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const
if (fromMemory) {
error = FT_New_Memory_Face(*ftLibrary, static_cast<const FT_Byte*>(fontAddress), static_cast<FT_Long>(fontSize), static_cast<FT_Long>(subFontIndex), &face);
} else {
error = FT_New_Face(*ftLibrary, static_cast<const char*>(fontAddress), static_cast<FT_Long>(subFontIndex), &face);
error = OpenFileFace(*ftLibrary, static_cast<const char*>(fontAddress), static_cast<FT_Long>(subFontIndex), &face);
}
if (error != 0 || !face) {
if (fromMemory || error == FT_Err_Unknown_File_Format) return SCE_FONT_ERROR_NO_SUPPORT_FORMAT;
@@ -1,6 +1,7 @@
add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL
Export.cpp
src/GuestObjects.cpp
src/Names.cpp
)
target_include_directories(libSceFreeType PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(libSceFreeType PRIVATE freetype)
@@ -20,7 +21,21 @@ if(BUILD_TESTING)
target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc)
configure_windows_unwind(guest_freetype_charmap_tests)
add_test(NAME guest_freetype_charmap COMMAND guest_freetype_charmap_tests)
add_test_executable(guest_freetype_names_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeNames.cpp)
target_include_directories(guest_freetype_names_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_freetype_names_tests PRIVATE libSceFreeType freetype libc)
configure_windows_unwind(guest_freetype_names_tests)
add_test(NAME guest_freetype_names COMMAND guest_freetype_names_tests)
add_test_executable(guest_freetype_sfnt_names_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeSfntNames.cpp)
target_include_directories(guest_freetype_sfnt_names_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_freetype_sfnt_names_tests PRIVATE libSceFreeType freetype libc)
configure_windows_unwind(guest_freetype_sfnt_names_tests)
add_test(NAME guest_freetype_sfnt_names COMMAND guest_freetype_sfnt_names_tests)
if(WIN32)
set_tests_properties(guest_freetype_sfnt_names PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
set_tests_properties(guest_freetype_names PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
set_tests_properties(guest_freetype PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
set_tests_properties(guest_freetype_charmap PROPERTIES ENVIRONMENT_MODIFICATION
+22
View File
@@ -149,6 +149,28 @@ int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec* face, std::uint32_t p
return error;
}
int APS5_VABI FT_Set_Char_Size_nid_postfix(GuestFaceRec* face, std::int64_t charWidth, std::int64_t charHeight,
std::uint32_t horizontalResolution, std::uint32_t verticalResolution) {
GuestFace* owner = FaceOf(face);
if (!owner) return FT_Err_Invalid_Face_Handle;
if (!owner->host->size) return FT_Err_Invalid_Size_Handle;
if (!charWidth) charWidth = charHeight;
else if (!charHeight) charHeight = charWidth;
if (charWidth < 64) charWidth = 64;
if (charHeight < 64) charHeight = 64;
if (!horizontalResolution) horizontalResolution = verticalResolution;
else if (!verticalResolution) verticalResolution = horizontalResolution;
if (!horizontalResolution) horizontalResolution = verticalResolution = 72;
const auto maximum = std::numeric_limits<FT_Long>::max();
if (charWidth > (maximum - 36) / horizontalResolution || charHeight > (maximum - 36) / verticalResolution) {
throw std::runtime_error("FT_Set_Char_Size: a character size or resolution outside the host arithmetic range is not implemented");
}
const FT_Error error = FT_Set_Char_Size(owner->host, static_cast<FT_F26Dot6>(charWidth), static_cast<FT_F26Dot6>(charHeight),
horizontalResolution, verticalResolution);
SyncSize(*owner);
return error;
}
std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec* face, std::uint64_t charcode) {
const GuestFace* owner = FaceOf(face);
return FT_Get_Char_Index(owner ? owner->host : nullptr, static_cast<FT_ULong>(static_cast<std::uint32_t>(charcode)));
@@ -40,6 +40,15 @@ struct GuestCharMapRec {
std::uint16_t encoding_id;
};
struct GuestSfntName {
std::uint16_t platform_id;
std::uint16_t encoding_id;
std::uint16_t language_id;
std::uint16_t name_id;
unsigned char* string;
std::uint32_t string_len;
};
struct GuestSizeMetrics {
std::uint16_t x_ppem;
std::uint16_t y_ppem;
@@ -152,6 +161,7 @@ struct GuestFaceRec {
static_assert(sizeof(GuestGeneric) == 16 && offsetof(GuestGeneric, finalizer) == 8);
static_assert(sizeof(GuestBitmapSize) == 32 && offsetof(GuestBitmapSize, size) == 8);
static_assert(sizeof(GuestCharMapRec) == 16 && offsetof(GuestCharMapRec, platform_id) == 12);
static_assert(sizeof(GuestSfntName) == 24 && offsetof(GuestSfntName, string) == 8 && offsetof(GuestSfntName, string_len) == 16);
static_assert(sizeof(GuestSizeMetrics) == 56 && offsetof(GuestSizeMetrics, x_scale) == 8);
static_assert(sizeof(GuestSizeRec) == 88 && offsetof(GuestSizeRec, metrics) == 24);
static_assert(sizeof(GuestBitmap) == 40 && offsetof(GuestBitmap, buffer) == 16 && offsetof(GuestBitmap, palette) == 32);
@@ -0,0 +1,55 @@
#include "prx/libSceFreeType/include/GuestObjects.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include FT_SFNT_NAMES_H
#include <cstdint>
using namespace FreeType;
extern "C" {
int APS5_VABI FT_Get_Glyph_Name_nid_postfix(GuestFaceRec* face, std::uint32_t glyphIndex, void* buffer, std::uint32_t bufferSize) {
const GuestFace* owner = FaceOf(face);
const FT_Face host = owner ? owner->host : nullptr;
if (!host || !buffer || bufferSize == 0) return FT_Get_Glyph_Name(host, glyphIndex, buffer, bufferSize);
GuestArena::HostWrite write(buffer, bufferSize);
if (static_cast<std::int64_t>(glyphIndex) >= host->num_glyphs) {
static_cast<char*>(buffer)[0] = '\0';
return FT_Err_Invalid_Glyph_Index;
}
return FT_Get_Glyph_Name(host, glyphIndex, buffer, bufferSize);
}
std::uint32_t APS5_VABI FT_Get_Name_Index_nid_postfix(GuestFaceRec* face, const char* glyphName) {
const GuestFace* owner = FaceOf(face);
return FT_Get_Name_Index(owner ? owner->host : nullptr, glyphName);
}
const char* APS5_VABI FT_Get_Postscript_Name_nid_postfix(GuestFaceRec* face) {
const GuestFace* owner = FaceOf(face);
return FT_Get_Postscript_Name(owner ? owner->host : nullptr);
}
std::uint32_t APS5_VABI FT_Get_Sfnt_Name_Count_nid_postfix(GuestFaceRec* face) {
const GuestFace* owner = FaceOf(face);
return FT_Get_Sfnt_Name_Count(owner ? owner->host : nullptr);
}
int APS5_VABI FT_Get_Sfnt_Name_nid_postfix(GuestFaceRec* face, std::uint32_t index, GuestSfntName* name) {
const GuestFace* owner = FaceOf(face);
FT_SfntName native{};
const FT_Error error = FT_Get_Sfnt_Name(owner ? owner->host : nullptr, index, name ? &native : nullptr);
if (!error) {
name->platform_id = native.platform_id;
name->encoding_id = native.encoding_id;
name->language_id = native.language_id;
name->name_id = native.name_id;
name->string = native.string;
name->string_len = native.string_len;
}
return error;
}
}
@@ -5,12 +5,15 @@
#include "prx/libSceUserService/UserService.hpp"
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <string>
static std::mutex g_mutex;
static bool g_initialized = false;
static constexpr int ErrorInvalidArgument = static_cast<int>(0x80A00002);
static constexpr int ErrorAlreadyInitialized = static_cast<int>(0x80A00003);
static constexpr int ErrorNotInitialized = static_cast<int>(0x80A00004);
static constexpr std::size_t ProgramInfoSize = 0x134C;
struct GameLiveStreamingProgramInfo;
@@ -53,7 +56,10 @@ int APS5_VABI sceGameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* st
}
int APS5_VABI sceGameLiveStreamingGetProgramInfo(GameLiveStreamingProgramInfo* info) {
NotImplemented_nid_no_patch(__func__);
std::lock_guard lock(g_mutex);
if (!g_initialized) return ErrorNotInitialized;
if (info == nullptr) return ErrorInvalidArgument;
std::memset(info, 0, ProgramInfoSize);
return 0;
}
@@ -267,8 +273,10 @@ int APS5_VABI sceGameLiveStreamingStopDebugBroadcast(void) {
return 0;
}
int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(void) {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceGameLiveStreamingEnableLiveStreaming(bool isEnableLiveStreaming) {
std::lock_guard lock(g_mutex);
if (!g_initialized) throw std::logic_error(std::string(__func__) + ": not initialized");
(void)isEnableLiveStreaming;
return 0;
}
+12
View File
@@ -14,3 +14,15 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHmd2>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_hmd2_gaze_tests ${LIBS_INCLUDE_DIR}/tests/GuestHmd2Gaze.cpp)
target_include_directories(guest_hmd2_gaze_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_hmd2_gaze_tests PRIVATE libSceHmd2 libc)
configure_windows_unwind(guest_hmd2_gaze_tests)
add_test(NAME guest_hmd2_gaze COMMAND guest_hmd2_gaze_tests)
if(WIN32)
set_tests_properties(guest_hmd2_gaze PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHmd2>")
endif()
endif()
+31 -5
View File
@@ -291,14 +291,40 @@ std::int32_t APS5_VABI sceHmd2Close(std::int32_t handle) {
return 0;
}
int APS5_VABI sceHmd2GazeGetResult(const Hmd2::GazeResultParameter* parameter, Hmd2::GazeResult* result) {
NotImplemented_nid_no_patch(__func__);
static std::int32_t Hmd2GazeHandle(const Hmd2::GazeResultParameter* param, const void* result, std::uint32_t resultSize, const char* name) {
if (!param || !result) return Hmd2::ParameterNull;
std::uint32_t size;
std::memcpy(&size, result, sizeof(size));
if (param->size != sizeof(Hmd2::GazeResultParameter) || size != resultSize) return Hmd2::ParameterInvalid;
std::lock_guard lock(stateMutex);
if (openHandle == 0 || param->handle != openHandle) return Hmd2::InvalidHandle;
NotImplemented_nid_no_patch(name);
return 0;
}
int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const Hmd2::GazeResultParameter* parameter, Hmd2::FoveatedRenderingResult* result) {
NotImplemented_nid_no_patch(__func__);
return 0;
static void Hmd2StoreInvalidGaze(std::byte* result, std::size_t offset) {
const std::int32_t invalidIndex = -1;
const float invalidPair[2] = {-0.0f, 0.0f};
const float invalidScalar = -1.0f;
std::memcpy(result + 4, &invalidIndex, sizeof(invalidIndex));
std::memcpy(result + offset, invalidPair, sizeof(invalidPair));
std::memcpy(result + offset + sizeof(invalidPair), &invalidScalar, sizeof(invalidScalar));
}
int APS5_VABI sceHmd2GazeGetResult(const Hmd2::GazeResultParameter* param, Hmd2::GazeResult* result) {
const std::int32_t status = Hmd2GazeHandle(param, result, 0x38, __func__);
if (status != Hmd2::InvalidHandle) return status;
auto* bytes = reinterpret_cast<std::byte*>(result);
std::memset(bytes + 4, 0, 0x38 - 4);
Hmd2StoreInvalidGaze(bytes, 0x14);
return status;
}
int APS5_VABI sceHmd2GazeGetResultForFoveatedRendering(const Hmd2::GazeResultParameter* param, Hmd2::FoveatedRenderingResult* result) {
const std::int32_t status = Hmd2GazeHandle(param, result, 0x18, __func__);
if (status != Hmd2::InvalidHandle) return status;
Hmd2StoreInvalidGaze(reinterpret_cast<std::byte*>(result), 8);
return status;
}
std::int32_t APS5_VABI sceHmd2GetDeviceInformation(Hmd2::DeviceInformation* info) {
+10 -6
View File
@@ -317,9 +317,8 @@ int APS5_VABI sceHttpSendRequest(int request_id, const void* post_data, size_t s
}
int APS5_VABI sceHttpSetAuthEnabled(int id, int enable) {
(void)id;
(void)enable;
return 0;
if (!HttpInitialised()) return ERROR_BEFORE_INIT;
return SetHttpOption(id, &HttpOptions::authEnabled, enable != 0) ? 0 : ERROR_INVALID_ID;
}
int APS5_VABI sceHttpSetAuthInfoCallback(int id, HttpAuthInfoCallback callback, void* userArg) {
@@ -726,12 +725,17 @@ int APS5_VABI sceHttpAddRequestHeaderRaw(int id, const char* header, int mode) {
}
int APS5_VABI sceHttpRemoveRequestHeader(int id, const char* name) {
NotImplemented_nid_no_patch(__func__);
(void)id;
(void)name;
return 0;
}
int APS5_VABI sceHttpGetAuthEnabled(int id, int* enabled) {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceHttpGetAuthEnabled(int id, int* enable) {
if (!HttpInitialised()) return ERROR_BEFORE_INIT;
if (!enable) return ERROR_INVALID_VALUE;
const auto options = FindHttpOptions(id);
if (!options) return ERROR_INVALID_ID;
*enable = options->authEnabled ? 1 : 0;
return 0;
}
+1
View File
@@ -6,6 +6,7 @@
struct HttpOptions {
bool autoRedirect = true;
bool authEnabled = false;
bool cookieEnabled = false;
bool nonblock = false;
bool acceptEncodingGzip = false;
+51 -54
View File
@@ -10,6 +10,8 @@
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libSceHttp/src/HttpErrors.hpp"
extern "C" int APS5_VABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize);
namespace {
constexpr uint32_t URI_BUILD_WITH_SCHEME = 0x01;
@@ -29,6 +31,7 @@ struct UriParts {
std::string_view password;
std::string_view hostname;
std::string path;
size_t pathSpace = 0;
std::string_view query;
std::string_view fragment;
uint16_t port = 0;
@@ -55,8 +58,8 @@ bool equalsIgnoreCase(std::string_view left, std::string_view right) {
}
uint16_t defaultPort(std::string_view scheme) {
if (equalsIgnoreCase(scheme, "http")) return 80;
if (equalsIgnoreCase(scheme, "https")) return 443;
if (scheme.size() >= 5 && equalsIgnoreCase(scheme.substr(0, 5), "https")) return 443;
if (scheme.size() >= 4 && equalsIgnoreCase(scheme.substr(0, 4), "http")) return 80;
return 0;
}
@@ -64,31 +67,21 @@ bool isHostCharacter(char c) {
return isAsciiAlpha(c) || isAsciiDigit(c) || c == '-' || c == '.' || c == '_';
}
std::string removeDotSegments(std::string_view input) {
std::string output;
while (!input.empty()) {
if (input.starts_with("../")) input.remove_prefix(3);
else if (input.starts_with("./")) input.remove_prefix(2);
else if (input.starts_with("/./")) input.remove_prefix(2);
else if (input == "/.") input = "/";
else if (input.starts_with("/../") || input == "/..") {
input = input.size() == 3 ? std::string_view("/") : input.substr(3);
const size_t slash = output.rfind('/');
output.erase(slash == std::string::npos ? 0 : slash);
} else if (input == "." || input == "..") input = {};
else {
const size_t end = input.find('/', 1);
const size_t length = end == std::string_view::npos ? input.size() : end;
output.append(input.substr(0, length));
input.remove_prefix(length);
}
}
return output;
bool isUserinfoCharacter(char c) {
return isAsciiAlpha(c) || isAsciiDigit(c) || std::string_view("-._~!$&'()*+,;=%:").find(c) != std::string_view::npos;
}
std::string sweptPath(std::string_view path) {
const std::string source(path);
std::string swept(source.size() + 1, '\0');
sceHttpUriSweepPath(swept.data(), source.c_str(), source.size() + 1);
swept.resize(std::strlen(swept.c_str()));
return swept;
}
size_t schemeLength(std::string_view uri) {
size_t schemeEnd = 0;
if (!uri.empty() && isAsciiAlpha(uri[0])) {
if (!uri.empty() && (isAsciiAlpha(uri[0]) || uri[0] == '+' || uri[0] == '-' || uri[0] == '.')) {
schemeEnd = 1;
while (schemeEnd < uri.size() && (isAsciiAlpha(uri[schemeEnd]) || isAsciiDigit(uri[schemeEnd]) || uri[schemeEnd] == '+'
|| uri[schemeEnd] == '-' || uri[schemeEnd] == '.')) ++schemeEnd;
@@ -110,49 +103,51 @@ int parseUri(std::string_view uri, UriParts& parts) {
parts.opaque = !uri.starts_with("//");
if (!parts.opaque) uri.remove_prefix(2);
std::string_view authority = uri.substr(0, uri.find_first_of("/?#"));
uri.remove_prefix(authority.size());
const size_t at = authority.rfind('@');
if (at != std::string_view::npos) {
const std::string_view userinfo = authority.substr(0, at);
size_t userinfoEnd = 0;
while (userinfoEnd < uri.size() && isUserinfoCharacter(uri[userinfoEnd])) ++userinfoEnd;
if (userinfoEnd < uri.size() && uri[userinfoEnd] == '@') {
const std::string_view userinfo = uri.substr(0, userinfoEnd);
const size_t colon = userinfo.find(':');
parts.username = userinfo.substr(0, colon);
if (colon != std::string_view::npos) parts.password = userinfo.substr(colon + 1);
authority.remove_prefix(at + 1);
uri.remove_prefix(userinfoEnd + 1);
}
std::string_view port;
if (authority.starts_with('[')) {
const size_t close = authority.find(']');
if (uri.starts_with('[')) {
const size_t close = uri.find(']');
if (close == std::string_view::npos) return ERROR_INVALID_URL;
parts.hostname = authority.substr(1, close - 1);
authority.remove_prefix(close + 1);
if (!authority.empty() && authority[0] != ':') return ERROR_INVALID_URL;
if (!authority.empty()) port = authority.substr(1);
parts.hostname = uri.substr(1, close - 1);
for (const char c : parts.hostname) {
if (!isHostCharacter(c) && c != ':') return ERROR_INVALID_URL;
}
uri.remove_prefix(close + 1);
} else {
const size_t colon = authority.find(':');
parts.hostname = authority.substr(0, colon);
if (colon != std::string_view::npos) port = authority.substr(colon + 1);
for (const char c : parts.hostname) {
if (!isHostCharacter(c)) return ERROR_INVALID_URL;
size_t hostEnd = 0;
if (!uri.starts_with('.')) {
while (hostEnd < uri.size() && isHostCharacter(uri[hostEnd])) ++hostEnd;
}
parts.hostname = uri.substr(0, hostEnd);
uri.remove_prefix(hostEnd);
}
if (port.size() > 5) return ERROR_INVALID_URL;
uint32_t portValue = 0;
for (const char c : port) {
if (!isAsciiDigit(c)) return ERROR_INVALID_URL;
portValue = portValue * 10 + static_cast<uint32_t>(c - '0');
parts.port = defaultPort(parts.scheme);
if (uri.starts_with(':')) {
uri.remove_prefix(1);
const std::string_view port = uri.substr(0, uri.find('/'));
uri.remove_prefix(port.size());
if (port.size() > 5) return ERROR_INVALID_URL;
uint32_t portValue = 0;
for (const char c : port) {
if (!isAsciiDigit(c)) return ERROR_INVALID_URL;
portValue = portValue * 10 + static_cast<uint32_t>(c - '0');
}
if (portValue > 0x10000) return ERROR_INVALID_URL;
if (!port.empty()) parts.port = static_cast<uint16_t>(portValue);
}
if (portValue > 0xFFFF) return ERROR_INVALID_URL;
parts.port = port.empty() ? defaultPort(parts.scheme) : static_cast<uint16_t>(portValue);
const std::string_view path = uri.substr(0, uri.find_first_of("?#"));
parts.path = removeDotSegments(path);
parts.path = sweptPath(path);
parts.pathSpace = path.size();
uri.remove_prefix(path.size());
if (uri.starts_with('?')) {
@@ -164,8 +159,8 @@ int parseUri(std::string_view uri, UriParts& parts) {
}
size_t poolSize(const UriParts& parts) {
const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.path, parts.query, parts.fragment};
size_t size = 0;
const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.query, parts.fragment};
size_t size = parts.pathSpace + 1;
for (const std::string_view field : fields) size += field.size() + 1;
return size;
}
@@ -345,10 +340,12 @@ int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* src_url, void*
if (const int result = parseUri(src_url, parts); result != 0) return result;
const std::string_view fields[] = {parts.scheme, parts.username, parts.password, parts.hostname, parts.path, parts.query, parts.fragment};
const size_t spaces[] = {parts.scheme.size(), parts.username.size(), parts.password.size(), parts.hostname.size(), parts.pathSpace, parts.query.size(),
parts.fragment.size()};
const size_t size = poolSize(parts);
if (write && prepare < size) return ERROR_OUT_OF_MEMORY;
if (require) *require = size;
if (!write) return 0;
if (prepare < size) return ERROR_OUT_OF_MEMORY;
char* next = static_cast<char*>(pool);
char* strings[7];
@@ -356,7 +353,7 @@ int APS5_VABI sceHttpUriParse(SceHttpUriElement* out, const char* src_url, void*
strings[i] = next;
if (!fields[i].empty()) std::memcpy(next, fields[i].data(), fields[i].size());
next[fields[i].size()] = '\0';
next += fields[i].size() + 1;
next += spaces[i] + 1;
}
*out = {};
out->opaque = parts.opaque;
+9 -6
View File
@@ -17,6 +17,7 @@ constexpr std::uint32_t EndStatusUserCanceled = 1;
constexpr std::uint32_t EndStatusAborted = 2;
constexpr std::uint32_t MaxTextLength = 2048;
constexpr int ErrorBusy = static_cast<int>(0x80bc0001u);
constexpr int ErrorSuspending = static_cast<int>(0x80bc0009u);
constexpr int ErrorNotOpened = static_cast<int>(0x80bc0002u);
constexpr int ErrorNotInUse = static_cast<int>(0x80bc0107u);
constexpr int ErrorInvalidMaxTextLength = static_cast<int>(0x80bc0016u);
@@ -190,18 +191,20 @@ int APS5_VABI sceImeDialogForceClose(void) {
}
int APS5_VABI sceImeDialogGetCurrentStarState(int64_t state) {
NotImplemented_nid_no_patch(__func__);
std::lock_guard lock(g_mutex);
if (g_dialog.status == StatusNone) return ErrorNotInUse;
if (state == 0) return ErrorInvalidAddress;
*reinterpret_cast<std::uint32_t*>(state) = 0;
return 0;
}
int APS5_VABI sceImeDialogInitInternal(Param* param, ExtendedParam* extended) {
NotImplemented_nid_no_patch(__func__);
return 0;
return sceImeDialogInit(param, extended);
}
int APS5_VABI sceImeDialogSetPanelPosition(int32_t posx, int32_t posy) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceImeDialogSetPanelPosition(int32_t, int32_t) {
std::lock_guard lock(g_mutex);
return g_dialog.status == StatusNone ? ErrorNotInUse : ErrorSuspending;
}
+15
View File
@@ -0,0 +1,15 @@
add_library(libSceJxr SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceJxr PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceJxr)
if(BUILD_TESTING)
add_test_executable(guest_jxr_tests ${LIBS_INCLUDE_DIR}/tests/GuestJxr.cpp)
target_include_directories(guest_jxr_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_jxr_tests PRIVATE libSceJxr libc)
configure_windows_unwind(guest_jxr_tests)
add_test(NAME guest_jxr COMMAND guest_jxr_tests)
if(WIN32)
set_tests_properties(guest_jxr PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJxr>")
endif()
endif()
+12 -3
View File
@@ -3,12 +3,21 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr std::int64_t WmpErrSuccess = 0;
}
extern "C" {
void APS5_VABI free_nid_postfix(void* memory);
APS5_DUMMY_FUN
int APS5_VABI PKFree_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::int64_t APS5_VABI PKFree_nid_postfix(void** memory) {
if (memory != nullptr) {
free_nid_postfix(*memory);
*memory = nullptr;
}
return WmpErrSuccess;
}
int APS5_VABI PixelFormatLookup_nid_postfix(void) {
+5 -7
View File
@@ -27,6 +27,10 @@ int APS5_VABI sceKeyboardReadState(int32_t handle, KeyboardData* data) {
return Keyboard::ReadState(handle, data);
}
int APS5_VABI sceKeyboardGetConnection(std::int32_t handle, bool* connected) {
return Keyboard::GetConnection(handle, connected);
}
int APS5_VABI sceKeyboardGetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint8_t led, std::uint8_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) {
return Keyboard::GetKey2Char(handle, arrange, led, modifierKey, keyCode, charData);
}
@@ -47,8 +51,7 @@ int APS5_VABI sceKeyboardDeviceOpen(std::uint64_t deviceId, std::int32_t index,
}
int APS5_VABI sceKeyboardDisconnectAll(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return Keyboard::DisconnectAll();
}
int APS5_VABI sceKeyboardDisconnectDevice(std::int32_t deviceId) {
@@ -61,11 +64,6 @@ int APS5_VABI sceKeyboardDisconnectPort(std::int32_t first, std::int32_t second)
return 0;
}
int APS5_VABI sceKeyboardGetConnection(std::int32_t handle, bool* connected) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKeyboardGetDeviceInfo(std::int32_t deviceId, KeyboardDeviceInfo* info) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -18,6 +18,8 @@ int Open(int userId, std::int32_t type, std::int32_t index);
int Close(std::int32_t handle);
int Read(std::int32_t handle, KeyboardData* data, std::int32_t num);
int ReadState(std::int32_t handle, KeyboardData* data);
int GetConnection(std::int32_t handle, bool* connected);
int DisconnectAll();
int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData);
void Publish(const KeyboardInputEvent& event);
}
@@ -154,6 +154,23 @@ int ReadState(std::int32_t handle, KeyboardData* data) {
return KEYBOARD_OK;
}
int GetConnection(std::int32_t handle, bool* isConnected) {
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE;
if (isConnected == nullptr) return KEYBOARD_ERROR_INVALID_ARG;
*isConnected = connected;
return KEYBOARD_OK;
}
int DisconnectAll() {
KeyboardInputEvent event{};
event.connectionChange = true;
event.connected = false;
Publish(event);
return KEYBOARD_OK;
}
int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t ledState, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) {
{
std::lock_guard lock(keyboardMutex);
@@ -13,6 +13,7 @@ int APS5_VABI sceKeyboardOpen(int, std::int32_t, std::int32_t, const void*);
int APS5_VABI sceKeyboardClose(std::int32_t);
int APS5_VABI sceKeyboardRead(std::int32_t, KeyboardData*, std::int32_t);
int APS5_VABI sceKeyboardReadState(std::int32_t, KeyboardData*);
int APS5_VABI sceKeyboardGetConnection(std::int32_t, bool*);
int APS5_VABI sceKeyboardGetKey2Char(std::int32_t, std::int32_t, std::uint8_t, std::uint8_t, std::uint16_t, KeyboardCharData*);
int APS5_VABI sceKeyboardConnectPort(void);
int APS5_VABI sceKeyboardDisconnectAll(void);
@@ -44,14 +45,20 @@ static std::uint16_t Jis(std::uint32_t modifierKey, std::uint16_t keyCode, std::
int main() {
KeyboardData data[16]{};
KeyboardCharData charData{};
bool connected = false;
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_NOT_INITIALIZED);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_NOT_INITIALIZED);
Require(sceKeyboardInit() == KEYBOARD_OK);
Require(sceKeyboardInit() == KEYBOARD_OK);
Require(sceKeyboardOpen(1, 0, 1, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardOpen(1, 1, 0, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_ALREADY_OPENED);
Require(sceKeyboardGetConnection(42, &connected) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && connected);
Require(sceKeyboardRead(42, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardRead(KEYBOARD_HANDLE, nullptr, 1) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 17) == KEYBOARD_ERROR_INVALID_ARG);
@@ -96,9 +103,11 @@ int main() {
window.window.event = SDL_WINDOWEVENT_CLOSE;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && !connected);
window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_OK && connected);
for (std::uint16_t key = 0x04; key < 0x04 + 20; ++key) {
KeyboardInputEvent press{};
@@ -143,11 +152,31 @@ int main() {
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, 2, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK);
Require(sceKeyboardGetConnection(KEYBOARD_HANDLE, &connected) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].length == 1 && data[0].key_code[0] == 0);
Require(data[0].connected);
KeyboardInputEvent pressed{};
pressed.keyCode = 0x04;
pressed.pressed = true;
KeyboardPublishInput_nid_postfix(pressed);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].key_code[0] == 0x04);
Require(sceKeyboardDisconnectAll() == KEYBOARD_OK);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0);
Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_OK && !data[0].connected && data[0].key_code[0] == 0);
Require(sceKeyboardDisconnectAll() == KEYBOARD_OK);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
pressed.keyCode = 0x05;
KeyboardPublishInput_nid_postfix(pressed);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
window.window.event = SDL_WINDOWEVENT_FOCUS_LOST;
input.HandleEvent(window, 7);
window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected);
Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK);
bool threwConnect = false;
@@ -157,12 +186,4 @@ int main() {
threwConnect = true;
}
Require(threwConnect);
bool threwDisconnect = false;
try {
sceKeyboardDisconnectAll();
} catch (const std::runtime_error&) {
threwDisconnect = true;
}
Require(threwDisconnect);
}
Loaded 100 of 348 files, more files were not shown because too many files have changed in this diff. Show more