mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
Merge main into feat/http-cookie-enabled-option
This commit is contained in:
commit
57bf968aa3
348 files changed
+12199
-1588
No files matched your search
@@ -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
|
||||
+1
Submodule 3rdparty/ogg added at be05b13e98.
+1
Submodule 3rdparty/vorbis added at 0657aee69d.
@@ -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>")
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
struct HttpOptions {
|
||||
bool autoRedirect = true;
|
||||
bool authEnabled = false;
|
||||
bool cookieEnabled = false;
|
||||
bool nonblock = false;
|
||||
bool acceptEncodingGzip = false;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
Reference in new issue
Block a user