feat(audio3d): implement the port queue
Initialize, PortOpen/Close, PortSetAttribute, PortGetQueueLevel, PortAdvance and PortPush. One port, validation order and error codes from the PS5 module as read by prosper; blocking push paced at granularity samples per frame at 48 kHz like KytyPS5. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bc24783e5b
commit
06d2ae5f05
|
|
@ -18,4 +18,13 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_audio3d_parameters PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudio3d>")
|
||||
endif()
|
||||
add_test_executable(guest_audio3d_port_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dPort.cpp)
|
||||
target_include_directories(guest_audio3d_port_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_audio3d_port_tests PRIVATE libSceAudio3d libc)
|
||||
configure_windows_unwind(guest_audio3d_port_tests)
|
||||
add_test(NAME guest_audio3d_port COMMAND guest_audio3d_port_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_audio3d_port PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudio3d>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,62 @@
|
|||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int AUDIO3D_ERROR_INVALID_PORT = static_cast<int>(0x80EA0002);
|
||||
constexpr int AUDIO3D_ERROR_INVALID_PARAMETER = static_cast<int>(0x80EA0004);
|
||||
constexpr int AUDIO3D_ERROR_OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
|
||||
constexpr int AUDIO3D_ERROR_NOT_READY = static_cast<int>(0x80EA0007);
|
||||
|
||||
constexpr int AUDIO3D_USER_ID_SYSTEM = 0xFF;
|
||||
constexpr std::uint32_t AUDIO3D_PORT_ID = 0;
|
||||
constexpr std::uint32_t AUDIO3D_RATE_48000 = 0;
|
||||
constexpr std::uint32_t AUDIO3D_SAMPLE_RATE = 48000;
|
||||
constexpr std::uint32_t AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2;
|
||||
constexpr std::uint32_t AUDIO3D_BLOCKING_ASYNC = 0;
|
||||
constexpr std::uint32_t AUDIO3D_BLOCKING_SYNC = 1;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL = 0x10001;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS = 0x10002;
|
||||
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE = 0x10003;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
struct Audio3dPort {
|
||||
bool open = false;
|
||||
std::uint32_t queue_depth = 0;
|
||||
Clock::duration frame_duration{};
|
||||
std::uint32_t advanced = 0;
|
||||
std::deque<Clock::time_point> playing;
|
||||
};
|
||||
|
||||
std::mutex g_mutex;
|
||||
bool g_initialized = false;
|
||||
Audio3dPort g_port;
|
||||
|
||||
[[noreturn]] void Unsupported(const char* function, const std::string& what) {
|
||||
throw std::runtime_error(std::string(function) + ": " + what);
|
||||
}
|
||||
|
||||
bool PortIsOpen(std::uint32_t port_id) {
|
||||
return port_id == AUDIO3D_PORT_ID && g_port.open;
|
||||
}
|
||||
|
||||
std::uint32_t QueueLevel(Clock::time_point now) {
|
||||
while (!g_port.playing.empty() && g_port.playing.front() <= now) g_port.playing.pop_front();
|
||||
return g_port.advanced + static_cast<std::uint32_t>(g_port.playing.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
|
||||
|
|
@ -14,53 +67,109 @@ void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
|
|||
}
|
||||
|
||||
int APS5_VABI sceAudio3dInitialize(int64_t reserved) {
|
||||
(void)reserved;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (reserved != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (g_initialized) Unsupported(__func__, "repeated initialization");
|
||||
g_initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortAdvance(uint32_t port_id) {
|
||||
(void)port_id;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (QueueLevel(Clock::now()) >= g_port.queue_depth) Unsupported(__func__, "queue is full");
|
||||
++g_port.advanced;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortClose(uint32_t port_id) {
|
||||
(void)port_id;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
g_port = Audio3dPort{};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level, uint32_t* queue_available) {
|
||||
(void)port_id;
|
||||
(void)queue_level;
|
||||
(void)queue_available;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (queue_level == nullptr && queue_available == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
const std::uint32_t level = QueueLevel(Clock::now());
|
||||
if (queue_level != nullptr) *queue_level = level;
|
||||
if (queue_available != nullptr) *queue_available = g_port.queue_depth - level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, uint32_t* id) {
|
||||
(void)user_id;
|
||||
(void)parameters;
|
||||
(void)id;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!g_initialized) return AUDIO3D_ERROR_NOT_READY;
|
||||
if (user_id != AUDIO3D_USER_ID_SYSTEM || parameters == nullptr || id == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
std::uint32_t num_beds = 2;
|
||||
switch (parameters->size_this) {
|
||||
case 0x10:
|
||||
case 0x18:
|
||||
Unsupported(__func__, "parameter size " + std::to_string(parameters->size_this) + " selects an unsupported buffer mode");
|
||||
case 0x20:
|
||||
break;
|
||||
case 0x28:
|
||||
num_beds = parameters->num_beds;
|
||||
break;
|
||||
default:
|
||||
return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
if (parameters->rate != AUDIO3D_RATE_48000) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (parameters->granularity < 0x100 || (parameters->granularity & 0xFF) != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (parameters->max_objects == 0 || parameters->queue_depth == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (parameters->buffer_mode > AUDIO3D_BUFFER_ADVANCE_AND_PUSH) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (num_beds != 2 && num_beds != 3) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (parameters->buffer_mode != AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
|
||||
Unsupported(__func__, "buffer mode " + std::to_string(parameters->buffer_mode));
|
||||
}
|
||||
if (num_beds != 2) Unsupported(__func__, "3 beds");
|
||||
if (g_port.open) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
||||
g_port = Audio3dPort{};
|
||||
g_port.open = true;
|
||||
g_port.queue_depth = parameters->queue_depth;
|
||||
g_port.frame_duration = std::chrono::duration_cast<Clock::duration>(
|
||||
std::chrono::nanoseconds(std::uint64_t{parameters->granularity} * 1000000000u / AUDIO3D_SAMPLE_RATE));
|
||||
*id = AUDIO3D_PORT_ID;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortPush(uint32_t port_id, uint32_t blocking) {
|
||||
(void)port_id;
|
||||
(void)blocking;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
Clock::time_point wait_until;
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (blocking != AUDIO3D_BLOCKING_ASYNC && blocking != AUDIO3D_BLOCKING_SYNC) {
|
||||
Unsupported(__func__, "blocking mode " + std::to_string(blocking));
|
||||
}
|
||||
const Clock::time_point now = Clock::now();
|
||||
const std::uint32_t level = QueueLevel(now);
|
||||
Clock::time_point end = g_port.playing.empty() ? now : g_port.playing.back();
|
||||
for (; g_port.advanced != 0; --g_port.advanced) {
|
||||
end += g_port.frame_duration;
|
||||
g_port.playing.push_back(end);
|
||||
}
|
||||
if (blocking == AUDIO3D_BLOCKING_ASYNC || level < g_port.queue_depth) return 0;
|
||||
wait_until = g_port.playing[level - g_port.queue_depth];
|
||||
}
|
||||
std::this_thread::sleep_until(wait_until);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id, const void* attribute, size_t attribute_size) {
|
||||
(void)port_id;
|
||||
(void)attribute_id;
|
||||
(void)attribute;
|
||||
(void)attribute_size;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (attribute == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (attribute_size != 4) Unsupported(__func__, "attribute size " + std::to_string(attribute_size));
|
||||
switch (attribute_id) {
|
||||
case AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL:
|
||||
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS:
|
||||
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE:
|
||||
return 0;
|
||||
default:
|
||||
Unsupported(__func__, "attribute " + std::to_string(attribute_id));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* parameters);
|
||||
int APS5_VABI sceAudio3dInitialize(std::int64_t reserved);
|
||||
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, std::uint32_t* id);
|
||||
int APS5_VABI sceAudio3dPortClose(std::uint32_t port_id);
|
||||
int APS5_VABI sceAudio3dPortSetAttribute(std::uint32_t port_id, std::uint32_t attribute_id, const void* attribute, std::size_t attribute_size);
|
||||
int APS5_VABI sceAudio3dPortGetQueueLevel(std::uint32_t port_id, std::uint32_t* queue_level, std::uint32_t* queue_available);
|
||||
int APS5_VABI sceAudio3dPortAdvance(std::uint32_t port_id);
|
||||
int APS5_VABI sceAudio3dPortPush(std::uint32_t port_id, std::uint32_t blocking);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int INVALID_PORT = static_cast<int>(0x80EA0002);
|
||||
constexpr int INVALID_PARAMETER = static_cast<int>(0x80EA0004);
|
||||
constexpr int OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
|
||||
constexpr int NOT_READY = static_cast<int>(0x80EA0007);
|
||||
constexpr int SYSTEM_USER = 0xFF;
|
||||
|
||||
void Require(bool value, const char* message) {
|
||||
if (value) return;
|
||||
std::fprintf(stderr, "%s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void RequireThrows(F f, const char* message) {
|
||||
bool threw = false;
|
||||
try {
|
||||
f();
|
||||
} catch (const std::exception&) {
|
||||
threw = true;
|
||||
}
|
||||
Require(threw, message);
|
||||
}
|
||||
|
||||
Audio3dOpenParameters Defaults() {
|
||||
Audio3dOpenParameters parameters{};
|
||||
sceAudio3dGetDefaultOpenParameters(¶meters);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
int Open(const Audio3dOpenParameters& parameters, std::uint32_t* id) {
|
||||
return sceAudio3dPortOpen(SYSTEM_USER, ¶meters, id);
|
||||
}
|
||||
|
||||
void RequireLevel(std::uint32_t level, std::uint32_t available, const char* message) {
|
||||
std::uint32_t got_level = 99;
|
||||
std::uint32_t got_available = 99;
|
||||
Require(sceAudio3dPortGetQueueLevel(0, &got_level, &got_available) == 0, message);
|
||||
Require(got_level == level && got_available == available, message);
|
||||
}
|
||||
|
||||
void CheckBeforeInitialize() {
|
||||
std::uint32_t id = 7;
|
||||
Audio3dOpenParameters parameters = Defaults();
|
||||
Require(sceAudio3dInitialize(1) == INVALID_PARAMETER, "non-zero reserved must be rejected");
|
||||
Require(Open(parameters, &id) == NOT_READY, "open before initialize must be NOT_READY");
|
||||
Require(id == 7, "a failed open must not write the port id");
|
||||
Require(sceAudio3dPortOpen(1, nullptr, nullptr) == NOT_READY, "NOT_READY is checked before the arguments");
|
||||
std::uint32_t level = 0;
|
||||
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == INVALID_PORT, "queue level needs an open port");
|
||||
Require(sceAudio3dPortClose(0) == INVALID_PORT, "close needs an open port");
|
||||
Require(sceAudio3dPortAdvance(0) == INVALID_PORT, "advance needs an open port");
|
||||
Require(sceAudio3dPortPush(0, 0) == INVALID_PORT, "push needs an open port");
|
||||
float value = 0.0f;
|
||||
Require(sceAudio3dPortSetAttribute(0, 0x10001, &value, sizeof(value)) == INVALID_PORT, "set attribute needs an open port");
|
||||
Require(sceAudio3dInitialize(0) == 0, "initialize");
|
||||
RequireThrows([] { sceAudio3dInitialize(0); }, "repeated initialize must throw");
|
||||
}
|
||||
|
||||
void CheckOpenParameters() {
|
||||
std::uint32_t id = 7;
|
||||
const Audio3dOpenParameters defaults = Defaults();
|
||||
Require(sceAudio3dPortOpen(1, &defaults, &id) == INVALID_PARAMETER, "user id must be the system user");
|
||||
Require(sceAudio3dPortOpen(SYSTEM_USER, nullptr, &id) == INVALID_PARAMETER, "null parameters");
|
||||
Require(sceAudio3dPortOpen(SYSTEM_USER, &defaults, nullptr) == INVALID_PARAMETER, "null port id");
|
||||
auto rejects = [&](auto change, const char* message) {
|
||||
Audio3dOpenParameters parameters = defaults;
|
||||
change(parameters);
|
||||
Require(Open(parameters, &id) == INVALID_PARAMETER, message);
|
||||
Require(id == 7, "a failed open must not write the port id");
|
||||
};
|
||||
rejects([](auto& p) { p.size_this = 0x30; }, "unknown parameter size");
|
||||
rejects([](auto& p) { p.size_this = 0x21; }, "parameter size is not masked");
|
||||
rejects([](auto& p) { p.rate = 1; }, "only 48 kHz");
|
||||
rejects([](auto& p) { p.granularity = 0x80; }, "granularity below 256");
|
||||
rejects([](auto& p) { p.granularity = 0x180; }, "granularity not a multiple of 256");
|
||||
rejects([](auto& p) { p.max_objects = 0; }, "zero objects");
|
||||
rejects([](auto& p) { p.queue_depth = 0; }, "zero queue depth");
|
||||
rejects([](auto& p) { p.buffer_mode = 3; }, "buffer mode above 2");
|
||||
rejects([](auto& p) { p.size_this = 0x28; p.num_beds = 4; }, "4 beds");
|
||||
rejects([](auto& p) { p.size_this = 0x28; p.num_beds = 1; }, "1 bed");
|
||||
auto throws = [&](auto change, const char* message) {
|
||||
Audio3dOpenParameters parameters = defaults;
|
||||
change(parameters);
|
||||
RequireThrows([&] { Open(parameters, &id); }, message);
|
||||
};
|
||||
throws([](auto& p) { p.size_this = 0x10; }, "0x10 parameters select buffer mode 0");
|
||||
throws([](auto& p) { p.size_this = 0x18; }, "0x18 parameters select buffer mode 1");
|
||||
throws([](auto& p) { p.buffer_mode = 1; }, "buffer mode 1");
|
||||
throws([](auto& p) { p.size_this = 0x28; p.num_beds = 3; }, "3 beds");
|
||||
}
|
||||
|
||||
void CheckOpenClose() {
|
||||
std::uint32_t id = 7;
|
||||
Audio3dOpenParameters parameters = Defaults();
|
||||
parameters.size_this = 0x28;
|
||||
parameters.num_beds = 2;
|
||||
Require(Open(parameters, &id) == 0 && id == 0, "open hands out port 0");
|
||||
std::uint32_t second = 7;
|
||||
Require(Open(parameters, &second) == OUT_OF_RESOURCES && second == 7, "only one port");
|
||||
std::uint32_t level = 0;
|
||||
Require(sceAudio3dPortGetQueueLevel(1, &level, nullptr) == INVALID_PORT, "port 1 does not exist");
|
||||
Require(sceAudio3dPortClose(1) == INVALID_PORT, "close of port 1");
|
||||
Require(sceAudio3dPortClose(0) == 0, "close");
|
||||
Require(sceAudio3dPortClose(0) == INVALID_PORT, "double close");
|
||||
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == INVALID_PORT, "closed port");
|
||||
}
|
||||
|
||||
void CheckAttributes() {
|
||||
float value = 0.5f;
|
||||
int flag = 1;
|
||||
Require(sceAudio3dPortSetAttribute(0, 0x10001, nullptr, 4) == INVALID_PARAMETER, "null attribute");
|
||||
Require(sceAudio3dPortSetAttribute(1, 0x10001, &value, 4) == INVALID_PORT, "attribute on port 1");
|
||||
Require(sceAudio3dPortSetAttribute(0, 0x10001, &value, 4) == 0, "late reverb level");
|
||||
Require(sceAudio3dPortSetAttribute(0, 0x10002, &value, 4) == 0, "downmix spread radius");
|
||||
Require(sceAudio3dPortSetAttribute(0, 0x10003, &flag, 4) == 0, "downmix spread height aware");
|
||||
RequireThrows([&] { sceAudio3dPortSetAttribute(0, 0x10004, &value, 4); }, "unknown attribute must throw");
|
||||
RequireThrows([&] { sceAudio3dPortSetAttribute(0, 0x10001, &value, 8); }, "wrong attribute size must throw");
|
||||
}
|
||||
|
||||
void CheckQueue() {
|
||||
using namespace std::chrono;
|
||||
constexpr std::uint32_t granularity = 0x1800;
|
||||
constexpr auto frame = microseconds(1000000ull * granularity / 48000);
|
||||
Audio3dOpenParameters parameters = Defaults();
|
||||
parameters.granularity = granularity;
|
||||
std::uint32_t id = 7;
|
||||
Require(Open(parameters, &id) == 0 && id == 0, "open");
|
||||
CheckAttributes();
|
||||
Require(sceAudio3dPortGetQueueLevel(0, nullptr, nullptr) == INVALID_PARAMETER, "both outputs null");
|
||||
std::uint32_t available = 0;
|
||||
Require(sceAudio3dPortGetQueueLevel(0, nullptr, &available) == 0 && available == 2, "level pointer is optional");
|
||||
RequireLevel(0, 2, "empty queue");
|
||||
Require(sceAudio3dPortPush(0, 1) == 0, "push of an empty queue");
|
||||
RequireLevel(0, 2, "empty push queues nothing");
|
||||
Require(sceAudio3dPortAdvance(0) == 0, "advance 1");
|
||||
RequireLevel(1, 1, "one frame queued");
|
||||
Require(sceAudio3dPortAdvance(0) == 0, "advance 2");
|
||||
RequireLevel(2, 0, "queue full");
|
||||
RequireThrows([] { sceAudio3dPortAdvance(0); }, "advance into a full queue must throw");
|
||||
RequireThrows([] { sceAudio3dPortPush(0, 2); }, "unknown blocking mode must throw");
|
||||
|
||||
auto start = steady_clock::now();
|
||||
Require(sceAudio3dPortPush(0, 1) == 0, "blocking push");
|
||||
auto waited = steady_clock::now() - start;
|
||||
Require(waited >= frame - milliseconds(2), "blocking push waits until one frame has played");
|
||||
RequireLevel(1, 1, "blocking push returns with one free slot");
|
||||
|
||||
Require(sceAudio3dPortAdvance(0) == 0, "advance 3");
|
||||
start = steady_clock::now();
|
||||
Require(sceAudio3dPortPush(0, 0) == 0, "async push");
|
||||
Require(steady_clock::now() - start < frame, "async push does not wait for a frame");
|
||||
std::uint32_t level = 0;
|
||||
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == 0 && level >= 1, "async push keeps frames queued");
|
||||
std::this_thread::sleep_for(frame * 3);
|
||||
RequireLevel(0, 2, "frames drain at the port's rate");
|
||||
Require(sceAudio3dPortClose(0) == 0, "close");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckBeforeInitialize();
|
||||
CheckOpenParameters();
|
||||
CheckOpenClose();
|
||||
CheckQueue();
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing
|
||||
- [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling
|
||||
- The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
|
||||
- [libSceAudio3d](../../core/libs/prx/libSceAudio3d/Export.cpp) - the port produces no sound: the library has no bed or object exports to give it audio, so `sceAudio3dPortPush` only paces the queue at `granularity` samples per frame at 48 kHz. `sceAudio3dPortSetAttribute` accepts the late reverb level and the downmix spread attributes and ignores them
|
||||
- [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement.
|
||||
- [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, and `sceNpTrophy2UnregisterUnlockCallback` only returns success.
|
||||
- [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time.
|
||||
|
|
@ -31,6 +32,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceVideoOutRegisterBuffers2](../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) (libSceVideoOut) - buffer option 8 (STRICT_COLORIMETRY) is presented as option 0, with the stored values unchanged; what the console changes for it is unknown. Only options 0 and 8 are accepted
|
||||
- [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown
|
||||
- [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware
|
||||
- [sceAudio3dPortOpen](../../core/libs/prx/libSceAudio3d/Export.cpp) (libSceAudio3d) - buffer modes 0 and 1 (also selected by the 0x10 and 0x18 parameter sizes) and 3 beds depend on an SDK version check in the module ([prosper](https://github.com/mattias800/prosper)) and throw. A repeated `sceAudio3dInitialize` (`NOT_READY` in shadPS4, 0 in prosper) and `sceAudio3dPortAdvance` on a full queue (`NOT_READY` in shadPS4, unhandled in KytyPS5) throw too
|
||||
- [AMPR WriteAddressFromCounterPairOnCompletion](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order
|
||||
- [AMPR markers](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - signatures taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: `sceAmprCommandBufferSetMarkerWithColor` takes the color by pointer, the push and measure variants by value. Colors are not recorded
|
||||
- [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale
|
||||
|
|
|
|||
Loading…
Reference in New Issue