Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions core/libs/prx/libSceAgc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ set(agcSources
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/PacketInfo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Platform.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Semaphore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Patch/src/CondExec.cpp
Expand All @@ -47,6 +48,7 @@ set(agcSources
${CMAKE_CURRENT_SOURCE_DIR}/Predication/src/Predication.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/CreateShader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/GsOversubscription.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/GsPrimPayload.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/InterpolantMapping.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/LinkShaders.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/PrimState.cpp
Expand Down Expand Up @@ -126,6 +128,19 @@ if(BUILD_TESTING)
endif()
endif()

add_test_executable(agc_semaphore_tests EXCLUDE_FROM_ALL tests/Semaphore.cpp)
target_include_directories(agc_semaphore_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_semaphore_tests PRIVATE libSceAgc libc)
configure_windows_unwind(agc_semaphore_tests)
if(BUILD_TESTING)
set_target_properties(agc_semaphore_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_semaphore_tests COMMAND agc_semaphore_tests)
if(WIN32)
set_tests_properties(agc_semaphore_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

add_test_executable(agc_packet_info_tests EXCLUDE_FROM_ALL tests/PacketInfo.cpp)
target_include_directories(agc_packet_info_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_packet_info_tests PRIVATE libSceAgc libc)
Expand Down Expand Up @@ -323,6 +338,22 @@ if(BUILD_TESTING)
endif()
endif()

add_test_executable(agc_gs_prim_payload_tests EXCLUDE_FROM_ALL
tests/GsPrimPayload.cpp
Shader/src/GsPrimPayload.cpp
)
target_include_directories(agc_gs_prim_payload_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_gs_prim_payload_tests PRIVATE libc)
configure_windows_unwind(agc_gs_prim_payload_tests)
if(BUILD_TESTING)
set_target_properties(agc_gs_prim_payload_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_gs_prim_payload_tests COMMAND agc_gs_prim_payload_tests)
if(WIN32)
set_tests_properties(agc_gs_prim_payload_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

add_test_executable(agc_dma_data_tests EXCLUDE_FROM_ALL
tests/DmaData.cpp
Command/src/Packet.cpp
Expand Down
3 changes: 3 additions & 0 deletions core/libs/prx/libSceAgc/DcbState/src/Init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"

static constexpr int SCE_AGC_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80890001);

extern "C" {

int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
Expand All @@ -16,6 +18,7 @@ int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
}

int APS5_VABI sceAgcInit(std::uint32_t version) {
if (version >= 14) return SCE_AGC_ERROR_INVALID_ARGUMENT;
Agc::Command::GetRegisterDefaults(version, false, __func__);
Agc::Command::GetRegisterDefaults(version, true, __func__);
return 0;
Expand Down
4 changes: 4 additions & 0 deletions core/libs/prx/libSceAgc/Misc/include/Semaphore.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SEMAPHORE_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SEMAPHORE_HPP

#endif
61 changes: 61 additions & 0 deletions core/libs/prx/libSceAgc/Misc/src/Semaphore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "prx/libSceAgc/Misc/include/Semaphore.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"

#include <cstdint>
#include <cstddef>
#include <cstring>
#include <limits>
#include <mutex>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"

namespace {

constexpr std::uint64_t semaphoreMemoryAlignment = 0x4000u;
constexpr std::uint64_t semaphoreLabelBytes = 32u;
constexpr std::uint64_t semaphoreCounterBytes = 8u;

std::mutex semaphoreMutex;
std::uint8_t* semaphoreMemory = nullptr;
std::uint64_t semaphoreBytes = 0;

bool hasOnlyZeroLabelTails(const std::uint8_t* memory, std::uint64_t sizeInBytes) {
for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) {
for (std::uint64_t byte = semaphoreCounterBytes; byte < semaphoreLabelBytes; ++byte) {
if (memory[label + byte] != 0) return false;
}
}
return true;
}

}

extern "C" {

int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes) {
const auto address = reinterpret_cast<std::uintptr_t>(memory);
std::lock_guard lock(semaphoreMutex);
Agc::Command::Require(semaphoreMemory == nullptr, __func__, "the AMM semaphore memory is already registered; the console's error is unknown");
Agc::Command::Require(memory != nullptr && sizeInBytes != 0, __func__, "null or empty AMM semaphore memory; the console's error is unknown");
Agc::Command::Require(((address | sizeInBytes) & (semaphoreMemoryAlignment - 1u)) == 0, __func__, "AMM semaphore memory or size is not 16 KiB aligned; the console's error is unknown");
Agc::Command::Require(sizeInBytes - 1u <= std::numeric_limits<std::uintptr_t>::max() - address, __func__, "AMM semaphore memory wraps the address space");
auto* labels = static_cast<std::uint8_t*>(memory);
Agc::Command::Require(hasOnlyZeroLabelTails(labels, sizeInBytes), __func__, "AMM semaphore labels hold data past their first 8 bytes; whether registration clears it is unknown");
for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) {
std::memset(labels + label, 0, semaphoreCounterBytes);
}
semaphoreMemory = labels;
semaphoreBytes = sizeInBytes;
return 0;
}

int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label) {
std::lock_guard lock(semaphoreMutex);
Agc::Command::Require(semaphoreMemory != nullptr, __func__, "no AMM semaphore memory is registered; the console's error is unknown");
Agc::Command::Require(index < semaphoreBytes / semaphoreLabelBytes, __func__, "the semaphore label lies outside the AMM semaphore memory; the console's error is unknown");
Agc::Command::Require(label != nullptr, __func__, "null semaphore label output; the console's error is unknown");
*label = semaphoreMemory + static_cast<std::uint64_t>(index) * semaphoreLabelBytes;
return 0;
}

}
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgc/Shader/include/ShaderConstants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ constexpr std::uint32_t GE_PC_ALLOC = 0x260u;

constexpr std::uint32_t SPI_SHADER_PGM_RSRC4_GS = 0x081u;
constexpr std::uint32_t SPI_VS_OUT_CONFIG = 0x1B1u;
constexpr std::uint32_t SPI_SHADER_IDX_FORMAT = 0x1C2u;
constexpr std::uint32_t GE_MAX_OUTPUT_PER_SUBGROUP = 0x1FFu;
constexpr std::uint32_t PA_CL_VS_OUT_CNTL = 0x207u;
constexpr std::uint32_t VGT_GS_ONCHIP_CNTL = 0x291u;
Expand Down
39 changes: 39 additions & 0 deletions core/libs/prx/libSceAgc/Shader/src/GsPrimPayload.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <cstdint>
#include <stdexcept>
#include <string>
#include <prx/libc/include/General.hpp>

#include "SceShaders.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"

namespace {

constexpr std::uint32_t IDX0_EXPORT_FORMAT_MASK = 0xFu;
constexpr std::uint32_t SPI_SHADER_2COMP = 2u;
constexpr std::uint32_t TwoComponentPayloadBytes = 8u;

}

extern "C" {

int APS5_VABI sceAgcGetGsPrimPayload(std::uint32_t* payload, const Shader* gs) {
if (payload == nullptr || gs == nullptr) {
throw std::runtime_error(std::string(__func__) + ": payload or gs is null");
}
if (gs->num_cx_registers != 0 && gs->cx_registers == nullptr) {
throw std::runtime_error(std::string(__func__) + ": gs->cx_registers is null but num_cx_registers != 0");
}

std::uint32_t bytes = 0;
for (std::uint32_t i = 0; i < gs->num_cx_registers; ++i) {
const ShaderRegister& reg = gs->cx_registers[i];
if (reg.offset == ShaderRegs::SPI_SHADER_IDX_FORMAT && (reg.value & IDX0_EXPORT_FORMAT_MASK) == SPI_SHADER_2COMP) {
bytes = TwoComponentPayloadBytes;
break;
}
}
*payload = bytes;
return 0;
}

}
15 changes: 0 additions & 15 deletions core/libs/prx/libSceAgc/Unimplemented.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,6 @@ int APS5_VABI sceAgcGetDefaultCxStateFlat() {
return 0;
}

int APS5_VABI sceAgcGetSemaphoreLabel() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceAgcSetAmmSemaphoreMemory() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceAgcSetSemaphoreMemory() {
NotImplemented_nid_no_patch(__func__);
return 0;
Expand All @@ -50,11 +40,6 @@ int APS5_VABI sceAgcCbMemsetExclusive() {
return 0;
}

int APS5_VABI sceAgcGetGsPrimPayload() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

APS5_EXPORT("vieBRwlh1Lw", sceAgcUnknown_vieBRwlh1Lw);
int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(void) {
NotImplemented_nid_no_patch(__func__);
Expand Down
8 changes: 6 additions & 2 deletions core/libs/prx/libSceAgc/tests/Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ extern "C" int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId);

namespace {

constexpr int SCE_AGC_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80890001);

void check(bool condition, const char* message) {
if (!condition) {
throw std::runtime_error(message);
Expand Down Expand Up @@ -538,8 +540,10 @@ void testDefaults() {
expectFailure([] { sceAgcInit_0090(nullptr, 8); });
expectFailure([&] { sceAgcInit_0090(&state, 14); });
check(sceAgcInit(8) == 0, "AGC version initialization failed");
expectFailure([] { sceAgcInit(14); });
expectFailure([] { sceAgcInit(0xffffffffu); });
check(sceAgcInit(13) == 0, "AGC version 13 initialization failed");
check(sceAgcInit(14) == SCE_AGC_ERROR_INVALID_ARGUMENT, "unsupported AGC version did not return invalid argument");
check(sceAgcInit(0xffffffffu) == SCE_AGC_ERROR_INVALID_ARGUMENT, "maximum AGC version did not return invalid argument");
check(sceAgcInit(0xffffc278u) == SCE_AGC_ERROR_INVALID_ARGUMENT, "pointer-valued AGC argument did not return invalid argument");
for (std::uint32_t version = 0; version < 14; ++version) {
for (const bool internal : {false, true}) {
auto* first = Agc::Command::GetRegisterDefaults(version, internal, __func__);
Expand Down
119 changes: 119 additions & 0 deletions core/libs/prx/libSceAgc/tests/GsPrimPayload.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "SceShaders.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"

#include <array>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <vector>

extern "C" int APS5_VABI sceAgcGetGsPrimPayload(std::uint32_t* payload, const Shader* gs);

namespace {

constexpr std::uint32_t Sentinel = 0xccccccccu;

void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}

template <typename TAction>
void expectFailure(TAction action, const char* message) {
try {
action();
} catch (const std::exception& error) {
check(error.what()[0] != '\0', "empty exception message");
return;
}
throw std::runtime_error(message);
}

Shader makeGs(std::vector<ShaderRegister>& cx) {
Shader gs{};
gs.cx_registers = cx.empty() ? nullptr : cx.data();
gs.num_cx_registers = static_cast<std::uint8_t>(cx.size());
return gs;
}

void expectPayload(const Shader& gs, std::uint32_t expected, const char* message) {
std::array<std::uint32_t, 2> out{Sentinel, Sentinel};
check(sceAgcGetGsPrimPayload(out.data(), &gs) == 0, message);
check(out[0] == expected, message);
check(out[1] == Sentinel, "the payload write went past four bytes");
}

void testNoRegisters() {
std::vector<ShaderRegister> cx;
expectPayload(makeGs(cx), 0, "a shader without context registers reported a payload");
}

void testTwoComponentIndexFormat() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u}};
expectPayload(makeGs(cx), 8, "a two-component index export did not report 8 bytes");
}

void testOnlyTheLowNibbleCounts() {
std::vector<ShaderRegister> high{{ShaderRegs::SPI_SHADER_IDX_FORMAT, 0xfffffff2u}};
expectPayload(makeGs(high), 8, "bits above IDX0_EXPORT_FORMAT changed the payload");
for (const std::uint32_t format : {0u, 1u, 3u, 4u, 0x20u}) {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_SHADER_IDX_FORMAT, format}};
expectPayload(makeGs(cx), 0, "an index export format other than two components reported a payload");
}
}

void testOtherRegistersAreIgnored() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_VS_OUT_CONFIG, 2u}, {ShaderRegs::GE_MAX_OUTPUT_PER_SUBGROUP, 2u}};
expectPayload(makeGs(cx), 0, "a register other than SPI_SHADER_IDX_FORMAT reported a payload");
}

