Files
AnyPS5/core/libs/prx/libSceNgs2.native/tests/Ngs2Geom.cpp
T
alosta b630e7897b fix(libSceNgs2): implement a minimal sceNgs2GeomApply pass-through
Implement sceNgs2GeomApply, which was previously an unimplemented
throwing stub and was blocking Dead Cells (PPSA15552).

The implementation validates null handles/output addresses and writes a
minimal Ngs2GeomAttribute: pitch_ratio 1.0, source position, source
volume/level maxima, and diagonal channel levels for the source's channel
count. It does not implement source attenuation, distance rolloff, panning
or Doppler.

The export lives in Geom.cpp's internal Ngs2GeomApply and is exposed through
the existing default-visibility wrapper in Voice.cpp. Unimplemented.cpp no
longer defines the stub. A passing test was added to Ngs2Geom.cpp.

Unblocks: PPSA15552 #<Game bring-up discussion number>
Depends on: none
AI assistance: yes
2026-10-16 21:59:08 +02:00

190 lines
7.8 KiB
C++

#include "Ngs2Test.hpp"
#include <cstdint>
#include <cstring>
#include <cmath>
#include <limits>
#include <stdexcept>
static constexpr float SoundSpeed = 343.0f;
static void TestLayouts() {
Require(sizeof(Ngs2GeomVector) == 12);
Require(sizeof(Ngs2GeomCone) == 16);
Require(sizeof(Ngs2GeomRolloff) == 16);
Require(sizeof(Ngs2GeomListenerParam) == 60);
Require(sizeof(Ngs2GeomListenerWork) == 96);
Require(sizeof(Ngs2GeomSourceParam) == 108);
Require(sizeof(Ngs2GeomA3dAttribute) == 32);
Require(sizeof(Ngs2GeomAttribute) == 308);
}
static void TestResetListenerParam() {
Ngs2GeomListenerParam param;
std::memset(&param, 0x5a, sizeof(param));
Require(sceNgs2GeomResetListenerParam(&param) == SCE_NGS2_OK);
Require(param.position.x == 0.0f && param.position.y == 0.0f && param.position.z == 0.0f);
Require(param.velocity.x == 0.0f && param.velocity.y == 0.0f && param.velocity.z == 0.0f);
Require(param.orient_front.x == 0.0f && param.orient_front.y == 0.0f && param.orient_front.z == 1.0f);
Require(param.orient_up.x == 0.0f && param.orient_up.y == 1.0f && param.orient_up.z == 0.0f);
Require(param.sound_speed == SoundSpeed);
Require(param.reserved[0] == 0u && param.reserved[1] == 0u);
Require(sceNgs2GeomResetListenerParam(nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
}
static void TestResetSourceParam() {
Ngs2GeomSourceParam param;
std::memset(&param, 0x5a, sizeof(param));
Require(sceNgs2GeomResetSourceParam(&param) == SCE_NGS2_OK);
Require(param.position.x == 0.0f && param.position.y == 0.0f && param.position.z == 0.0f);
Require(param.velocity.x == 0.0f && param.velocity.y == 0.0f && param.velocity.z == 0.0f);
Require(param.direction.x == 0.0f && param.direction.y == 0.0f && param.direction.z == 1.0f);
Require(param.cone.inner_level == 1.0f && param.cone.inner_angle == 360.0f);
Require(param.cone.outer_level == 1.0f && param.cone.outer_angle == 360.0f);
Require(param.rolloff.model == 0u);
Require(param.rolloff.max_distance == 1000000.0f);
Require(param.rolloff.rolloff_factor == 1.0f);
Require(param.rolloff.reference_distance == 1.0f);
Require(param.doppler_factor == 1.0f && param.fbw_level == 1.0f && param.lfe_level == 1.0f);
Require(param.max_level == 1.0f && param.min_level == 0.0f && param.radius == 0.0f);
Require(param.num_speakers == 2u && param.matrix_format == 2u);
Require(param.reserved[0] == 0u && param.reserved[1] == 0u);
Require(sceNgs2GeomResetSourceParam(nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
}
static Ngs2GeomListenerParam Listener() {
Ngs2GeomListenerParam param{};
Require(sceNgs2GeomResetListenerParam(&param) == SCE_NGS2_OK);
return param;
}
static void RequireMatrix(const Ngs2GeomListenerWork& work, const float (&expected)[4][4]) {
for (std::size_t row = 0; row < 4; row++) {
for (std::size_t column = 0; column < 4; column++) Require(std::abs(work.matrix[row][column] - expected[row][column]) < 0.00001f);
}
}
static void TestListenerTransform() {
auto param = Listener();
param.velocity = {2.0f, -3.0f, 4.0f};
param.sound_speed = 1500.0f;
Ngs2GeomListenerWork work;
std::memset(&work, 0x5a, sizeof(work));
Require(sceNgs2GeomCalcListener(&param, &work, 0) == SCE_NGS2_OK);
RequireMatrix(work, {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}});
Require(work.velocity.x == 2.0f && work.velocity.y == -3.0f && work.velocity.z == 4.0f);
Require(work.sound_speed == 1500.0f && work.coordinate == 0);
Require(work.reserved[0] == 0 && work.reserved[1] == 0 && work.reserved[2] == 0);
param.position = {3.0f, 4.0f, 5.0f};
Require(sceNgs2GeomCalcListener(&param, &work, 0) == SCE_NGS2_OK);
RequireMatrix(work, {{1, 0, 0, -3}, {0, 1, 0, -4}, {0, 0, 1, -5}, {0, 0, 0, 1}});
param.orient_front = {2.0f, 0.0f, 0.0f};
param.orient_up = {1.0f, 3.0f, 0.0f};
Require(sceNgs2GeomCalcListener(&param, &work, 0) == SCE_NGS2_OK);
RequireMatrix(work, {{0, 0, -1, 5}, {0, 1, 0, -4}, {1, 0, 0, -3}, {0, 0, 0, 1}});
param.orient_front = {0.0f, 0.0f, -2.0f};
param.orient_up = {0.0f, 3.0f, 0.0f};
Require(sceNgs2GeomCalcListener(&param, &work, 1) == SCE_NGS2_OK);
RequireMatrix(work, {{1, 0, 0, -3}, {0, 1, 0, -4}, {0, 0, -1, 5}, {0, 0, 0, 1}});
Require(work.coordinate == 1);
param.orient_front = {0.0f, 0.0f, std::numeric_limits<float>::max()};
param.orient_up = {0.0f, std::numeric_limits<float>::min(), 0.0f};
Require(sceNgs2GeomCalcListener(&param, &work, 0) == SCE_NGS2_OK);
RequireMatrix(work, {{1, 0, 0, -3}, {0, 1, 0, -4}, {0, 0, 1, -5}, {0, 0, 0, 1}});
}
static void RejectListener(const Ngs2GeomListenerParam* param, std::uint32_t flags) {
Ngs2GeomListenerWork work;
std::memset(&work, 0x5a, sizeof(work));
unsigned char before[sizeof(work)];
std::memcpy(before, &work, sizeof(work));
bool rejected = false;
try {
sceNgs2GeomCalcListener(param, &work, flags);
} catch (const std::exception&) {
rejected = true;
}
Require(rejected);
Require(std::memcmp(before, &work, sizeof(work)) == 0);
}
static void TestInvalidListener() {
auto param = Listener();
Require(sceNgs2GeomCalcListener(&param, nullptr, 0) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
RejectListener(nullptr, 0);
RejectListener(&param, 2);
RejectListener(&param, 0xffffffffu);
for (const float value : {0.0f, -1.0f, std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
param.sound_speed = value;
RejectListener(&param, 0);
}
param = Listener();
param.orient_front = {};
RejectListener(&param, 0);
param = Listener();
param.orient_up = param.orient_front;
RejectListener(&param, 0);
param = Listener();
param.orient_up = {};
RejectListener(&param, 0);
param = Listener();
param.position.x = std::numeric_limits<float>::infinity();
RejectListener(&param, 0);
param = Listener();
param.velocity.z = std::numeric_limits<float>::quiet_NaN();
RejectListener(&param, 0);
param = Listener();
param.orient_front.y = std::numeric_limits<float>::quiet_NaN();
RejectListener(&param, 0);
}
static void TestGeomApply() {
Ngs2GeomListenerWork listener{};
for (std::size_t row = 0; row < 4; row++) {
for (std::size_t column = 0; column < 4; column++) listener.matrix[row][column] = row == column ? 1.0f : 0.0f;
}
Ngs2GeomSourceParam source;
Require(sceNgs2GeomResetSourceParam(&source) == SCE_NGS2_OK);
source.position = {1.0f, 2.0f, 3.0f};
source.min_level = -0.25f;
source.max_level = 0.5f;
source.matrix_format = 3;
Ngs2GeomAttribute attribute;
std::memset(&attribute, 0x5a, sizeof(attribute));
Require(sceNgs2GeomApply(&listener, &source, &attribute, 0) == SCE_NGS2_OK);
Require(attribute.pitch_ratio == 1.0f);
Require(attribute.a3d_attrib.position.x == 1.0f && attribute.a3d_attrib.position.y == 2.0f && attribute.a3d_attrib.position.z == 3.0f);
Require(attribute.a3d_attrib.volume == 0.5f);
Require(attribute.level[0] == 0.5f);
Require(attribute.level[1] == 0.0f);
Require(attribute.level[9] == 0.5f);
Require(attribute.level[18] == 0.5f);
Require(attribute.level[24] == 0.0f);
Require(attribute.level[63] == 0.0f);
Require(sceNgs2GeomApply(&listener, &source, nullptr, 0) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
source.matrix_format = 0;
std::memset(&attribute, 0x5a, sizeof(attribute));
Require(sceNgs2GeomApply(&listener, &source, &attribute, 0) == SCE_NGS2_OK);
Require(attribute.level[0] == 0.5f);
Require(attribute.level[9] == 0.5f);
Require(attribute.level[16] == 0.0f);
}
int main() {
TestLayouts();
TestResetListenerParam();
TestResetSourceParam();
TestListenerTransform();
TestInvalidListener();
TestGeomApply();
return 0;
}