From b52632c9246922295a61ae1e45fb6983bf6c6ee7 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Sun, 3 Nov 2024 19:23:53 +0300 Subject: [PATCH] refactor half ajm --- CMakeLists.txt | 23 +- src/common/indexed_resources.h | 56 -- src/common/slot_array.h | 63 +++ src/core/libraries/ajm/ajm.cpp | 676 ++----------------------- src/core/libraries/ajm/ajm.h | 88 +++- src/core/libraries/ajm/ajm_at9.cpp | 2 +- src/core/libraries/ajm/ajm_at9.h | 2 +- src/core/libraries/ajm/ajm_batch.cpp | 461 +++++++++++++++++ src/core/libraries/ajm/ajm_batch.h | 74 +++ src/core/libraries/ajm/ajm_context.cpp | 230 +++++++++ src/core/libraries/ajm/ajm_context.h | 56 ++ src/core/libraries/ajm/ajm_instance.h | 84 +-- src/core/libraries/ajm/ajm_mp3.cpp | 2 +- src/core/libraries/ajm/ajm_mp3.h | 2 +- 14 files changed, 1006 insertions(+), 813 deletions(-) delete mode 100644 src/common/indexed_resources.h create mode 100644 src/common/slot_array.h create mode 100644 src/core/libraries/ajm/ajm_batch.cpp create mode 100644 src/core/libraries/ajm/ajm_batch.h create mode 100644 src/core/libraries/ajm/ajm_context.cpp create mode 100644 src/core/libraries/ajm/ajm_context.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c34ed6b1f..718706fea 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,18 +179,24 @@ set(AUDIO_CORE src/audio_core/sdl_audio.cpp src/audio_core/sdl_audio.h ) +set(AJM_LIB src/core/libraries/ajm/ajm.cpp + src/core/libraries/ajm/ajm.h + src/core/libraries/ajm/ajm_at9.cpp + src/core/libraries/ajm/ajm_at9.h + src/core/libraries/ajm/ajm_batch.cpp + src/core/libraries/ajm/ajm_batch.h + src/core/libraries/ajm/ajm_context.cpp + src/core/libraries/ajm/ajm_context.h + src/core/libraries/ajm/ajm_error.h + src/core/libraries/ajm/ajm_instance.h + src/core/libraries/ajm/ajm_mp3.cpp + src/core/libraries/ajm/ajm_mp3.h +) + set(AUDIO_LIB src/core/libraries/audio/audioin.cpp src/core/libraries/audio/audioin.h src/core/libraries/audio/audioout.cpp src/core/libraries/audio/audioout.h - src/core/libraries/ajm/ajm.cpp - src/core/libraries/ajm/ajm.h - src/core/libraries/ajm/ajm_instance.h - src/core/libraries/ajm/ajm_error.h - src/core/libraries/ajm/ajm_mp3.cpp - src/core/libraries/ajm/ajm_mp3.h - src/core/libraries/ajm/ajm_at9.cpp - src/core/libraries/ajm/ajm_at9.h src/core/libraries/ngs2/ngs2.cpp src/core/libraries/ngs2/ngs2.h ) @@ -499,6 +505,7 @@ set(CORE src/core/aerolib/stubs.cpp src/core/libraries/error_codes.h src/core/libraries/libs.h src/core/libraries/libs.cpp + ${AJM_LIB} ${AUDIO_LIB} ${GNM_LIB} ${KERNEL_LIB} diff --git a/src/common/indexed_resources.h b/src/common/indexed_resources.h deleted file mode 100644 index 4f3fefb45..000000000 --- a/src/common/indexed_resources.h +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include -#include -#include -#include - -#include - -namespace Common { - -template ::max()> -class IndexedResources { -public: - IndexedResources() { - m_free_indices += boost::icl::interval::closed(0, MaxIndex); - } - - template - std::optional Create(Types&&... args) { - std::unique_lock lock{m_mutex}; - if (m_free_indices.empty()) { - return {}; - } - auto index = first(*m_free_indices.begin()); - m_free_indices -= index; - m_container.emplace(index, T(std::forward(args)...)); - return index; - } - - void Destroy(Index index) { - std::unique_lock lock{m_mutex}; - if (m_container.erase(index) > 0) { - m_free_indices += index; - } - } - - std::optional> Get(Index index) { - std::shared_lock lock{m_mutex}; - auto it = m_container.find(index); - if (it == m_container.end()) { - return {}; - } - return it->second; - } - -private: - std::shared_mutex m_mutex; - std::unordered_map m_container; - boost::icl::interval_set m_free_indices; -}; - -} // namespace Common diff --git a/src/common/slot_array.h b/src/common/slot_array.h new file mode 100644 index 000000000..3a57899c2 --- /dev/null +++ b/src/common/slot_array.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// #include + +#include +#include +#include +#include + +#include +#include + +namespace Common { + +template ::max(), IndexType MinIndex = 0> +class SlotArray { +public: + SlotArray() { + std::iota(m_free_indices.begin(), m_free_indices.end(), MinIndex); + } + + template + std::optional Create(Types&&... args) { + if (!HasFreeSlots()) { + return std::nullopt; + } + const auto index = m_free_indices[m_curr_cursor]; + m_resources[index - MinIndex] = ResourceType(std::forward(args)...); + m_curr_cursor += 1; + return index; + } + + bool Destroy(IndexType index) { + if (!m_resources[index - MinIndex].has_value()) { + return false; + } + m_curr_cursor -= 1; + m_free_indices[m_curr_cursor] = index; + m_resources[index - MinIndex] = std::nullopt; + return true; + } + + ResourceType* Get(IndexType index) { + auto& resource = m_resources[index - MinIndex]; + if (!resource.has_value()) { + return nullptr; + } + return &resource.value(); + } + + bool HasFreeSlots() { + return m_curr_cursor < m_free_indices.size(); + } + +private: + size_t m_curr_cursor = 0; + std::array m_free_indices; + std::array, MaxIndex - MinIndex> m_resources; +}; + +} // namespace Common diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index b29ed7203..ebab85061 100644 --- a/src/core/libraries/ajm/ajm.cpp +++ b/src/core/libraries/ajm/ajm.cpp @@ -1,202 +1,23 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/indexed_resources.h" #include "common/logging/log.h" -#include "common/scope_exit.h" #include "core/libraries/ajm/ajm.h" -#include "core/libraries/ajm/ajm_at9.h" +#include "core/libraries/ajm/ajm_context.h" #include "core/libraries/ajm/ajm_error.h" -#include "core/libraries/ajm/ajm_instance.h" #include "core/libraries/ajm/ajm_mp3.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" -extern "C" { -#include -#include -#include -#include -#include -} +#include namespace Libraries::Ajm { -static constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000; - -static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1; - -static constexpr u32 MaxInstances = 0x2fff; - -static constexpr u32 MaxBatches = 1000; - -struct AjmJob { - u32 instance_id = 0; - AjmJobFlags flags = {.raw = 0}; - AjmJobInput input; - AjmJobOutput output; -}; - -struct BatchInfo { - u32 id; - std::atomic_bool waiting{}; - std::atomic_bool canceled{}; - std::binary_semaphore finished{0}; - boost::container::small_vector jobs; -}; - -template -ChunkType& AjmBufferExtract(u8*& p_cursor) { - auto* const result = reinterpret_cast(p_cursor); - p_cursor += sizeof(ChunkType); - return *result; -} - -template -void AjmBufferSkip(u8*& p_cursor) { - p_cursor += sizeof(ChunkType); -} - -template -ChunkType& AjmBufferPeek(u8* p_cursor) { - return *reinterpret_cast(p_cursor); -} - -struct AjmDevice { - u32 max_prio{}; - u32 min_prio{}; - u32 curr_cursor{}; - u32 release_cursor{MaxInstances - 1}; - std::array is_registered{}; - std::array free_instances{}; - std::array, MaxInstances> instances; - Common::IndexedResources, MaxBatches> batches; - std::mutex batches_mutex; - - std::jthread worker_thread{}; - std::queue> batch_queue{}; - std::mutex batch_queue_mutex{}; - std::mutex batch_queue_cv_mutex{}; - std::condition_variable_any batch_queue_cv{}; - - [[nodiscard]] bool IsRegistered(AjmCodecType type) const { - return is_registered[static_cast(type)]; - } - - void Register(AjmCodecType type) { - is_registered[static_cast(type)] = true; - } - - AjmDevice() { - std::iota(free_instances.begin(), free_instances.end(), 1); - worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); }); - } - - void WorkerThread(std::stop_token stop) { - while (!stop.stop_requested()) { - { - std::unique_lock lock(batch_queue_cv_mutex); - if (!batch_queue_cv.wait(lock, stop, [this] { return !batch_queue.empty(); })) { - continue; - } - } - - std::shared_ptr batch; - { - std::lock_guard lock(batch_queue_mutex); - batch = batch_queue.front(); - batch_queue.pop(); - } - ProcessBatch(batch->id, batch->jobs); - batch->finished.release(); - } - } - - void ProcessBatch(u32 id, std::span jobs) { - // Perform operation requested by control flags. - for (auto& job : jobs) { - LOG_DEBUG(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, - job.instance_id, job.flags.raw); - - AjmInstance* p_instance = instances[job.instance_id].get(); - - const auto control_flags = job.flags.control_flags; - if (True(control_flags & AjmJobControlFlags::Reset)) { - LOG_INFO(Lib_Ajm, "Resetting instance {}", job.instance_id); - p_instance->Reset(); - } - if (True(control_flags & AjmJobControlFlags::Initialize)) { - LOG_INFO(Lib_Ajm, "Initializing instance {}", job.instance_id); - ASSERT_MSG(job.input.init_params.has_value(), - "Initialize called without control buffer"); - auto& params = job.input.init_params.value(); - p_instance->Initialize(¶ms, sizeof(params)); - } - if (True(control_flags & AjmJobControlFlags::Resample)) { - LOG_ERROR(Lib_Ajm, "Unimplemented: resample params"); - ASSERT_MSG(job.input.resample_parameters.has_value(), - "Resample paramters are absent"); - p_instance->resample_parameters = job.input.resample_parameters.value(); - } - - const auto sideband_flags = job.flags.sideband_flags; - if (True(sideband_flags & AjmJobSidebandFlags::Format)) { - ASSERT_MSG(job.input.format.has_value(), "Format parameters are absent"); - p_instance->format = job.input.format.value(); - } - if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode)) { - ASSERT_MSG(job.input.gapless_decode.has_value(), - "Gapless decode parameters are absent"); - auto& params = job.input.gapless_decode.value(); - p_instance->gapless.total_samples = params.total_samples; - p_instance->gapless.skip_samples = params.skip_samples; - } - - if (!job.input.buffer.empty()) { - p_instance->Decode(&job.input, &job.output); - } - - if (job.output.p_gapless_decode != nullptr) { - *job.output.p_gapless_decode = p_instance->gapless; - } - - if (job.output.p_codec_info != nullptr) { - p_instance->GetCodecInfo(job.output.p_codec_info); - } - } - } -}; - -static std::unique_ptr dev{}; +static std::unique_ptr context{}; int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id) { - std::shared_ptr batch{}; - { - std::lock_guard guard(dev->batches_mutex); - const auto opt_batch = dev->batches.Get(batch_id); - if (!opt_batch.has_value()) { - return ORBIS_AJM_ERROR_INVALID_BATCH; - } - - batch = opt_batch.value().get(); - } - - batch->canceled = true; - - return ORBIS_OK; + LOG_INFO(Lib_Ajm, "called context_id = {} batch_id = {}", context_id, batch_id); + return context->BatchCancel(batch_id); } int PS4_SYSV_ABI sceAjmBatchErrorDump() { @@ -209,78 +30,15 @@ void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id void* p_sideband_output, size_t sideband_output_size, void* p_return_address) { - LOG_TRACE(Lib_Ajm, "called"); - - u8* p_current = (u8*)p_buffer; - - auto& header = AjmBufferExtract(p_current); - header.ident = AjmIdentJob; - header.payload = instance_id; - - const u8* const p_begin = p_current; - - if (p_return_address != nullptr) { - auto& chunk_ra = AjmBufferExtract(p_current); - chunk_ra.header.ident = AjmIdentReturnAddressBuf; - chunk_ra.header.payload = 0; - chunk_ra.header.size = 0; - chunk_ra.p_address = p_return_address; - } - - { - auto& chunk_input = AjmBufferExtract(p_current); - chunk_input.header.ident = AjmIdentInputControlBuf; - chunk_input.header.payload = 0; - chunk_input.header.size = sideband_input_size; - chunk_input.p_address = p_sideband_input; - } - - { - // 0x0000'0000'C001'8007 (AJM_INSTANCE_STATISTICS): - // | sideband | reserved | statistics | command | codec | revision | - // | 000 | 0000000000000 | 11000000000000011 | 0000 | 00000000 | 111 | - // statistics flags: - // STATISTICS_ENGINE | STATISTICS_ENGINE_PER_CODEC | ??STATISTICS_UNK?? | STATISTICS_MEMORY - - // 0x0000'6000'0000'E7FF: - // | sideband | reserved | control | run | codec | revision | - // | 011 | 00000000000000000000000000000 | 111 | 00 | 11111111 | 111 | - const bool is_statistics = instance_id == AJM_INSTANCE_STATISTICS; - flags &= is_statistics ? 0x0000'0000'C001'8007 : 0x0000'6000'0000'E7FF; - - auto& chunk_flags = AjmBufferExtract(p_current); - chunk_flags.ident = AjmIdentControlFlags; - chunk_flags.payload = u32(flags >> 32); - chunk_flags.size = u32(flags); - } - - { - auto& chunk_output = AjmBufferExtract(p_current); - chunk_output.header.ident = AjmIdentOutputControlBuf; - chunk_output.header.payload = 0; - chunk_output.header.size = sideband_output_size; - chunk_output.p_address = p_sideband_output; - } - - header.size = u32(p_current - p_begin); - return p_current; + return BatchJobControlBufferRa(p_buffer, instance_id, flags, p_sideband_input, + sideband_input_size, p_sideband_output, sideband_output_size, + p_return_address); } void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(void* p_buffer, const void* p_data_input, size_t data_input_size, const void** pp_batch_address) { - LOG_TRACE(Lib_Ajm, "called"); - - u8* p_current = (u8*)p_buffer; - - auto& header = AjmBufferExtract(p_current); - header.ident = AjmIdentInlineBuf; - header.payload = 0; - header.size = Common::AlignUp(data_input_size, 8); - *pp_batch_address = p_current; - - memcpy(p_current, p_data_input, data_input_size); - return p_current + header.size; + return BatchJobInlineBuffer(p_buffer, p_data_input, data_input_size, pp_batch_address); } void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags, @@ -288,62 +46,9 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(void* p_buffer, u32 instance_id, u6 void* p_data_output, size_t data_output_size, void* p_sideband_output, size_t sideband_output_size, void* p_return_address) { - LOG_TRACE(Lib_Ajm, "called"); - - u8* p_current = (u8*)p_buffer; - - auto& header = AjmBufferExtract(p_current); - header.ident = AjmIdentJob; - header.payload = instance_id; - - const u8* const p_begin = p_current; - - if (p_return_address != nullptr) { - auto& chunk_ra = AjmBufferExtract(p_current); - chunk_ra.header.ident = AjmIdentReturnAddressBuf; - chunk_ra.header.payload = 0; - chunk_ra.header.size = 0; - chunk_ra.p_address = p_return_address; - } - - { - auto& chunk_input = AjmBufferExtract(p_current); - chunk_input.header.ident = AjmIdentInputRunBuf; - chunk_input.header.payload = 0; - chunk_input.header.size = data_input_size; - chunk_input.p_address = p_data_input; - } - - { - // 0x0000'E000'0000'1FFF: - // | sideband | reserved | control | run | codec | revision | - // | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 | - flags &= 0x0000'E000'0000'1FFF; - - auto& chunk_flags = AjmBufferExtract(p_current); - chunk_flags.ident = AjmIdentRunFlags; - chunk_flags.payload = u32(flags >> 32); - chunk_flags.size = u32(flags); - } - - { - auto& chunk_output = AjmBufferExtract(p_current); - chunk_output.header.ident = AjmIdentOutputRunBuf; - chunk_output.header.payload = 0; - chunk_output.header.size = data_output_size; - chunk_output.p_address = p_data_output; - } - - { - auto& chunk_output = AjmBufferExtract(p_current); - chunk_output.header.ident = AjmIdentOutputControlBuf; - chunk_output.header.payload = 0; - chunk_output.header.size = sideband_output_size; - chunk_output.p_address = p_sideband_output; - } - - header.size = u32(p_current - p_begin); - return p_current; + return BatchJobRunBufferRa(p_buffer, instance_id, flags, p_data_input, data_input_size, + p_data_output, data_output_size, p_sideband_output, + sideband_output_size, p_return_address); } void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa( @@ -351,280 +56,25 @@ void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa( size_t num_data_input_buffers, const AjmBuffer* p_data_output_buffers, size_t num_data_output_buffers, void* p_sideband_output, size_t sideband_output_size, void* p_return_address) { - LOG_TRACE(Lib_Ajm, "called"); - - u8* p_current = (u8*)p_buffer; - - auto& header = AjmBufferExtract(p_current); - header.ident = AjmIdentJob; - header.payload = instance_id; - - const u8* const p_begin = p_current; - - if (p_return_address != nullptr) { - auto& chunk_ra = AjmBufferExtract(p_current); - chunk_ra.header.ident = AjmIdentReturnAddressBuf; - chunk_ra.header.payload = 0; - chunk_ra.header.size = 0; - chunk_ra.p_address = p_return_address; - } - - for (s32 i = 0; i < num_data_input_buffers; i++) { - auto& chunk_input = AjmBufferExtract(p_current); - chunk_input.header.ident = AjmIdentInputRunBuf; - chunk_input.header.payload = 0; - chunk_input.header.size = p_data_input_buffers[i].size; - chunk_input.p_address = p_data_input_buffers[i].p_address; - } - - { - // 0x0000'E000'0000'1FFF: - // | sideband | reserved | control | run | codec | revision | - // | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 | - flags &= 0x0000'E000'0000'1FFF; - - auto& chunk_flags = AjmBufferExtract(p_current); - chunk_flags.ident = AjmIdentRunFlags; - chunk_flags.payload = u32(flags >> 32); - chunk_flags.size = u32(flags); - } - - for (s32 i = 0; i < num_data_output_buffers; i++) { - auto& chunk_output = AjmBufferExtract(p_current); - chunk_output.header.ident = AjmIdentOutputRunBuf; - chunk_output.header.payload = 0; - chunk_output.header.size = p_data_output_buffers[i].size; - chunk_output.p_address = p_data_output_buffers[i].p_address; - } - - { - auto& chunk_output = AjmBufferExtract(p_current); - chunk_output.header.ident = AjmIdentOutputControlBuf; - chunk_output.header.payload = 0; - chunk_output.header.size = sideband_output_size; - chunk_output.p_address = p_sideband_output; - } - - header.size = u32(p_current - p_begin); - return p_current; + return BatchJobRunSplitBufferRa(p_buffer, instance_id, flags, p_data_input_buffers, + num_data_input_buffers, p_data_output_buffers, + num_data_output_buffers, p_sideband_output, + sideband_output_size, p_return_address); } -int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, u8* p_batch, u32 batch_size, +int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context_id, u8* p_batch, u32 batch_size, const int priority, AjmBatchError* batch_error, u32* out_batch_id) { - LOG_INFO(Lib_Ajm, "called context = {}, batch_size = {:#x}, priority = {}", context, batch_size, - priority); - - if ((batch_size & 7) != 0) { - LOG_ERROR(Lib_Ajm, "ORBIS_AJM_ERROR_MALFORMED_BATCH"); - return ORBIS_AJM_ERROR_MALFORMED_BATCH; - } - - const auto batch_info = std::make_shared(); - auto batch_id = dev->batches.Create(batch_info); - if (!batch_id.has_value()) { - return ORBIS_AJM_ERROR_OUT_OF_MEMORY; - } - batch_info->id = batch_id.value(); - *out_batch_id = batch_id.value(); - - u8* p_current = p_batch; - u8* const p_batch_end = p_current + batch_size; - - while (p_current < p_batch_end) { - auto& header = AjmBufferExtract(p_current); - ASSERT(header.ident == AjmIdentJob); - - batch_info->jobs.push_back(AjmJob{}); - auto& job = batch_info->jobs.back(); - job.instance_id = header.payload; - - std::optional job_flags = {}; - std::optional input_control_buffer = {}; - std::optional output_control_buffer = {}; - std::optional inline_buffer = {}; - boost::container::small_vector input_run_buffers; - boost::container::small_vector output_run_buffers; - - // Read parameters of a job - auto* const p_job_end = p_current + header.size; - while (p_current < p_job_end) { - auto& header = AjmBufferPeek(p_current); - switch (header.ident) { - case Identifier::AjmIdentInputRunBuf: { - auto& buffer = AjmBufferExtract(p_current); - u8* p_begin = reinterpret_cast(buffer.p_address); - job.input.buffer.insert(job.input.buffer.end(), p_begin, - p_begin + buffer.header.size); - break; - } - case Identifier::AjmIdentInputControlBuf: { - ASSERT_MSG(!input_control_buffer.has_value(), - "Only one instance of input control buffer is allowed per job"); - input_control_buffer = AjmBufferExtract(p_current); - break; - } - case Identifier::AjmIdentControlFlags: - case Identifier::AjmIdentRunFlags: { - ASSERT_MSG(!job_flags.has_value(), - "Only one instance of job flags is allowed per job"); - auto& flags_chunk = AjmBufferExtract(p_current); - job_flags = AjmJobFlags{ - .raw = (u64(flags_chunk.payload) << 32) + flags_chunk.size, - }; - break; - } - case Identifier::AjmIdentReturnAddressBuf: { - // Ignore return address buffers. - AjmBufferSkip(p_current); - break; - } - case Identifier::AjmIdentInlineBuf: { - ASSERT_MSG(!output_control_buffer.has_value(), - "Only one instance of inline buffer is allowed per job"); - inline_buffer = AjmBufferExtract(p_current); - break; - } - case Identifier::AjmIdentOutputRunBuf: { - auto& buffer = AjmBufferExtract(p_current); - u8* p_begin = reinterpret_cast(buffer.p_address); - job.output.buffers.emplace_back( - std::span(p_begin, p_begin + buffer.header.size)); - break; - } - case Identifier::AjmIdentOutputControlBuf: { - ASSERT_MSG(!output_control_buffer.has_value(), - "Only one instance of output control buffer is allowed per job"); - output_control_buffer = AjmBufferExtract(p_current); - break; - } - default: - UNREACHABLE_MSG("Unknown chunk: {}", header.ident); - } - } - - job.flags = job_flags.value(); - - // Perform operation requested by control flags. - const auto control_flags = job_flags.value().control_flags; - if (True(control_flags & AjmJobControlFlags::Initialize)) { - ASSERT_MSG(input_control_buffer.has_value(), - "Initialize called without control buffer"); - const auto& in_buffer = input_control_buffer.value(); - job.input.init_params = AjmDecAt9InitializeParameters{}; - std::memcpy(&job.input.init_params.value(), in_buffer.p_address, in_buffer.header.size); - } - if (True(control_flags & AjmJobControlFlags::Resample)) { - ASSERT_MSG(inline_buffer.has_value(), - "Resample paramters are stored in the inline buffer"); - auto* p_buffer = reinterpret_cast(inline_buffer.value().p_address); - job.input.resample_parameters = - AjmBufferExtract(p_buffer); - } - - // Initialize sideband input parameters - if (input_control_buffer.has_value()) { - auto* p_sideband = reinterpret_cast(input_control_buffer.value().p_address); - auto* const p_end = p_sideband + input_control_buffer.value().header.size; - - const auto sideband_flags = job_flags.value().sideband_flags; - if (True(sideband_flags & AjmJobSidebandFlags::Format) && p_sideband < p_end) { - job.input.format = AjmBufferExtract(p_sideband); - } - if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && p_sideband < p_end) { - job.input.gapless_decode = AjmBufferExtract(p_sideband); - } - - ASSERT_MSG(p_sideband <= p_end, "Input sideband out of bounds"); - } - - // Initialize sideband output parameters - if (output_control_buffer.has_value()) { - auto* p_sideband = reinterpret_cast(output_control_buffer.value().p_address); - auto* const p_end = p_sideband + output_control_buffer.value().header.size; - job.output.p_result = &AjmBufferExtract(p_sideband); - *job.output.p_result = AjmSidebandResult{}; - - const auto sideband_flags = job_flags.value().sideband_flags; - if (True(sideband_flags & AjmJobSidebandFlags::Stream) && p_sideband < p_end) { - job.output.p_stream = &AjmBufferExtract(p_sideband); - *job.output.p_stream = AjmSidebandStream{}; - } - if (True(sideband_flags & AjmJobSidebandFlags::Format) && p_sideband < p_end) { - LOG_ERROR(Lib_Ajm, "SIDEBAND_FORMAT is not implemented"); - job.output.p_format = &AjmBufferExtract(p_sideband); - *job.output.p_format = AjmSidebandFormat{}; - } - if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && p_sideband < p_end) { - job.output.p_gapless_decode = - &AjmBufferExtract(p_sideband); - *job.output.p_gapless_decode = AjmSidebandGaplessDecode{}; - } - - const auto run_flags = job_flags.value().run_flags; - if (True(run_flags & AjmJobRunFlags::MultipleFrames) && p_sideband < p_end) { - job.output.p_mframe = &AjmBufferExtract(p_sideband); - *job.output.p_mframe = AjmSidebandMFrame{}; - } - if (True(run_flags & AjmJobRunFlags::GetCodecInfo) && p_sideband < p_end) { - job.output.p_codec_info = p_sideband; - } - - ASSERT_MSG(p_sideband <= p_end, "Output sideband out of bounds"); - } - } - - { - std::lock_guard lock(dev->batch_queue_mutex); - dev->batch_queue.push(batch_info); - } - - { - std::unique_lock lock(dev->batch_queue_cv_mutex); - dev->batch_queue_cv.notify_all(); - } - - return ORBIS_OK; + LOG_TRACE(Lib_Ajm, "called context = {}, batch_size = {:#x}, priority = {}", context_id, + batch_size, priority); + return context->BatchStartBuffer(p_batch, batch_size, priority, batch_error, out_batch_id); } -int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout, +int PS4_SYSV_ABI sceAjmBatchWait(const u32 context_id, const u32 batch_id, const u32 timeout, AjmBatchError* const batch_error) { - LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context, batch_id, + LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context_id, batch_id, timeout); - - std::shared_ptr batch{}; - { - std::lock_guard guard(dev->batches_mutex); - const auto opt_batch = dev->batches.Get(batch_id); - if (!opt_batch.has_value()) { - return ORBIS_AJM_ERROR_INVALID_BATCH; - } - - batch = opt_batch.value().get(); - } - - bool expected = false; - if (!batch->waiting.compare_exchange_strong(expected, true)) { - return ORBIS_AJM_ERROR_BUSY; - } - - if (timeout == ORBIS_AJM_WAIT_INFINITE) { - batch->finished.acquire(); - } else if (!batch->finished.try_acquire_for(std::chrono::milliseconds(timeout))) { - batch->waiting = false; - return ORBIS_AJM_ERROR_IN_PROGRESS; - } - - { - std::lock_guard guard(dev->batches_mutex); - dev->batches.Destroy(batch_id); - } - - if (batch->canceled) { - return ORBIS_AJM_ERROR_CANCELLED; - } - - return ORBIS_OK; + return context->BatchWait(batch_id, timeout, batch_error); } int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() { @@ -649,13 +99,13 @@ int PS4_SYSV_ABI sceAjmFinalize() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context) { +int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) { LOG_INFO(Lib_Ajm, "called reserved = {}", reserved); - if (out_context == nullptr || reserved != 0) { + if (p_context_id == nullptr || reserved != 0) { return ORBIS_AJM_ERROR_INVALID_PARAMETER; } - *out_context = 1; - dev = std::make_unique(); + *p_context_id = 1; + context = std::make_unique(); return ORBIS_OK; } @@ -664,61 +114,16 @@ int PS4_SYSV_ABI sceAjmInstanceCodecType() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags, - u32* out_instance) { - LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, flags = {:#x}", context, +int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type, + AjmInstanceFlags flags, u32* out_instance) { + LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, flags = {:#x}", context_id, magic_enum::enum_name(codec_type), flags.raw); - - if (codec_type >= AjmCodecType::Max) { - return ORBIS_AJM_ERROR_INVALID_PARAMETER; - } - if (flags.version == 0) { - return ORBIS_AJM_ERROR_WRONG_REVISION_FLAG; - } - if (!dev->IsRegistered(codec_type)) { - return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED; - } - if (dev->curr_cursor == dev->release_cursor) { - return ORBIS_AJM_ERROR_OUT_OF_RESOURCES; - } - ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!"); - const u32 index = dev->free_instances[dev->curr_cursor++]; - dev->curr_cursor %= MaxInstances; - std::unique_ptr instance; - switch (codec_type) { - case AjmCodecType::Mp3Dec: - instance = std::make_unique(); - break; - case AjmCodecType::At9Dec: - instance = std::make_unique(); - break; - default: - UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type)); - } - instance->index = index; - instance->codec_type = codec_type; - instance->num_channels = flags.channels; - instance->flags = flags; - dev->instances[index] = std::move(instance); - *out_instance = index; - - LOG_INFO(Lib_Ajm, "instance = {}", index); - - return ORBIS_OK; + return context->InstanceCreate(codec_type, flags, out_instance); } -int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance) { - LOG_INFO(Lib_Ajm, "called context = {}, instance = {}", context, instance); - if ((instance & 0x3fff) > MaxInstances) { - return ORBIS_AJM_ERROR_INVALID_INSTANCE; - } - const u32 next_slot = (dev->release_cursor + 1) % MaxInstances; - if (next_slot != dev->curr_cursor) { - dev->free_instances[dev->release_cursor] = instance; - dev->release_cursor = next_slot; - } - dev->instances[instance].reset(); - return ORBIS_OK; +int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context_id, u32 instance_id) { + LOG_INFO(Lib_Ajm, "called context = {}, instance = {}", context_id, instance_id); + return context->InstanceDestroy(instance_id); } int PS4_SYSV_ABI sceAjmInstanceExtend() { @@ -741,17 +146,12 @@ int PS4_SYSV_ABI sceAjmMemoryUnregister() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmModuleRegister(u32 context, AjmCodecType codec_type, s64 reserved) { - LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, reserved = {}", context, - u32(codec_type), reserved); - if (codec_type >= AjmCodecType::Max || reserved != 0) { +int PS4_SYSV_ABI sceAjmModuleRegister(u32 context_id, AjmCodecType codec_type, s64 reserved) { + LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}", context_id, u32(codec_type)); + if (reserved != 0) { return ORBIS_AJM_ERROR_INVALID_PARAMETER; } - if (dev->IsRegistered(codec_type)) { - return ORBIS_AJM_ERROR_CODEC_ALREADY_REGISTERED; - } - dev->Register(codec_type); - return ORBIS_OK; + return context->ModuleRegister(codec_type); } int PS4_SYSV_ABI sceAjmModuleUnregister() { diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h index 183d95592..ee9224a13 100644 --- a/src/core/libraries/ajm/ajm.h +++ b/src/core/libraries/ajm/ajm.h @@ -7,14 +7,15 @@ #include "common/enum.h" #include "common/types.h" -#include "core/libraries/ajm/ajm_instance.h" - namespace Core::Loader { class SymbolsResolver; } namespace Libraries::Ajm { +constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4; +constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000; + struct AjmBatchInfo { void* pBuffer; u64 offset; @@ -33,30 +34,6 @@ struct AjmBuffer { u64 size; }; -enum Identifier : u8 { - AjmIdentJob = 0, - AjmIdentInputRunBuf = 1, - AjmIdentInputControlBuf = 2, - AjmIdentControlFlags = 3, - AjmIdentRunFlags = 4, - AjmIdentReturnAddressBuf = 6, - AjmIdentInlineBuf = 7, - AjmIdentOutputRunBuf = 17, - AjmIdentOutputControlBuf = 18, -}; - -struct AjmChunkHeader { - u32 ident : 6; - u32 payload : 20; - u32 reserved : 6; - u32 size; -}; - -struct AjmChunkBuffer { - AjmChunkHeader header; - void* p_address; -}; - enum class AjmJobControlFlags : u64 { Reset = 1 << 0, Initialize = 1 << 1, @@ -89,6 +66,65 @@ union AjmJobFlags { }; }; +struct AjmSidebandResult { + s32 result; + s32 internal_result; +}; + +struct AjmSidebandMFrame { + u32 num_frames; + u32 reserved; +}; + +struct AjmSidebandStream { + s32 input_consumed; + s32 output_written; + u64 total_decoded_samples; +}; + +enum class AjmFormatEncoding : u32 { + S16 = 0, + S32 = 1, + Float = 2, +}; + +struct AjmSidebandFormat { + u32 num_channels; + u32 channel_mask; + u32 sampl_freq; + AjmFormatEncoding sample_encoding; + u32 bitrate; + u32 reserved; +}; + +struct AjmSidebandGaplessDecode { + u32 total_samples; + u16 skip_samples; + u16 skipped_samples; +}; + +struct AjmSidebandResampleParameters { + float ratio; + uint32_t flags; +}; + +struct AjmDecAt9InitializeParameters { + u8 config_data[ORBIS_AT9_CONFIG_DATA_SIZE]; + u32 reserved; +}; + +union AjmInstanceFlags { + u64 raw; + struct { + u64 version : 3; + u64 channels : 4; + u64 format : 3; + u64 gapless_loop : 1; + u64 : 21; + u64 codec : 28; + }; +}; + struct AjmDecMp3ParseFrame; enum class AjmCodecType : u32; diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp index cf44d6327..56795d6fc 100644 --- a/src/core/libraries/ajm/ajm_at9.cpp +++ b/src/core/libraries/ajm/ajm_at9.cpp @@ -64,7 +64,7 @@ void AjmAt9Decoder::GetCodecInfo(void* out_info) { codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize; } -void AjmAt9Decoder::Decode(const AjmJobInput* input, AjmJobOutput* output) { +void AjmAt9Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) { Atrac9CodecInfo codec_info; Atrac9GetCodecInfo(handle, &codec_info); diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h index 2885158e8..47080a08a 100644 --- a/src/core/libraries/ajm/ajm_at9.h +++ b/src/core/libraries/ajm/ajm_at9.h @@ -42,7 +42,7 @@ struct AjmAt9Decoder final : AjmInstance { return sizeof(AjmSidebandDecAt9CodecInfo); } - void Decode(const AjmJobInput* input, AjmJobOutput* output) override; + void Decode(const AjmJob::Input* input, AjmJob::Output* output) override; private: void ResetCodec(); diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp new file mode 100644 index 000000000..3a47b584d --- /dev/null +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -0,0 +1,461 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "core/libraries/ajm/ajm_batch.h" + +namespace Libraries::Ajm { + +enum Identifier : u8 { + AjmIdentJob = 0, + AjmIdentInputRunBuf = 1, + AjmIdentInputControlBuf = 2, + AjmIdentControlFlags = 3, + AjmIdentRunFlags = 4, + AjmIdentReturnAddressBuf = 6, + AjmIdentInlineBuf = 7, + AjmIdentOutputRunBuf = 17, + AjmIdentOutputControlBuf = 18, +}; + +struct AjmChunkHeader { + u32 ident : 6; + u32 payload : 20; + u32 reserved : 6; +}; +static_assert(sizeof(AjmChunkHeader) == 4); + +struct AjmChunkJob { + AjmChunkHeader header; + u32 size; +}; +static_assert(sizeof(AjmChunkJob) == 8); + +struct AjmChunkFlags { + AjmChunkHeader header; + u32 flags_low; +}; +static_assert(sizeof(AjmChunkFlags) == 8); + +struct AjmChunkBuffer { + AjmChunkHeader header; + u32 size; + void* p_address; +}; +static_assert(sizeof(AjmChunkBuffer) == 16); + +class AjmBatchBuffer { +public: + static constexpr size_t DynamicExtent = std::numeric_limits::max(); + + AjmBatchBuffer(u8* begin, u8* end) + : m_p_begin(begin), m_p_current(begin), m_size(end - begin) {} + AjmBatchBuffer(u8* begin, size_t size = DynamicExtent) + : m_p_begin(begin), m_p_current(m_p_begin), m_size(size) {} + AjmBatchBuffer(std::span data) + : m_p_begin(data.data()), m_p_current(m_p_begin), m_size(data.size()) {} + + AjmBatchBuffer SubBuffer(size_t size = DynamicExtent) { + auto current = m_p_current; + if (size != DynamicExtent) { + m_p_current += size; + } + return AjmBatchBuffer(current, size); + } + + template + T& Peek() const { + DEBUG_ASSERT(m_size == DynamicExtent || (m_p_current + sizeof(T)) <= (m_p_begin + m_size)); + return *reinterpret_cast(m_p_current); + } + + template + T& Consume() { + auto* const result = reinterpret_cast(m_p_current); + m_p_current += sizeof(T); + DEBUG_ASSERT(m_size == DynamicExtent || m_p_current <= (m_p_begin + m_size)); + return *result; + } + + template + void Skip() { + Advance(sizeof(T)); + } + + void Advance(size_t size) { + m_p_current += size; + DEBUG_ASSERT(m_size == DynamicExtent || m_p_current <= (m_p_begin + m_size)); + } + + bool IsEmpty() { + return m_size != DynamicExtent && m_p_current >= (m_p_begin + m_size); + } + + size_t BytesConsumed() const { + return m_p_current - m_p_begin; + } + + size_t BytesRemaining() const { + if (m_size == DynamicExtent) { + return DynamicExtent; + } + return m_size - (m_p_current - m_p_begin); + } + + u8* GetCurrent() const { + return m_p_current; + } + +private: + u8* m_p_begin{}; + u8* m_p_current{}; + size_t m_size{}; +}; + +AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { + std::optional job_flags = {}; + std::optional input_control_buffer = {}; + std::optional output_control_buffer = {}; + std::optional inline_buffer = {}; + + AjmJob job; + job.instance_id = instance_id; + + // Read parameters of a job + while (!batch_buffer.IsEmpty()) { + auto& header = batch_buffer.Peek(); + switch (header.ident) { + case Identifier::AjmIdentInputRunBuf: { + auto& buffer = batch_buffer.Consume(); + u8* p_begin = reinterpret_cast(buffer.p_address); + job.input.buffer.insert(job.input.buffer.end(), p_begin, p_begin + buffer.size); + break; + } + case Identifier::AjmIdentInputControlBuf: { + ASSERT_MSG(!input_control_buffer.has_value(), + "Only one instance of input control buffer is allowed per job"); + input_control_buffer = batch_buffer.Consume(); + break; + } + case Identifier::AjmIdentControlFlags: + case Identifier::AjmIdentRunFlags: { + ASSERT_MSG(!job_flags.has_value(), "Only one instance of job flags is allowed per job"); + auto& chunk = batch_buffer.Consume(); + job_flags = AjmJobFlags{ + .raw = (u64(chunk.header.payload) << 32) + chunk.flags_low, + }; + break; + } + case Identifier::AjmIdentReturnAddressBuf: { + // Ignore return address buffers. + batch_buffer.Skip(); + break; + } + case Identifier::AjmIdentInlineBuf: { + ASSERT_MSG(!output_control_buffer.has_value(), + "Only one instance of inline buffer is allowed per job"); + inline_buffer = batch_buffer.Consume(); + break; + } + case Identifier::AjmIdentOutputRunBuf: { + auto& buffer = batch_buffer.Consume(); + u8* p_begin = reinterpret_cast(buffer.p_address); + job.output.buffers.emplace_back(std::span(p_begin, p_begin + buffer.size)); + break; + } + case Identifier::AjmIdentOutputControlBuf: { + ASSERT_MSG(!output_control_buffer.has_value(), + "Only one instance of output control buffer is allowed per job"); + output_control_buffer = batch_buffer.Consume(); + break; + } + default: + UNREACHABLE_MSG("Unknown chunk: {}", header.ident); + } + } + + job.flags = job_flags.value(); + + // Initialize sideband input parameters + if (input_control_buffer.has_value()) { + AjmBatchBuffer input_batch(reinterpret_cast(input_control_buffer->p_address), + input_control_buffer->size); + + const auto sideband_flags = job_flags->sideband_flags; + if (True(sideband_flags & AjmJobSidebandFlags::Format) && !input_batch.IsEmpty()) { + job.input.format = input_batch.Consume(); + } + if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && !input_batch.IsEmpty()) { + job.input.gapless_decode = input_batch.Consume(); + } + + const auto control_flags = job_flags.value().control_flags; + if (True(control_flags & AjmJobControlFlags::Initialize)) { + job.input.init_params = AjmDecAt9InitializeParameters{}; + std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(), + input_batch.BytesRemaining()); + } + } + + if (inline_buffer.has_value()) { + AjmBatchBuffer inline_batch(reinterpret_cast(inline_buffer->p_address), + inline_buffer->size); + + const auto control_flags = job_flags.value().control_flags; + if (True(control_flags & AjmJobControlFlags::Resample)) { + job.input.resample_parameters = inline_batch.Consume(); + } + } + + // Initialize sideband output parameters + if (output_control_buffer.has_value()) { + AjmBatchBuffer output_batch(reinterpret_cast(output_control_buffer->p_address), + output_control_buffer->size); + + job.output.p_result = &output_batch.Consume(); + *job.output.p_result = AjmSidebandResult{}; + + const auto sideband_flags = job_flags->sideband_flags; + if (True(sideband_flags & AjmJobSidebandFlags::Stream) && !output_batch.IsEmpty()) { + job.output.p_stream = &output_batch.Consume(); + *job.output.p_stream = AjmSidebandStream{}; + } + if (True(sideband_flags & AjmJobSidebandFlags::Format) && !output_batch.IsEmpty()) { + LOG_ERROR(Lib_Ajm, "SIDEBAND_FORMAT is not implemented"); + job.output.p_format = &output_batch.Consume(); + *job.output.p_format = AjmSidebandFormat{}; + } + if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && !output_batch.IsEmpty()) { + job.output.p_gapless_decode = &output_batch.Consume(); + *job.output.p_gapless_decode = AjmSidebandGaplessDecode{}; + } + + const auto run_flags = job_flags->run_flags; + if (True(run_flags & AjmJobRunFlags::MultipleFrames) && !output_batch.IsEmpty()) { + job.output.p_mframe = &output_batch.Consume(); + *job.output.p_mframe = AjmSidebandMFrame{}; + } + if (True(run_flags & AjmJobRunFlags::GetCodecInfo) && !output_batch.IsEmpty()) { + job.output.p_codec_info = output_batch.GetCurrent(); + } + } + + return job; +} + +std::shared_ptr AjmBatch::FromBatchBuffer(std::span data) { + auto batch = std::make_shared(); + + AjmBatchBuffer buffer(data); + while (!buffer.IsEmpty()) { + auto& job_chunk = buffer.Consume(); + ASSERT(job_chunk.header.ident == AjmIdentJob); + auto instance_id = job_chunk.header.payload; + batch->jobs.push_back(AjmJobFromBatchBuffer(instance_id, buffer.SubBuffer(job_chunk.size))); + } + + return batch; +} + +void* BatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_sideband_input, + size_t sideband_input_size, void* p_sideband_output, + size_t sideband_output_size, void* p_return_address) { + LOG_TRACE(Lib_Ajm, "called"); + + AjmBatchBuffer buffer(reinterpret_cast(p_buffer)); + + auto& job_chunk = buffer.Consume(); + job_chunk.header.ident = AjmIdentJob; + job_chunk.header.payload = instance_id; + + auto job_buffer = buffer.SubBuffer(); + + if (p_return_address != nullptr) { + auto& chunk_ra = job_buffer.Consume(); + chunk_ra.header.ident = AjmIdentReturnAddressBuf; + chunk_ra.header.payload = 0; + chunk_ra.size = 0; + chunk_ra.p_address = p_return_address; + } + + { + auto& chunk_input = job_buffer.Consume(); + chunk_input.header.ident = AjmIdentInputControlBuf; + chunk_input.header.payload = 0; + chunk_input.size = sideband_input_size; + chunk_input.p_address = p_sideband_input; + } + + { + // 0x0000'0000'C001'8007 (AJM_INSTANCE_STATISTICS): + // | sideband | reserved | statistics | command | codec | revision | + // | 000 | 0000000000000 | 11000000000000011 | 0000 | 00000000 | 111 | + // statistics flags: + // STATISTICS_ENGINE | STATISTICS_ENGINE_PER_CODEC | ??STATISTICS_UNK?? | STATISTICS_MEMORY + + // 0x0000'6000'0000'E7FF: + // | sideband | reserved | control | run | codec | revision | + // | 011 | 00000000000000000000000000000 | 111 | 00 | 11111111 | 111 | + const bool is_statistics = instance_id == AJM_INSTANCE_STATISTICS; + flags &= is_statistics ? 0x0000'0000'C001'8007 : 0x0000'6000'0000'E7FF; + + auto& chunk_flags = job_buffer.Consume(); + chunk_flags.header.ident = AjmIdentControlFlags; + chunk_flags.header.payload = u32(flags >> 32); + chunk_flags.flags_low = u32(flags); + } + + { + auto& chunk_output = job_buffer.Consume(); + chunk_output.header.ident = AjmIdentOutputControlBuf; + chunk_output.header.payload = 0; + chunk_output.size = sideband_output_size; + chunk_output.p_address = p_sideband_output; + } + + job_chunk.size = job_buffer.BytesConsumed(); + return job_buffer.GetCurrent(); +} + +void* BatchJobInlineBuffer(void* p_buffer, const void* p_data_input, size_t data_input_size, + const void** pp_batch_address) { + LOG_TRACE(Lib_Ajm, "called"); + + AjmBatchBuffer buffer(reinterpret_cast(p_buffer)); + + auto& job_chunk = buffer.Consume(); + job_chunk.header.ident = AjmIdentInlineBuf; + job_chunk.header.payload = 0; + job_chunk.size = Common::AlignUp(data_input_size, 8); + *pp_batch_address = buffer.GetCurrent(); + + memcpy(buffer.GetCurrent(), p_data_input, data_input_size); + return buffer.GetCurrent() + job_chunk.size; +} + +void* BatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_data_input, + size_t data_input_size, void* p_data_output, size_t data_output_size, + void* p_sideband_output, size_t sideband_output_size, + void* p_return_address) { + LOG_TRACE(Lib_Ajm, "called"); + + AjmBatchBuffer buffer(reinterpret_cast(p_buffer)); + + auto& job_chunk = buffer.Consume(); + job_chunk.header.ident = AjmIdentJob; + job_chunk.header.payload = instance_id; + + auto job_buffer = buffer.SubBuffer(); + + if (p_return_address != nullptr) { + auto& chunk_ra = job_buffer.Consume(); + chunk_ra.header.ident = AjmIdentReturnAddressBuf; + chunk_ra.header.payload = 0; + chunk_ra.size = 0; + chunk_ra.p_address = p_return_address; + } + + { + auto& chunk_input = job_buffer.Consume(); + chunk_input.header.ident = AjmIdentInputRunBuf; + chunk_input.header.payload = 0; + chunk_input.size = data_input_size; + chunk_input.p_address = p_data_input; + } + + { + // 0x0000'E000'0000'1FFF: + // | sideband | reserved | control | run | codec | revision | + // | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 | + flags &= 0x0000'E000'0000'1FFF; + + auto& chunk_flags = job_buffer.Consume(); + chunk_flags.header.ident = AjmIdentRunFlags; + chunk_flags.header.payload = u32(flags >> 32); + chunk_flags.flags_low = u32(flags); + } + + { + auto& chunk_output = job_buffer.Consume(); + chunk_output.header.ident = AjmIdentOutputRunBuf; + chunk_output.header.payload = 0; + chunk_output.size = data_output_size; + chunk_output.p_address = p_data_output; + } + + { + auto& chunk_output = job_buffer.Consume(); + chunk_output.header.ident = AjmIdentOutputControlBuf; + chunk_output.header.payload = 0; + chunk_output.size = sideband_output_size; + chunk_output.p_address = p_sideband_output; + } + + job_chunk.size = job_buffer.BytesConsumed(); + return job_buffer.GetCurrent(); +} + +void* BatchJobRunSplitBufferRa(void* p_buffer, u32 instance_id, u64 flags, + const AjmBuffer* p_data_input_buffers, size_t num_data_input_buffers, + const AjmBuffer* p_data_output_buffers, + size_t num_data_output_buffers, void* p_sideband_output, + size_t sideband_output_size, void* p_return_address) { + LOG_TRACE(Lib_Ajm, "called"); + + AjmBatchBuffer buffer(reinterpret_cast(p_buffer)); + + auto& job_chunk = buffer.Consume(); + job_chunk.header.ident = AjmIdentJob; + job_chunk.header.payload = instance_id; + + auto job_buffer = buffer.SubBuffer(); + + if (p_return_address != nullptr) { + auto& chunk_ra = job_buffer.Consume(); + chunk_ra.header.ident = AjmIdentReturnAddressBuf; + chunk_ra.header.payload = 0; + chunk_ra.size = 0; + chunk_ra.p_address = p_return_address; + } + + for (s32 i = 0; i < num_data_input_buffers; i++) { + auto& chunk_input = job_buffer.Consume(); + chunk_input.header.ident = AjmIdentInputRunBuf; + chunk_input.header.payload = 0; + chunk_input.size = p_data_input_buffers[i].size; + chunk_input.p_address = p_data_input_buffers[i].p_address; + } + + { + // 0x0000'E000'0000'1FFF: + // | sideband | reserved | control | run | codec | revision | + // | 111 | 00000000000000000000000000000 | 000 | 11 | 11111111 | 111 | + flags &= 0x0000'E000'0000'1FFF; + + auto& chunk_flags = job_buffer.Consume(); + chunk_flags.header.ident = AjmIdentRunFlags; + chunk_flags.header.payload = u32(flags >> 32); + chunk_flags.flags_low = u32(flags); + } + + for (s32 i = 0; i < num_data_output_buffers; i++) { + auto& chunk_output = job_buffer.Consume(); + chunk_output.header.ident = AjmIdentOutputRunBuf; + chunk_output.header.payload = 0; + chunk_output.size = p_data_output_buffers[i].size; + chunk_output.p_address = p_data_output_buffers[i].p_address; + } + + { + auto& chunk_output = job_buffer.Consume(); + chunk_output.header.ident = AjmIdentOutputControlBuf; + chunk_output.header.payload = 0; + chunk_output.size = sideband_output_size; + chunk_output.p_address = p_sideband_output; + } + + job_chunk.size = job_buffer.BytesConsumed(); + return job_buffer.GetCurrent(); +} + +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h new file mode 100644 index 000000000..e7dc4320d --- /dev/null +++ b/src/core/libraries/ajm/ajm_batch.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/assert.h" +#include "common/types.h" +#include "core/libraries/ajm/ajm.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace Libraries::Ajm { + +struct AjmJob { + struct Input { + std::optional init_params; + std::optional resample_parameters; + std::optional format; + std::optional gapless_decode; + std::vector buffer; + }; + + struct Output { + boost::container::small_vector, 4> buffers; + AjmSidebandResult* p_result = nullptr; + AjmSidebandStream* p_stream = nullptr; + AjmSidebandFormat* p_format = nullptr; + AjmSidebandGaplessDecode* p_gapless_decode = nullptr; + AjmSidebandMFrame* p_mframe = nullptr; + u8* p_codec_info = nullptr; + }; + + u32 instance_id{}; + AjmJobFlags flags{}; + Input input; + Output output; +}; + +struct AjmBatch { + u32 id{}; + std::atomic_bool waiting{}; + std::atomic_bool canceled{}; + std::binary_semaphore finished{0}; + boost::container::small_vector jobs; + + static std::shared_ptr FromBatchBuffer(std::span buffer); +}; + +void* BatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_sideband_input, + size_t sideband_input_size, void* p_sideband_output, + size_t sideband_output_size, void* p_return_address); + +void* BatchJobInlineBuffer(void* p_buffer, const void* p_data_input, size_t data_input_size, + const void** pp_batch_address); + +void* BatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags, void* p_data_input, + size_t data_input_size, void* p_data_output, size_t data_output_size, + void* p_sideband_output, size_t sideband_output_size, + void* p_return_address); + +void* BatchJobRunSplitBufferRa(void* p_buffer, u32 instance_id, u64 flags, + const AjmBuffer* p_data_input_buffers, size_t num_data_input_buffers, + const AjmBuffer* p_data_output_buffers, + size_t num_data_output_buffers, void* p_sideband_output, + size_t sideband_output_size, void* p_return_address); + +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_context.cpp b/src/core/libraries/ajm/ajm_context.cpp new file mode 100644 index 000000000..898ef3322 --- /dev/null +++ b/src/core/libraries/ajm/ajm_context.cpp @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/ajm/ajm_at9.h" +#include "core/libraries/ajm/ajm_context.h" +#include "core/libraries/ajm/ajm_error.h" +#include "core/libraries/ajm/ajm_instance.h" +#include "core/libraries/ajm/ajm_mp3.h" +#include "core/libraries/error_codes.h" + +#include +#include + +namespace Libraries::Ajm { + +static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1; + +AjmContext::AjmContext() { + worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); }); +} + +bool AjmContext::IsRegistered(AjmCodecType type) const { + return registered_codecs[std::to_underlying(type)]; +} + +s32 AjmContext::BatchCancel(const u32 batch_id) { + std::shared_ptr batch{}; + { + std::shared_lock guard(batches_mutex); + const auto p_batch = batches.Get(batch_id); + if (p_batch == nullptr) { + return ORBIS_AJM_ERROR_INVALID_BATCH; + } + batch = *p_batch; + } + + batch->canceled = true; + return ORBIS_OK; +} + +s32 AjmContext::ModuleRegister(AjmCodecType type) { + if (std::to_underlying(type) >= NumAjmCodecs) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + if (IsRegistered(type)) { + return ORBIS_AJM_ERROR_CODEC_ALREADY_REGISTERED; + } + registered_codecs[std::to_underlying(type)] = true; + return ORBIS_OK; +} + +void AjmContext::WorkerThread(std::stop_token stop) { + while (!stop.stop_requested()) { + auto batch = batch_queue.PopWait(stop); + if (batch != nullptr) { + ProcessBatch(batch->id, batch->jobs); + batch->finished.release(); + } + } +} + +void AjmContext::ProcessBatch(u32 id, std::span jobs) { + // Perform operation requested by control flags. + for (auto& job : jobs) { + LOG_DEBUG(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, job.instance_id, + job.flags.raw); + + std::shared_ptr instance; + { + std::shared_lock lock(instances_mutex); + auto* p_instance = instances.Get(job.instance_id); + ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance"); + instance = *p_instance; + } + + const auto control_flags = job.flags.control_flags; + if (True(control_flags & AjmJobControlFlags::Reset)) { + LOG_INFO(Lib_Ajm, "Resetting instance {}", job.instance_id); + instance->Reset(); + } + if (True(control_flags & AjmJobControlFlags::Initialize)) { + LOG_INFO(Lib_Ajm, "Initializing instance {}", job.instance_id); + ASSERT_MSG(job.input.init_params.has_value(), + "Initialize called without control buffer"); + auto& params = job.input.init_params.value(); + instance->Initialize(¶ms, sizeof(params)); + } + if (True(control_flags & AjmJobControlFlags::Resample)) { + LOG_ERROR(Lib_Ajm, "Unimplemented: resample params"); + ASSERT_MSG(job.input.resample_parameters.has_value(), "Resample paramters are absent"); + instance->resample_parameters = job.input.resample_parameters.value(); + } + + const auto sideband_flags = job.flags.sideband_flags; + if (True(sideband_flags & AjmJobSidebandFlags::Format)) { + ASSERT_MSG(job.input.format.has_value(), "Format parameters are absent"); + instance->format = job.input.format.value(); + } + if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode)) { + ASSERT_MSG(job.input.gapless_decode.has_value(), + "Gapless decode parameters are absent"); + auto& params = job.input.gapless_decode.value(); + instance->gapless.total_samples = params.total_samples; + instance->gapless.skip_samples = params.skip_samples; + } + + if (!job.input.buffer.empty()) { + instance->Decode(&job.input, &job.output); + } + + if (job.output.p_gapless_decode != nullptr) { + *job.output.p_gapless_decode = instance->gapless; + } + + if (job.output.p_codec_info != nullptr) { + instance->GetCodecInfo(job.output.p_codec_info); + } + } +} + +s32 AjmContext::BatchWait(const u32 batch_id, const u32 timeout, AjmBatchError* const batch_error) { + std::shared_ptr batch{}; + { + std::shared_lock guard(batches_mutex); + const auto p_batch = batches.Get(batch_id); + if (p_batch == nullptr) { + return ORBIS_AJM_ERROR_INVALID_BATCH; + } + batch = *p_batch; + } + + bool expected = false; + if (!batch->waiting.compare_exchange_strong(expected, true)) { + return ORBIS_AJM_ERROR_BUSY; + } + + if (timeout == ORBIS_AJM_WAIT_INFINITE) { + batch->finished.acquire(); + } else if (!batch->finished.try_acquire_for(std::chrono::milliseconds(timeout))) { + batch->waiting = false; + return ORBIS_AJM_ERROR_IN_PROGRESS; + } + + { + std::unique_lock guard(batches_mutex); + batches.Destroy(batch_id); + } + + if (batch->canceled) { + return ORBIS_AJM_ERROR_CANCELLED; + } + + return ORBIS_OK; +} + +int AjmContext::BatchStartBuffer(u8* p_batch, u32 batch_size, const int priority, + AjmBatchError* batch_error, u32* out_batch_id) { + if ((batch_size & 7) != 0) { + LOG_ERROR(Lib_Ajm, "ORBIS_AJM_ERROR_MALFORMED_BATCH"); + return ORBIS_AJM_ERROR_MALFORMED_BATCH; + } + + const auto batch_info = AjmBatch::FromBatchBuffer({p_batch, batch_size}); + std::optional batch_id; + { + std::unique_lock guard(batches_mutex); + batch_id = batches.Create(batch_info); + } + if (!batch_id.has_value()) { + return ORBIS_AJM_ERROR_OUT_OF_MEMORY; + } + *out_batch_id = batch_id.value(); + batch_info->id = *out_batch_id; + + batch_queue.EmplaceWait(batch_info); + + return ORBIS_OK; +} + +s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags, u32* out_instance) { + if (codec_type >= AjmCodecType::Max) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + if (flags.version == 0) { + return ORBIS_AJM_ERROR_WRONG_REVISION_FLAG; + } + if (!IsRegistered(codec_type)) { + return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED; + } + ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!"); + std::unique_ptr instance; + switch (codec_type) { + case AjmCodecType::Mp3Dec: + instance = std::make_unique(); + break; + case AjmCodecType::At9Dec: + instance = std::make_unique(); + break; + default: + UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type)); + } + instance->codec_type = codec_type; + instance->num_channels = flags.channels; + instance->flags = flags; + + std::optional opt_index; + { + std::unique_lock lock(instances_mutex); + opt_index = instances.Create(std::move(instance)); + } + if (!opt_index.has_value()) { + return ORBIS_AJM_ERROR_OUT_OF_RESOURCES; + } + *out_instance = opt_index.value(); + + LOG_INFO(Lib_Ajm, "instance = {}", *out_instance); + return ORBIS_OK; +} + +s32 AjmContext::InstanceDestroy(u32 instance) { + std::unique_lock lock(instances_mutex); + if (!instances.Destroy(instance)) { + return ORBIS_AJM_ERROR_INVALID_INSTANCE; + } + return ORBIS_OK; +} + +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_context.h b/src/core/libraries/ajm/ajm_context.h new file mode 100644 index 000000000..08543dc43 --- /dev/null +++ b/src/core/libraries/ajm/ajm_context.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/bounded_threadsafe_queue.h" +#include "common/slot_array.h" +#include "common/types.h" +#include "core/libraries/ajm/ajm.h" +#include "core/libraries/ajm/ajm_batch.h" +#include "core/libraries/ajm/ajm_instance.h" + +#include +#include +#include +#include +#include + +namespace Libraries::Ajm { + +class AjmContext { +public: + AjmContext(); + + s32 InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags, u32* out_instance_id); + s32 InstanceDestroy(u32 instance_id); + + s32 BatchCancel(const u32 batch_id); + s32 ModuleRegister(AjmCodecType type); + s32 BatchWait(const u32 batch_id, const u32 timeout, AjmBatchError* const p_batch_error); + s32 BatchStartBuffer(u8* p_batch, u32 batch_size, const int priority, + AjmBatchError* p_batch_error, u32* p_batch_id); + + void WorkerThread(std::stop_token stop); + void ProcessBatch(u32 id, std::span jobs); + +private: + static constexpr u32 MaxInstances = 0x2fff; + static constexpr u32 MaxBatches = 0x0400; + static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max); + + [[nodiscard]] bool IsRegistered(AjmCodecType type) const; + + std::array registered_codecs{}; + + std::shared_mutex instances_mutex; + Common::SlotArray, MaxInstances, 1> instances; + + std::shared_mutex batches_mutex; + Common::SlotArray, MaxBatches, 1> batches; + + std::jthread worker_thread{}; + Common::MPSCQueue> batch_queue; +}; + +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h index 126ce1ee4..a3db567b8 100644 --- a/src/core/libraries/ajm/ajm_instance.h +++ b/src/core/libraries/ajm/ajm_instance.h @@ -5,6 +5,8 @@ #include "common/enum.h" #include "common/types.h" +#include "core/libraries/ajm/ajm.h" +#include "core/libraries/ajm/ajm_batch.h" #include @@ -33,8 +35,6 @@ constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200; constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000; constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000; -constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4; - enum class AjmCodecType : u32 { Mp3Dec = 0, At9Dec = 1, @@ -42,90 +42,12 @@ enum class AjmCodecType : u32 { Max = 23, }; DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType); -static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max); - -enum class AjmFormatEncoding : u32 { - S16 = 0, - S32 = 1, - Float = 2, -}; - -struct AjmSidebandResult { - s32 result; - s32 internal_result; -}; - -struct AjmSidebandMFrame { - u32 num_frames; - u32 reserved; -}; - -struct AjmSidebandStream { - s32 input_consumed; - s32 output_written; - u64 total_decoded_samples; -}; - -struct AjmSidebandFormat { - u32 num_channels; - u32 channel_mask; - u32 sampl_freq; - AjmFormatEncoding sample_encoding; - u32 bitrate; - u32 reserved; -}; - -struct AjmSidebandGaplessDecode { - u32 total_samples; - u16 skip_samples; - u16 skipped_samples; -}; - -struct AjmSidebandResampleParameters { - float ratio; - uint32_t flags; -}; - -struct AjmDecAt9InitializeParameters { - u8 config_data[ORBIS_AT9_CONFIG_DATA_SIZE]; - u32 reserved; -}; union AjmSidebandInitParameters { AjmDecAt9InitializeParameters at9; u8 reserved[8]; }; -struct AjmJobInput { - std::optional init_params; - std::optional resample_parameters; - std::optional format; - std::optional gapless_decode; - std::vector buffer; -}; - -struct AjmJobOutput { - boost::container::small_vector, 4> buffers; - AjmSidebandResult* p_result = nullptr; - AjmSidebandStream* p_stream = nullptr; - AjmSidebandFormat* p_format = nullptr; - AjmSidebandGaplessDecode* p_gapless_decode = nullptr; - AjmSidebandMFrame* p_mframe = nullptr; - u8* p_codec_info = nullptr; -}; - -union AjmInstanceFlags { - u64 raw; - struct { - u64 version : 3; - u64 channels : 4; - u64 format : 3; - u64 gapless_loop : 1; - u64 pad : 21; - u64 codec : 28; - }; -}; - struct AjmInstance { AjmCodecType codec_type; AjmFormatEncoding fmt{}; @@ -148,7 +70,7 @@ struct AjmInstance { virtual void GetCodecInfo(void* out_info) = 0; virtual u32 GetCodecInfoSize() = 0; - virtual void Decode(const AjmJobInput* input, AjmJobOutput* output) = 0; + virtual void Decode(const AjmJob::Input* input, AjmJob::Output* output) = 0; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index 7d12ca755..668077490 100644 --- a/src/core/libraries/ajm/ajm_mp3.cpp +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -77,7 +77,7 @@ void AjmMp3Decoder::Reset() { gapless_decoded_samples = 0; } -void AjmMp3Decoder::Decode(const AjmJobInput* input, AjmJobOutput* output) { +void AjmMp3Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) { AVPacket* pkt = av_packet_alloc(); size_t out_buffer_index = 0; diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h index 8a93ca78c..ac0c62fa8 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -74,7 +74,7 @@ struct AjmMp3Decoder : public AjmInstance { return sizeof(AjmSidebandDecMp3CodecInfo); } - void Decode(const AjmJobInput* input, AjmJobOutput* output) override; + void Decode(const AjmJob::Input* input, AjmJob::Output* output) override; static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame);