diff --git a/core/libs/prx/libSceAcm/Export.cpp b/core/libs/prx/libSceAcm/Export.cpp index ddbe893628..951db95cd6 100644 --- a/core/libs/prx/libSceAcm/Export.cpp +++ b/core/libs/prx/libSceAcm/Export.cpp @@ -18,6 +18,84 @@ std::mutex g_mutex; std::unordered_set g_contexts; std::atomic g_nextId{1}; +int EncodeTransforms(AcmBatchInfo* batch, bool inverse, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags) { + const std::int32_t conflictingFlags = inverse ? 5 : 3; + if (!batch || transformCount == 0 || transformCount > 0x7FFFFFFF || !inputs || !outputs || (flags & conflictingFlags) == conflictingFlags || inputFormat < 0 || inputFormat > 1 || outputFormat < 0 || outputFormat > 1) return SCE_ACM_ERROR_BAD_POINTER; + if (transformSize != 256 && transformSize != 512 && transformSize != 1024 && transformSize != 2048) return SCE_ACM_ERROR_BAD_POINTER; + const bool real = (flags & 1) != 0; + const bool wide = transformSize == 2048; + if (wide && !inverse && !real) return SCE_ACM_ERROR_BAD_POINTER; + const std::uint32_t sizeIndex = transformSize == 256 ? 0 : transformSize == 512 ? 1 : transformSize == 1024 ? 2 : 3; + const std::uint32_t inputBytes = transformSize * (inputFormat == 1 ? 2 : 4); + const std::uint32_t outputBytes = transformSize * (outputFormat == 1 ? 2 : 4); + const std::uint64_t inputBlocks = (inputBytes >> 8) >> (!inverse && real ? 1 : 0); + const std::uint64_t outputBlocks = outputBytes >> 8; + std::uint32_t inputCommand; + std::uint32_t transformCommand; + std::uint32_t outputCommand; + if (wide) { + inputCommand = 2 * inputFormat + (inverse ? 1 : 0); + transformCommand = inverse ? 7 : 6; + outputCommand = 4 | outputFormat; + } else if (inverse) { + inputCommand = 4 | inputFormat; + transformCommand = 0xC; + outputCommand = 6 | outputFormat; + } else { + inputCommand = (real ? 0 : 2) | inputFormat; + transformCommand = real ? 0xA : 0xB; + outputCommand = 8 | outputFormat; + } + std::uint64_t header = 0; + if (!(flags & 8)) { + const auto encodedCount = transformCount < 64000 ? transformCount : 64000; + header = (inverse ? 0 : std::uint64_t{1} << 16) | (static_cast(flags & 6) << 20) | (real ? std::uint64_t{1} << (inverse ? 23 : 24) : 0) | (std::uint64_t{1} << (inputFormat == 1 ? 25 : 27)) | (std::uint64_t{1} << (outputFormat == 1 ? 26 : 28)) | (std::uint64_t{1} << (17 + sizeIndex)) | (static_cast(encodedCount) << 29); + } + header |= static_cast(7 + sizeIndex) << 8; + auto* buffer = static_cast(batch->buffer); + auto offset = batch->offset; + const auto capacity = batch->buffer_size; + const auto hasRoom = [&](std::uint64_t bytes) { return offset <= capacity && capacity - offset >= bytes; }; + const auto appendWord = [&](std::uint64_t value) { + std::memcpy(buffer + offset, &value, sizeof(value)); + offset += sizeof(value); + }; + constexpr int noSpace = static_cast(0x81940009); + constexpr int badAlignment = static_cast(0x8194000A); + if (!hasRoom(16)) return noSpace; + appendWord(header); + appendWord(10 | ((wide ? 8u : 0xDu) << 8)); + const bool pairedInputs = (flags & 2) != 0; + const bool pairedOutputs = (flags & 4) != 0; + const bool realOutput = inverse && real; + for (std::uint32_t index = 0; index < transformCount; ++index) { + const auto inputIndex = static_cast(index) * (pairedInputs ? 2 : 1); + const auto input = reinterpret_cast(inputs[inputIndex]); + const auto secondInput = pairedInputs ? reinterpret_cast(inputs[inputIndex + 1]) : 0; + if (!pairedInputs && !input) return SCE_ACM_ERROR_BAD_POINTER; + if ((input | secondInput) & 127) return badAlignment; + if (!hasRoom((pairedInputs ? 24 : 16) + 24)) return noSpace; + appendWord((pairedInputs ? 7 : 6) | (inputBlocks << 32) | (pairedInputs ? static_cast(inputBytes / 2) << 48 : 0)); + appendWord(input); + if (pairedInputs) appendWord(secondInput); + appendWord(10 | (inputCommand << 8)); + appendWord(10 | (transformCommand << 8)); + appendWord(10 | (outputCommand << 8)); + const auto outputIndex = static_cast(index) * (pairedOutputs ? 2 : 1); + const auto output = reinterpret_cast(outputs[outputIndex]); + const auto secondOutput = pairedOutputs ? reinterpret_cast(outputs[outputIndex + 1]) : 0; + if ((output | secondOutput) & 127) return badAlignment; + const bool longRecord = pairedOutputs || realOutput; + if (!hasRoom(longRecord ? 24 : 16)) return noSpace; + appendWord((longRecord ? 9 : 8) | (outputBlocks << 32) | (longRecord ? static_cast(outputBytes / 2) << 48 : 0)); + if (realOutput) appendWord(0); + appendWord(output); + if (pairedOutputs) appendWord(secondOutput); + } + batch->offset = offset; + return 0; +} + } extern "C" { @@ -85,79 +163,7 @@ int APS5_VABI sceAcm_ConvReverb_SharedInput(AcmBatchInfo* batch, std::uint32_t m } int APS5_VABI sceAcm_FFT(AcmBatchInfo* batch, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags) { - if (!batch || transformCount == 0 || transformCount > 0x7FFFFFFF || !inputs || !outputs || (flags & 3) == 3 || inputFormat < 0 || inputFormat > 1 || outputFormat < 0 || outputFormat > 1) return SCE_ACM_ERROR_BAD_POINTER; - if (transformSize != 512 && transformSize != 1024 && transformSize != 2048) return SCE_ACM_ERROR_BAD_POINTER; - const bool realInput = (flags & 1) != 0; - if (transformSize == 2048 && !realInput) return SCE_ACM_ERROR_BAD_POINTER; - const std::uint32_t inputBytes = transformSize * (inputFormat == 1 ? 2 : 4); - const std::uint32_t outputBytes = transformSize * (outputFormat == 1 ? 2 : 4); - const std::uint32_t inputBlocks = (inputBytes >> 9) >> (realInput ? 1 : 0); - const std::uint32_t outputBlocks = outputBytes >> 9; - std::uint32_t transformCommand; - std::uint32_t inputCommand; - std::uint32_t outputCommand; - std::uint32_t setupCommand; - std::uint32_t sizeCode; - if (transformSize == 512) { - sizeCode = 4; - transformCommand = realInput ? 7 : 11; - inputCommand = realInput ? 2 * inputFormat : 9 + inputFormat; - outputCommand = 4 | outputFormat; - setupCommand = realInput ? 6 : 12; - } else if (transformSize == 1024) { - sizeCode = 5; - transformCommand = realInput ? 8 : 13; - inputCommand = realInput ? 2 * inputFormat : 14 | inputFormat; - outputCommand = 5 | (2 * outputFormat); - setupCommand = realInput ? 10 : 12; - } else { - sizeCode = 6; - transformCommand = 6; - inputCommand = 2 * inputFormat; - outputCommand = 4 | outputFormat; - setupCommand = 8; - } - auto* buffer = static_cast(batch->buffer); - auto offset = batch->offset; - const auto capacity = batch->buffer_size; - const auto appendWord = [&](std::uint64_t value) { - if (offset > capacity || capacity - offset < sizeof(value)) return false; - std::memcpy(buffer + offset, &value, sizeof(value)); - offset += sizeof(value); - return true; - }; - std::uint64_t header = static_cast(sizeCode) << 8; - if (!(flags & 8)) { - const auto encodedCount = transformCount < 64000 ? transformCount : 64000; - header |= (std::uint64_t{1} << 16) | (static_cast(transformSize) << 8) | (static_cast(flags & 6) << 19) | (static_cast(flags & 1) << 23) | (std::uint64_t{1} << (inputFormat == 1 ? 24 : 26)) | (std::uint64_t{1} << (outputFormat == 1 ? 25 : 27)) | (static_cast(encodedCount) << 28); - } - constexpr int noSpace = static_cast(0x81940009); - constexpr int badAlignment = static_cast(0x8194000A); - if (!appendWord(header) || !appendWord(10 | (setupCommand << 8))) return noSpace; - for (std::uint32_t index = 0; index < transformCount; ++index) { - const auto inputIndex = static_cast(index) * ((flags & 2) ? 2 : 1); - const auto input = reinterpret_cast(inputs[inputIndex]); - const auto secondInput = (flags & 2) ? reinterpret_cast(inputs[inputIndex + 1]) : 0; - if (!(flags & 2) && !input) return SCE_ACM_ERROR_BAD_POINTER; - if ((input | secondInput) & 127) return badAlignment; - const auto inputRecordSize = (flags & 2) ? 24u : 16u; - if (offset > capacity || capacity - offset < inputRecordSize) return noSpace; - appendWord(((flags & 2) ? 7 : 6) | (static_cast(inputBlocks) << 32) | ((flags & 2) ? static_cast(inputBytes / 2) << 48 : 0)); - appendWord(input); - if (flags & 2) appendWord(secondInput); - if (!appendWord(10 | (inputCommand << 8)) || !appendWord(10 | (transformCommand << 8)) || !appendWord(10 | (outputCommand << 8))) return noSpace; - const auto outputIndex = static_cast(index) * ((flags & 4) ? 2 : 1); - const auto output = reinterpret_cast(outputs[outputIndex]); - const auto secondOutput = (flags & 4) ? reinterpret_cast(outputs[outputIndex + 1]) : 0; - if ((output | secondOutput) & 127) return badAlignment; - const auto outputRecordSize = (flags & 4) ? 24u : 16u; - if (offset > capacity || capacity - offset < outputRecordSize) return noSpace; - appendWord(((flags & 4) ? 9 : 8) | (static_cast(outputBlocks) << 32) | ((flags & 4) ? static_cast(outputBytes / 2) << 48 : 0)); - appendWord(output); - if (flags & 4) appendWord(secondOutput); - } - batch->offset = offset; - return 0; + return EncodeTransforms(batch, false, transformSize, transformCount, inputFormat, inputs, outputFormat, outputs, flags); } int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, const void* const* sources, std::uint32_t sourceMode, std::uint32_t outputMode, std::uint32_t channelCount, const void* const* sourceParameters, const void* const* sourceMatrices, const void* const* inputChannels, void* const* outputChannels) { @@ -213,9 +219,8 @@ int APS5_VABI sceAcm_ConvReverb_SharedIr() { return 0; } -int APS5_VABI sceAcm_IFFT() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAcm_IFFT(AcmBatchInfo* batch, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags) { + return EncodeTransforms(batch, true, transformSize, transformCount, inputFormat, inputs, outputFormat, outputs, flags); } int APS5_VABI sceAcmBatchJobNotification() { diff --git a/core/libs/prx/libSceAcm/include/Api.hpp b/core/libs/prx/libSceAcm/include/Api.hpp index c4da065395..626f27bc4b 100644 --- a/core/libs/prx/libSceAcm/include/Api.hpp +++ b/core/libs/prx/libSceAcm/include/Api.hpp @@ -14,7 +14,7 @@ int APS5_VABI sceAcm_FFT(AcmBatchInfo* batch, std::int32_t transformSize, std::u int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, const void* const* sources, std::uint32_t sourceMode, std::uint32_t outputMode, std::uint32_t channelCount, const void* const* sourceParameters, const void* const* sourceMatrices, const void* const* inputChannels, void* const* outputChannels); int APS5_VABI sceAcmBatchJobPriority(AcmBatchInfo* batch, std::uint32_t priority); int APS5_VABI sceAcm_ConvReverb_SharedIr(); -int APS5_VABI sceAcm_IFFT(); +int APS5_VABI sceAcm_IFFT(AcmBatchInfo* batch, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags); int APS5_VABI sceAcmBatchJobNotification(); } diff --git a/core/libs/tests/GuestAcm.cpp b/core/libs/tests/GuestAcm.cpp index 0ef10e0adf..6e272f5a1f 100644 --- a/core/libs/tests/GuestAcm.cpp +++ b/core/libs/tests/GuestAcm.cpp @@ -71,7 +71,7 @@ int main() { for (std::size_t index = 8; index < 16; ++index) Require(descriptor[index] == 0); commandBatch = {commands, 0, sizeof(commands)}; Require(sceAcm_FFT(&commandBatch, 512, 0, 0, sources, 0, outputs, 0) == BadPointer); - Require(sceAcm_FFT(&commandBatch, 256, 1, 0, sources, 0, outputs, 0) == BadPointer); + Require(sceAcm_FFT(&commandBatch, 768, 1, 0, sources, 0, outputs, 0) == BadPointer); Require(sceAcm_FFT(&commandBatch, 4096, 1, 0, sources, 0, outputs, 0) == BadPointer); Require(sceAcm_FFT(&commandBatch, 512, 1, 2, sources, 0, outputs, 0) == BadPointer); Require(sceAcm_FFT(&commandBatch, 512, 1, 0, sources, 2, outputs, 0) == BadPointer); @@ -83,20 +83,40 @@ int main() { Require(commandBatch.offset == 72 && commandBatch.buffer_size == sizeof(commands)); std::uint64_t fftWords[9]; std::memcpy(fftWords, commands, sizeof(fftWords)); - Require(fftWords[0] == 0x1C030400 && fftWords[1] == 0xC0A); - Require(fftWords[2] == 0x400000006 && fftWords[3] == reinterpret_cast(sourceData)); - Require(fftWords[4] == 0x90A && fftWords[5] == 0xB0A && fftWords[6] == 0x40A); - Require(fftWords[7] == 0x400000008 && fftWords[8] == reinterpret_cast(sourceData)); + Require(fftWords[0] == 0x38050800 && fftWords[1] == 0xD0A); + Require(fftWords[2] == 0x800000006 && fftWords[3] == reinterpret_cast(sourceData)); + Require(fftWords[4] == 0x20A && fftWords[5] == 0xB0A && fftWords[6] == 0x80A); + Require(fftWords[7] == 0x800000008 && fftWords[8] == reinterpret_cast(sourceData)); commandBatch = {commands, 0, 71}; Require(sceAcm_FFT(&commandBatch, 512, 1, 0, sources, 0, outputs, 0) == static_cast(0x81940009)); Require(commandBatch.offset == 0); commandBatch = {commands, 0, sizeof(commands)}; Require(sceAcm_FFT(&commandBatch, 2048, 1, 1, sources, 1, outputs, 9) == 0); std::memcpy(fftWords, commands, sizeof(fftWords)); - Require(fftWords[0] == 0x600 && fftWords[1] == 0x80A && fftWords[4] == 0x20A && fftWords[5] == 0x60A && fftWords[6] == 0x50A); + Require(fftWords[0] == 0xA00 && fftWords[1] == 0x80A && fftWords[2] == 0x800000006 && fftWords[4] == 0x20A && fftWords[5] == 0x60A && fftWords[6] == 0x50A); + commandBatch = {commands, 0, sizeof(commands)}; + Require(sceAcm_FFT(&commandBatch, 256, 1, 0, sources, 0, outputs, 1) == 0 && commandBatch.offset == 72); + std::memcpy(fftWords, commands, sizeof(fftWords)); + Require(fftWords[0] == 0x39030700 && fftWords[1] == 0xD0A && fftWords[2] == 0x200000006); + Require(fftWords[4] == 0xA && fftWords[5] == 0xA0A && fftWords[6] == 0x80A && fftWords[7] == 0x400000008); + std::uint64_t ifftWords[10]; + commandBatch = {commands, 0, sizeof(commands)}; + Require(sceAcm_IFFT(nullptr, 1024, 1, 1, sources, 0, outputs, 1) == BadPointer); + Require(sceAcm_IFFT(&commandBatch, 1024, 1, 1, sources, 0, outputs, 5) == BadPointer); + Require(sceAcm_IFFT(&commandBatch, 1024, 1, 1, sources, 0, outputs, 3) == 0 && commandBatch.offset == 88); + commandBatch = {commands, 0, sizeof(commands)}; + Require(sceAcm_IFFT(&commandBatch, 1024, 1, 1, sources, 0, outputs, 1) == 0 && commandBatch.offset == 80); + std::memcpy(ifftWords, commands, sizeof(ifftWords)); + Require(ifftWords[0] == 0x32880900 && ifftWords[1] == 0xD0A && ifftWords[2] == 0x800000006 && ifftWords[3] == reinterpret_cast(sourceData)); + Require(ifftWords[4] == 0x50A && ifftWords[5] == 0xC0A && ifftWords[6] == 0x60A); + Require(ifftWords[7] == 0x0800001000000009 && ifftWords[8] == 0 && ifftWords[9] == reinterpret_cast(sourceData)); + commandBatch = {commands, 0, sizeof(commands)}; + Require(sceAcm_IFFT(&commandBatch, 2048, 1, 0, sources, 1, outputs, 0) == 0 && commandBatch.offset == 72); + std::memcpy(ifftWords, commands, sizeof(ifftWords)); + Require(ifftWords[0] == 0x2C100A00 && ifftWords[1] == 0x80A && ifftWords[4] == 0x10A && ifftWords[5] == 0x70A && ifftWords[6] == 0x50A); sources[1] = sourceData; outputs[1] = sourceData; - for (const auto size : {512, 1024, 2048}) { + for (const auto size : {256, 512, 1024, 2048}) { for (std::int32_t inputFormat = 0; inputFormat < 2; ++inputFormat) { for (std::int32_t outputFormat = 0; outputFormat < 2; ++outputFormat) { for (std::int32_t flags = 0; flags < 16; ++flags) { @@ -107,6 +127,13 @@ int main() { } else { Require(result == 0 && commandBatch.offset == 72 + ((flags & 2) ? 8 : 0) + ((flags & 4) ? 8 : 0)); } + commandBatch = {commands, 0, sizeof(commands)}; + const auto inverseResult = sceAcm_IFFT(&commandBatch, size, 1, inputFormat, sources, outputFormat, outputs, flags); + if ((flags & 5) == 5) { + Require(inverseResult == BadPointer && commandBatch.offset == 0); + } else { + Require(inverseResult == 0 && commandBatch.offset == 72 + ((flags & 2) ? 8 : 0) + ((flags & 5) ? 8 : 0)); + } } } } diff --git a/docs/dev/debt/libSceAcm.md b/docs/dev/debt/libSceAcm.md index c94c8ce717..595a348ffb 100644 --- a/docs/dev/debt/libSceAcm.md +++ b/docs/dev/debt/libSceAcm.md @@ -7,7 +7,8 @@ ### Unknown function info - [libSceAcm](../../../core/libs/prx/libSceAcm/Export.cpp) context creation, batch submission and waiting use host identifiers instead of the native device and ioctl requests. Batch identifiers, timeout handling, DSP scheduling and service-side errors remain unimplemented. Panner source parameter and matrix payloads are forwarded by the native library; their DSP interpretation remains unverified. -- [sceAcm_ConvReverb_SharedIr, sceAcm_IFFT](../../../core/libs/prx/libSceAcm/Export.cpp) (libSceAcm) - their names and NIDs are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAcm.c), but no credible prototypes were found. They are declared without parameters solely to resolve known imports; this is not an ABI claim +- [sceAcm_ConvReverb_SharedIr](../../../core/libs/prx/libSceAcm/Export.cpp) (libSceAcm) - its name and NID are in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAcm.c), but no credible prototype was found. It is declared without parameters solely to resolve known imports; this is not an ABI claim +- [`sceAcm_FFT` and `sceAcm_IFFT`](../../../core/libs/prx/libSceAcm/Export.cpp) encode their jobs as `libSceAcm.sprx` does on firmware 13.40, where both exports call one encoder with a direction flag (read from the module's code and compared with that code run on x86-64, not measured on a console). PPSA14632 imports `sceAcm_FFT`; no known title imports `sceAcm_IFFT`. The DSP meaning of the job words is unverified. - [`sceAcmBatchJobNotification`](../../../core/libs/prx/libSceAcm/Export.cpp) is a parameterless pending declaration for import resolution. Its signature and behavior are unverified; it throws through `NotImplemented_nid_no_patch`.