mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #5538 from JJenkx/fix/ngs2-render-capacity
fix(ngs2): limit render writes to the configured grain
This commit is contained in:
3 files changed
+71
-1
No files matched your search
@@ -291,6 +291,16 @@ static void WriteOutput(const Ngs2RenderBufferInfo& output, const std::vector<fl
|
||||
|
||||
void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo, std::uint32_t numBufferInfo) {
|
||||
const auto grain = system.option.num_grain_samples;
|
||||
std::vector<std::size_t> outputBytes(numBufferInfo);
|
||||
for (std::uint32_t i = 0; i < numBufferInfo; i++) {
|
||||
const auto& output = bufferInfo[i];
|
||||
if (output.buffer == nullptr) continue;
|
||||
if (output.num_channels == 0 || output.num_channels > NGS2_MAX_CHANNELS) {
|
||||
throw std::invalid_argument("NGS2: render channel count is invalid");
|
||||
}
|
||||
outputBytes[i] = static_cast<std::size_t>(grain) * output.num_channels * SampleBytes(output.waveform_type);
|
||||
if (output.buffer_size < outputBytes[i]) throw std::invalid_argument("NGS2: render buffer " + std::to_string(i) + " is too small");
|
||||
}
|
||||
std::vector<Ngs2Voice*> voices;
|
||||
for (auto* rack : system.racks) {
|
||||
for (auto& voice : rack->voices) {
|
||||
@@ -311,8 +321,8 @@ void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo
|
||||
}
|
||||
}
|
||||
for (std::uint32_t i = 0; i < numBufferInfo; i++) {
|
||||
if (bufferInfo[i].buffer != nullptr) std::memset(bufferInfo[i].buffer, 0, bufferInfo[i].buffer_size);
|
||||
if (!mixes[i].empty()) WriteOutput(bufferInfo[i], mixes[i]);
|
||||
else if (outputBytes[i] != 0) std::memset(bufferInfo[i].buffer, 0, outputBytes[i]);
|
||||
}
|
||||
for (auto* voice : voices) {
|
||||
if (voice->state == Ngs2PlayState::Stopped) voice->state = Ngs2PlayState::Empty;
|
||||
|
||||
@@ -883,6 +883,60 @@ static void TestUnsetMatrix() {
|
||||
Require(sceNgs2SystemDestroy(stereoSystem, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
template <typename TSample>
|
||||
static void TestRenderCapacity(std::uint32_t waveformType, std::uint32_t channels) {
|
||||
const auto system = CreateSystem();
|
||||
const auto samples = Grain * channels;
|
||||
const TSample sentinel = 123;
|
||||
std::vector<TSample> out(samples * 3, sentinel);
|
||||
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(TSample), waveformType, channels};
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
Require(std::all_of(out.begin(), out.begin() + samples, [](TSample value) { return value == 0; }));
|
||||
Require(std::all_of(out.begin() + samples, out.end(), [sentinel](TSample value) { return value == sentinel; }));
|
||||
|
||||
const auto master = Mastering(system, channels);
|
||||
const std::vector<std::int16_t> pcm(Grain, 16384);
|
||||
const auto sampler = Sampler(system, pcm, 0);
|
||||
Patch(sampler, master);
|
||||
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
std::fill(out.begin(), out.end(), sentinel);
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
Require(std::all_of(out.begin(), out.begin() + samples, [sentinel](TSample value) { return value > 0 && value != sentinel; }));
|
||||
Require(std::all_of(out.begin() + samples, out.end(), [sentinel](TSample value) { return value == sentinel; }));
|
||||
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
Require(std::all_of(out.begin(), out.begin() + samples, [](TSample value) { return value == 0; }));
|
||||
Require(std::all_of(out.begin() + samples, out.end(), [sentinel](TSample value) { return value == sentinel; }));
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestSilentRenderValidation() {
|
||||
const auto system = CreateSystem();
|
||||
std::vector<float> out(Grain, 123.0f);
|
||||
Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float) - 1, SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1};
|
||||
const auto invalid = [&] {
|
||||
bool rejected = false;
|
||||
try { sceNgs2SystemRender(system, &info, 1); } catch (const std::invalid_argument&) { rejected = true; }
|
||||
Require(rejected);
|
||||
Require(std::all_of(out.begin(), out.end(), [](float value) { return value == 123.0f; }));
|
||||
};
|
||||
invalid();
|
||||
info.buffer_size++;
|
||||
info.num_channels = 0;
|
||||
invalid();
|
||||
info.num_channels = 9;
|
||||
invalid();
|
||||
info.num_channels = 1;
|
||||
info.waveform_type = UINT32_MAX;
|
||||
bool unsupported = false;
|
||||
try { sceNgs2SystemRender(system, &info, 1); } catch (const std::runtime_error&) { unsupported = true; }
|
||||
Require(unsupported);
|
||||
Require(std::all_of(out.begin(), out.end(), [](float value) { return value == 123.0f; }));
|
||||
info = {};
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestAllocator() {
|
||||
const Ngs2BufferAllocator allocator{Allocate, Release, 9};
|
||||
uintptr_t system = 0;
|
||||
@@ -922,6 +976,11 @@ int main() {
|
||||
TestLock();
|
||||
TestDefaultChannelMap();
|
||||
TestUnsetMatrix();
|
||||
TestRenderCapacity<float>(SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1);
|
||||
TestRenderCapacity<float>(SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2);
|
||||
TestRenderCapacity<std::int16_t>(SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1);
|
||||
TestRenderCapacity<std::int16_t>(SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2);
|
||||
TestSilentRenderValidation();
|
||||
TestAllocator();
|
||||
exitSystem = CreateSystem();
|
||||
return 0;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
### Unknown function info
|
||||
|
||||
- [NGS2 render buffer capacity](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) is treated as available storage for one configured grain. PPSA01880 passes 303104 bytes for 256 stereo float frames (2048 bytes); rendering writes or clears only those frames and preserves the remaining capacity. Non-null silent outputs now undergo the same supported-format and minimum-size checks as audible outputs, with channel counts limited to 1 through 8. Unused null outputs remain ignored. The console's handling of surplus capacity and invalid silent output descriptors has not been measured; invalid descriptors throw rather than return guessed SDK errors.
|
||||
- [NGS2 ATRAC9 waveform loops](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) accepts one integer forward `smpl` loop. Inclusive RIFF endpoints and encoder-delay adjustment follow [vgmstream riff.c](https://github.com/vgmstream/vgmstream/blob/7dc938fa2f210943b37c7b6511852b516ef432ab/src/meta/riff.c). PPSA03747 supplies a 60-byte sampler chunk with one loop, `samplerData` 24, start 256, inclusive end 299224 and delay 256 for 298969 audible samples. The chunk size and loop count bound the records; `samplerData` is not interpreted. Output positions are assumed to use audible samples and an exclusive end. Blocks split into an optional intro, a repeated range and an optional tail; zero play count means infinite repeats, positive play count includes the first pass. ATRAC9 ranges include a preceding codec frame for decoder history. The output block layout, repeat-count mapping and error codes are unverified on hardware. Multiple, reverse, alternating and fractional loops throw.
|
||||
- [NGS2 submixer UserFx](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (control `0x20000004`) invokes the title's callback after input mixing on every playing grain, including silence. The 40-byte control and 48-byte process context follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/ngs2/ngs2_submixer.h). PPSA03747 installs a peak-meter callback; its code confirms the channel pointers, user data 0, channel count, grain size and sample-rate offsets and returns 0. Other user data words are preserved. Flags are assumed to be 1 after registration/restart and 0 thereafter, following UserFx2's first-process convention; this callback does not inspect them. Null handlers disable the effect, setup clears it, and nonzero callback results throw. These lifecycle and error semantics are unverified on console hardware.
|
||||
- [NGS2 ATRAC9 streaming](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) keeps waveform duration and skip counts across compressed input blocks. The shared `APPEND` flag (2), called `ONLY_DATA` in the compressed-data reference, ignores an ATRAC9 continuation block's sample, skip and repeat counts, following [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/bb98f26433fa9e711c219aac1711fe8ecba5bf3a/src/libs/ngs2.cpp); PPSA03747 confirms a 262044-byte initial buffer declaring 11310532 samples, followed by 16384-byte buffers with flags 3. Incomplete superframes are retained until more input arrives. Input-block completion callbacks allow refills; the final callback of a logical waveform waits for its buffered PCM to drain, preserving the existing complete-block repeat behaviour. This buffering, callback timing, decoded-byte accounting and starvation silence are unverified on hardware. A closed ATRAC9 stream ending before its declared samples throws; fragmented ATRAC9 waveforms with repeats are unsupported. PCM streaming retains its existing append validation and early-close behaviour.
|
||||
|
||||
Reference in new issue
Block a user