void testSearchContinuesPastAMismatch() {
std::vector<ShaderRegister> cx{
{ShaderRegs::SPI_VS_OUT_CONFIG, 0u},
{ShaderRegs::SPI_SHADER_IDX_FORMAT, 4u},
{ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u},
};
expectPayload(makeGs(cx), 8, "a non-matching SPI_SHADER_IDX_FORMAT entry stopped the search");
}

void testCountBoundsTheSearch() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_VS_OUT_CONFIG, 0u}, {ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u}};
Shader gs = makeGs(cx);
gs.num_cx_registers = 1;
expectPayload(gs, 0, "an entry past num_cx_registers was read");
}

void testRejections() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u}};
const Shader gs = makeGs(cx);
std::uint32_t out = Sentinel;
expectFailure([&] { sceAgcGetGsPrimPayload(nullptr, &gs); }, "a null payload pointer was accepted");
expectFailure([&] { sceAgcGetGsPrimPayload(&out, nullptr); }, "a null shader was accepted");
Shader missing{};
missing.num_cx_registers = 1;
expectFailure([&] { sceAgcGetGsPrimPayload(&out, &missing); }, "a register count without a register array was accepted");
check(out == Sentinel, "a rejected call wrote the payload");
}

}

int main() {
try {
testNoRegisters();
testTwoComponentIndexFormat();
testOnlyTheLowNibbleCounts();
testOtherRegistersAreIgnored();
testSearchContinuesPastAMismatch();
testCountBoundsTheSearch();
testRejections();
LibcRunShutdown_nid_postfix();
std::puts("AGC GS primitive payload tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
try { LibcRunShutdown_nid_postfix(); }
catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); }
return 1;
}
}
Loading
Loading