mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-04 16:32:39 +00:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
a657e22769
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -99,3 +99,6 @@
|
|||||||
path = externals/discord-rpc
|
path = externals/discord-rpc
|
||||||
url = https://github.com/shadps4-emu/ext-discord-rpc.git
|
url = https://github.com/shadps4-emu/ext-discord-rpc.git
|
||||||
shallow = true
|
shallow = true
|
||||||
|
[submodule "externals/LibAtrac9"]
|
||||||
|
path = externals/LibAtrac9
|
||||||
|
url = https://github.com/shadps4-emu/ext-LibAtrac9.git
|
||||||
|
@ -179,12 +179,25 @@ set(AUDIO_CORE src/audio_core/sdl_audio.cpp
|
|||||||
src/audio_core/sdl_audio.h
|
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.cpp
|
||||||
|
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
|
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
||||||
src/core/libraries/audio/audioin.h
|
src/core/libraries/audio/audioin.h
|
||||||
src/core/libraries/audio/audioout.cpp
|
src/core/libraries/audio/audioout.cpp
|
||||||
src/core/libraries/audio/audioout.h
|
src/core/libraries/audio/audioout.h
|
||||||
src/core/libraries/ajm/ajm.cpp
|
|
||||||
src/core/libraries/ajm/ajm.h
|
|
||||||
src/core/libraries/ngs2/ngs2.cpp
|
src/core/libraries/ngs2/ngs2.cpp
|
||||||
src/core/libraries/ngs2/ngs2.h
|
src/core/libraries/ngs2/ngs2.h
|
||||||
)
|
)
|
||||||
@ -194,8 +207,7 @@ set(GNM_LIB src/core/libraries/gnmdriver/gnmdriver.cpp
|
|||||||
src/core/libraries/gnmdriver/gnm_error.h
|
src/core/libraries/gnmdriver/gnm_error.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(KERNEL_LIB
|
set(KERNEL_LIB src/core/libraries/kernel/event_flag/event_flag.cpp
|
||||||
src/core/libraries/kernel/event_flag/event_flag.cpp
|
|
||||||
src/core/libraries/kernel/event_flag/event_flag.h
|
src/core/libraries/kernel/event_flag/event_flag.h
|
||||||
src/core/libraries/kernel/event_flag/event_flag_obj.cpp
|
src/core/libraries/kernel/event_flag/event_flag_obj.cpp
|
||||||
src/core/libraries/kernel/event_flag/event_flag_obj.h
|
src/core/libraries/kernel/event_flag/event_flag_obj.h
|
||||||
@ -494,6 +506,7 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||||||
src/core/libraries/error_codes.h
|
src/core/libraries/error_codes.h
|
||||||
src/core/libraries/libs.h
|
src/core/libraries/libs.h
|
||||||
src/core/libraries/libs.cpp
|
src/core/libraries/libs.cpp
|
||||||
|
${AJM_LIB}
|
||||||
${AUDIO_LIB}
|
${AUDIO_LIB}
|
||||||
${GNM_LIB}
|
${GNM_LIB}
|
||||||
${KERNEL_LIB}
|
${KERNEL_LIB}
|
||||||
@ -795,7 +808,7 @@ endif()
|
|||||||
create_target_directory_groups(shadps4)
|
create_target_directory_groups(shadps4)
|
||||||
|
|
||||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half)
|
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half)
|
||||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
|
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
|
||||||
|
|
||||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||||
|
5
externals/CMakeLists.txt
vendored
5
externals/CMakeLists.txt
vendored
@ -47,6 +47,11 @@ if (NOT TARGET FFmpeg::ffmpeg)
|
|||||||
add_library(FFmpeg::ffmpeg ALIAS ffmpeg)
|
add_library(FFmpeg::ffmpeg ALIAS ffmpeg)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# LibAtrac9
|
||||||
|
file(GLOB LIBATRAC9_SOURCES LibAtrac9/C/src/*.c)
|
||||||
|
add_library(LibAtrac9 STATIC ${LIBATRAC9_SOURCES})
|
||||||
|
target_include_directories(LibAtrac9 INTERFACE LibAtrac9/C/src)
|
||||||
|
|
||||||
# Zlib-Ng
|
# Zlib-Ng
|
||||||
if (NOT TARGET zlib-ng::zlib)
|
if (NOT TARGET zlib-ng::zlib)
|
||||||
set(ZLIB_ENABLE_TESTS OFF)
|
set(ZLIB_ENABLE_TESTS OFF)
|
||||||
|
1
externals/LibAtrac9
vendored
Submodule
1
externals/LibAtrac9
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 3acdcdc78f129c2e6145331ff650fa76dd88d62c
|
63
src/common/slot_array.h
Normal file
63
src/common/slot_array.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
// #include <boost/icl/interval_set.hpp>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <class IndexType, class ResourceType,
|
||||||
|
IndexType MaxIndex = std::numeric_limits<IndexType>::max(), IndexType MinIndex = 0>
|
||||||
|
class SlotArray {
|
||||||
|
public:
|
||||||
|
SlotArray() {
|
||||||
|
std::iota(m_free_indices.begin(), m_free_indices.end(), MinIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Types>
|
||||||
|
std::optional<IndexType> Create(Types&&... args) {
|
||||||
|
if (!HasFreeSlots()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto index = m_free_indices[m_curr_cursor];
|
||||||
|
m_resources[index - MinIndex] = ResourceType(std::forward<Types>(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<IndexType, MaxIndex - MinIndex> m_free_indices;
|
||||||
|
std::array<std::optional<ResourceType>, MaxIndex - MinIndex> m_resources;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -1,18 +1,46 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "ajm.h"
|
|
||||||
#include "ajm_error.h"
|
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/libraries/ajm/ajm.h"
|
||||||
|
#include "core/libraries/ajm/ajm_context.h"
|
||||||
|
#include "core/libraries/ajm/ajm_error.h"
|
||||||
|
#include "core/libraries/ajm/ajm_mp3.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
|
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchCancel() {
|
constexpr int ORBIS_AJM_CHANNELMASK_MONO = 0x0004;
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
constexpr int ORBIS_AJM_CHANNELMASK_STEREO = 0x0003;
|
||||||
return ORBIS_OK;
|
constexpr int ORBIS_AJM_CHANNELMASK_QUAD = 0x0033;
|
||||||
|
constexpr int ORBIS_AJM_CHANNELMASK_5POINT1 = 0x060F;
|
||||||
|
constexpr int ORBIS_AJM_CHANNELMASK_7POINT1 = 0x063F;
|
||||||
|
|
||||||
|
static std::unique_ptr<AjmContext> context{};
|
||||||
|
|
||||||
|
u32 GetChannelMask(u32 num_channels) {
|
||||||
|
switch (num_channels) {
|
||||||
|
case 1:
|
||||||
|
return ORBIS_AJM_CHANNELMASK_MONO;
|
||||||
|
case 2:
|
||||||
|
return ORBIS_AJM_CHANNELMASK_STEREO;
|
||||||
|
case 4:
|
||||||
|
return ORBIS_AJM_CHANNELMASK_QUAD;
|
||||||
|
case 6:
|
||||||
|
return ORBIS_AJM_CHANNELMASK_5POINT1;
|
||||||
|
case 8:
|
||||||
|
return ORBIS_AJM_CHANNELMASK_7POINT1;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id) {
|
||||||
|
LOG_INFO(Lib_Ajm, "called context_id = {} batch_id = {}", context_id, batch_id);
|
||||||
|
return context->BatchCancel(batch_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchErrorDump() {
|
int PS4_SYSV_ABI sceAjmBatchErrorDump() {
|
||||||
@ -20,34 +48,56 @@ int PS4_SYSV_ABI sceAjmBatchErrorDump() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobControlBufferRa() {
|
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
void* p_sideband_input, size_t sideband_input_size,
|
||||||
return ORBIS_OK;
|
void* p_sideband_output,
|
||||||
|
size_t sideband_output_size,
|
||||||
|
void* p_return_address) {
|
||||||
|
return BatchJobControlBufferRa(p_buffer, instance_id, flags, p_sideband_input,
|
||||||
|
sideband_input_size, p_sideband_output, sideband_output_size,
|
||||||
|
p_return_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobInlineBuffer() {
|
void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(void* p_buffer, const void* p_data_input,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
size_t data_input_size,
|
||||||
return ORBIS_OK;
|
const void** pp_batch_address) {
|
||||||
|
return BatchJobInlineBuffer(p_buffer, p_data_input, data_input_size, pp_batch_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobRunBufferRa() {
|
void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
void* p_data_input, size_t data_input_size,
|
||||||
return ORBIS_OK;
|
void* p_data_output, size_t data_output_size,
|
||||||
|
void* p_sideband_output, size_t sideband_output_size,
|
||||||
|
void* p_return_address) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa() {
|
void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
void* p_buffer, u32 instance_id, u64 flags, const AjmBuffer* p_data_input_buffers,
|
||||||
return ORBIS_OK;
|
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) {
|
||||||
|
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() {
|
int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context_id, u8* p_batch, u32 batch_size,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
const int priority, AjmBatchError* batch_error,
|
||||||
return ORBIS_OK;
|
u32* out_batch_id) {
|
||||||
|
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() {
|
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context_id, const u32 batch_id, const u32 timeout,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
AjmBatchError* const batch_error) {
|
||||||
return ORBIS_OK;
|
LOG_TRACE(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context_id, batch_id,
|
||||||
|
timeout);
|
||||||
|
return context->BatchWait(batch_id, timeout, batch_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() {
|
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() {
|
||||||
@ -55,9 +105,9 @@ int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmDecMp3ParseFrame() {
|
int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
AjmDecMp3ParseFrame* frame) {
|
||||||
return ORBIS_OK;
|
return AjmMp3Decoder::ParseMp3Header(buf, stream_size, parse_ofl, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmFinalize() {
|
int PS4_SYSV_ABI sceAjmFinalize() {
|
||||||
@ -65,8 +115,14 @@ int PS4_SYSV_ABI sceAjmFinalize() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInitialize() {
|
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
LOG_INFO(Lib_Ajm, "called reserved = {}", reserved);
|
||||||
|
ASSERT_MSG(context == nullptr, "Multiple contexts are currently unsupported.");
|
||||||
|
if (p_context_id == nullptr || reserved != 0) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
*p_context_id = 1;
|
||||||
|
context = std::make_unique<AjmContext>();
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,14 +131,16 @@ int PS4_SYSV_ABI sceAjmInstanceCodecType() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInstanceCreate() {
|
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
AjmInstanceFlags flags, u32* out_instance) {
|
||||||
return ORBIS_OK;
|
LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, flags = {:#x}", context_id,
|
||||||
|
magic_enum::enum_name(codec_type), flags.raw);
|
||||||
|
return context->InstanceCreate(codec_type, flags, out_instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInstanceDestroy() {
|
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context_id, u32 instance_id) {
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
LOG_INFO(Lib_Ajm, "called context = {}, instance = {}", context_id, instance_id);
|
||||||
return ORBIS_OK;
|
return context->InstanceDestroy(instance_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmInstanceExtend() {
|
int PS4_SYSV_ABI sceAjmInstanceExtend() {
|
||||||
@ -105,9 +163,12 @@ int PS4_SYSV_ABI sceAjmMemoryUnregister() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmModuleRegister() {
|
int PS4_SYSV_ABI sceAjmModuleRegister(u32 context_id, AjmCodecType codec_type, s64 reserved) {
|
||||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}", context_id, u32(codec_type));
|
||||||
return ORBIS_OK;
|
if (reserved != 0) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
return context->ModuleRegister(codec_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmModuleUnregister() {
|
int PS4_SYSV_ABI sceAjmModuleUnregister() {
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/enum.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Core::Loader {
|
namespace Core::Loader {
|
||||||
@ -11,26 +13,174 @@ class SymbolsResolver;
|
|||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchCancel();
|
constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4;
|
||||||
|
constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000;
|
||||||
|
|
||||||
|
enum class AjmCodecType : u32 {
|
||||||
|
Mp3Dec = 0,
|
||||||
|
At9Dec = 1,
|
||||||
|
M4aacDec = 2,
|
||||||
|
Max = 23,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType);
|
||||||
|
|
||||||
|
struct AjmBatchInfo {
|
||||||
|
void* pBuffer;
|
||||||
|
u64 offset;
|
||||||
|
u64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmBatchError {
|
||||||
|
int error_code;
|
||||||
|
const void* job_addr;
|
||||||
|
u32 cmd_offset;
|
||||||
|
const void* job_ra;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmBuffer {
|
||||||
|
u8* p_address;
|
||||||
|
u64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AjmJobControlFlags : u64 {
|
||||||
|
Reset = 1 << 0,
|
||||||
|
Initialize = 1 << 1,
|
||||||
|
Resample = 1 << 2,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(AjmJobControlFlags)
|
||||||
|
|
||||||
|
enum class AjmJobRunFlags : u64 {
|
||||||
|
GetCodecInfo = 1 << 0,
|
||||||
|
MultipleFrames = 1 << 1,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(AjmJobRunFlags)
|
||||||
|
|
||||||
|
enum class AjmJobSidebandFlags : u64 {
|
||||||
|
GaplessDecode = 1 << 0,
|
||||||
|
Format = 1 << 1,
|
||||||
|
Stream = 1 << 2,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(AjmJobSidebandFlags)
|
||||||
|
|
||||||
|
union AjmJobFlags {
|
||||||
|
u64 raw;
|
||||||
|
struct {
|
||||||
|
u64 version : 3;
|
||||||
|
u64 codec : 8;
|
||||||
|
AjmJobRunFlags run_flags : 2;
|
||||||
|
AjmJobControlFlags control_flags : 3;
|
||||||
|
u64 reserved : 29;
|
||||||
|
AjmJobSidebandFlags sideband_flags : 3;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 AjmSidebandInitParameters {
|
||||||
|
AjmDecAt9InitializeParameters at9;
|
||||||
|
u8 reserved[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
union AjmInstanceFlags {
|
||||||
|
u64 raw;
|
||||||
|
struct {
|
||||||
|
u64 version : 3;
|
||||||
|
u64 channels : 4;
|
||||||
|
u64 format : 3;
|
||||||
|
u64 gapless_loop : 1;
|
||||||
|
u64 : 21;
|
||||||
|
u64 codec : 28;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(AjmInstanceFlags) == 8);
|
||||||
|
|
||||||
|
struct AjmDecMp3ParseFrame;
|
||||||
|
|
||||||
|
u32 GetChannelMask(u32 num_channels);
|
||||||
|
|
||||||
|
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id);
|
||||||
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobControlBufferRa();
|
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags,
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobInlineBuffer();
|
void* p_sideband_input, size_t sideband_input_size,
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobRunBufferRa();
|
void* p_sideband_output,
|
||||||
int PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa();
|
size_t sideband_output_size,
|
||||||
int PS4_SYSV_ABI sceAjmBatchStartBuffer();
|
void* p_return_address);
|
||||||
int PS4_SYSV_ABI sceAjmBatchWait();
|
void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(void* p_buffer, const void* p_data_input,
|
||||||
|
size_t data_input_size,
|
||||||
|
const void** pp_batch_address);
|
||||||
|
void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(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* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(
|
||||||
|
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);
|
||||||
|
int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, u8* batch, u32 batch_size, const int priority,
|
||||||
|
AjmBatchError* batch_error, u32* out_batch_id);
|
||||||
|
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout,
|
||||||
|
AjmBatchError* const batch_error);
|
||||||
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData();
|
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData();
|
||||||
int PS4_SYSV_ABI sceAjmDecMp3ParseFrame();
|
int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int parse_ofl,
|
||||||
|
AjmDecMp3ParseFrame* frame);
|
||||||
int PS4_SYSV_ABI sceAjmFinalize();
|
int PS4_SYSV_ABI sceAjmFinalize();
|
||||||
int PS4_SYSV_ABI sceAjmInitialize();
|
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context);
|
||||||
int PS4_SYSV_ABI sceAjmInstanceCodecType();
|
int PS4_SYSV_ABI sceAjmInstanceCodecType();
|
||||||
int PS4_SYSV_ABI sceAjmInstanceCreate();
|
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||||
int PS4_SYSV_ABI sceAjmInstanceDestroy();
|
u32* instance);
|
||||||
|
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance);
|
||||||
int PS4_SYSV_ABI sceAjmInstanceExtend();
|
int PS4_SYSV_ABI sceAjmInstanceExtend();
|
||||||
int PS4_SYSV_ABI sceAjmInstanceSwitch();
|
int PS4_SYSV_ABI sceAjmInstanceSwitch();
|
||||||
int PS4_SYSV_ABI sceAjmMemoryRegister();
|
int PS4_SYSV_ABI sceAjmMemoryRegister();
|
||||||
int PS4_SYSV_ABI sceAjmMemoryUnregister();
|
int PS4_SYSV_ABI sceAjmMemoryUnregister();
|
||||||
int PS4_SYSV_ABI sceAjmModuleRegister();
|
int PS4_SYSV_ABI sceAjmModuleRegister(u32 context, AjmCodecType codec_type, s64 reserved);
|
||||||
int PS4_SYSV_ABI sceAjmModuleUnregister();
|
int PS4_SYSV_ABI sceAjmModuleUnregister();
|
||||||
int PS4_SYSV_ABI sceAjmStrError();
|
int PS4_SYSV_ABI sceAjmStrError();
|
||||||
|
|
||||||
|
143
src/core/libraries/ajm/ajm_at9.cpp
Normal file
143
src/core/libraries/ajm/ajm_at9.cpp
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "core/libraries/ajm/ajm_at9.h"
|
||||||
|
#include "error_codes.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <decoder.h>
|
||||||
|
#include <libatrac9.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
|
||||||
|
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {
|
||||||
|
ASSERT_MSG(m_handle, "Atrac9GetHandle failed");
|
||||||
|
AjmAt9Decoder::Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
AjmAt9Decoder::~AjmAt9Decoder() {
|
||||||
|
Atrac9ReleaseHandle(m_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmAt9Decoder::Reset() {
|
||||||
|
Atrac9ReleaseHandle(m_handle);
|
||||||
|
m_handle = Atrac9GetHandle();
|
||||||
|
Atrac9InitDecoder(m_handle, m_config_data);
|
||||||
|
Atrac9GetCodecInfo(m_handle, &m_codec_info);
|
||||||
|
|
||||||
|
m_num_frames = 0;
|
||||||
|
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
|
||||||
|
ASSERT_MSG(buffer_size == sizeof(AjmDecAt9InitializeParameters),
|
||||||
|
"Incorrect At9 initialization buffer size {}", buffer_size);
|
||||||
|
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
|
||||||
|
std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE);
|
||||||
|
AjmAt9Decoder::Reset();
|
||||||
|
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPointCodeSize(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 AjmAt9Decoder::GetPointCodeSize() {
|
||||||
|
switch (m_format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
return sizeof(s16);
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
return sizeof(s32);
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
return sizeof(float);
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmAt9Decoder::GetInfo(void* out_info) {
|
||||||
|
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
||||||
|
info->super_frame_size = m_codec_info.superframeSize;
|
||||||
|
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
|
||||||
|
info->frame_samples = m_codec_info.frameSamples;
|
||||||
|
info->next_frame_size = static_cast<Atrac9Handle*>(m_handle)->Config.FrameBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||||
|
AjmSidebandGaplessDecode& gapless,
|
||||||
|
std::optional<u32> max_samples_per_channel) {
|
||||||
|
int ret = 0;
|
||||||
|
int bytes_used = 0;
|
||||||
|
switch (m_format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
ret = Atrac9Decode(m_handle, in_buf.data(), reinterpret_cast<s16*>(m_pcm_buffer.data()),
|
||||||
|
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
ret = Atrac9DecodeS32(m_handle, in_buf.data(), reinterpret_cast<s32*>(m_pcm_buffer.data()),
|
||||||
|
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
ret =
|
||||||
|
Atrac9DecodeF32(m_handle, in_buf.data(), reinterpret_cast<float*>(m_pcm_buffer.data()),
|
||||||
|
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||||
|
in_buf = in_buf.subspan(bytes_used);
|
||||||
|
|
||||||
|
m_superframe_bytes_remain -= bytes_used;
|
||||||
|
|
||||||
|
u32 skipped_samples = 0;
|
||||||
|
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||||
|
skipped_samples = std::min(u32(m_codec_info.frameSamples),
|
||||||
|
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||||
|
gapless.skipped_samples += skipped_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto max_samples = max_samples_per_channel.has_value()
|
||||||
|
? max_samples_per_channel.value() * m_codec_info.channels
|
||||||
|
: std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
size_t samples_written = 0;
|
||||||
|
switch (m_format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
samples_written = WriteOutputSamples<s16>(output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
samples_written = WriteOutputSamples<s32>(output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
samples_written = WriteOutputSamples<float>(output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_num_frames += 1;
|
||||||
|
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
|
||||||
|
if (m_superframe_bytes_remain) {
|
||||||
|
in_buf = in_buf.subspan(m_superframe_bytes_remain);
|
||||||
|
}
|
||||||
|
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||||
|
m_num_frames = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {1, samples_written / m_codec_info.channels};
|
||||||
|
}
|
||||||
|
|
||||||
|
AjmSidebandFormat AjmAt9Decoder::GetFormat() {
|
||||||
|
return AjmSidebandFormat{
|
||||||
|
.num_channels = u32(m_codec_info.channels),
|
||||||
|
.channel_mask = GetChannelMask(u32(m_codec_info.channels)),
|
||||||
|
.sampl_freq = u32(m_codec_info.samplingRate),
|
||||||
|
.sample_encoding = m_format,
|
||||||
|
.bitrate = u32(m_codec_info.samplingRate * GetPointCodeSize() * 8),
|
||||||
|
.reserved = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
64
src/core/libraries/ajm/ajm_at9.h
Normal file
64
src/core/libraries/ajm/ajm_at9.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
|
||||||
|
#include "libatrac9.h"
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
||||||
|
|
||||||
|
enum AjmAt9CodecFlags : u32 {
|
||||||
|
ParseRiffHeader = 1 << 0,
|
||||||
|
NonInterleavedOutput = 1 << 8,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(AjmAt9CodecFlags)
|
||||||
|
|
||||||
|
struct AjmSidebandDecAt9CodecInfo {
|
||||||
|
u32 super_frame_size;
|
||||||
|
u32 frames_in_super_frame;
|
||||||
|
u32 next_frame_size;
|
||||||
|
u32 frame_samples;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmAt9Decoder final : AjmCodec {
|
||||||
|
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
|
||||||
|
~AjmAt9Decoder() override;
|
||||||
|
|
||||||
|
void Reset() override;
|
||||||
|
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||||
|
void GetInfo(void* out_info) override;
|
||||||
|
AjmSidebandFormat GetFormat() override;
|
||||||
|
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
|
AjmSidebandGaplessDecode& gapless,
|
||||||
|
std::optional<u32> max_samples) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8 GetPointCodeSize();
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) {
|
||||||
|
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),
|
||||||
|
m_pcm_buffer.size() / sizeof(T)};
|
||||||
|
pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels);
|
||||||
|
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
|
||||||
|
return output.Write(pcm_data.subspan(0, pcm_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
const AjmFormatEncoding m_format;
|
||||||
|
const AjmAt9CodecFlags m_flags;
|
||||||
|
void* m_handle{};
|
||||||
|
u8 m_config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{};
|
||||||
|
u32 m_superframe_bytes_remain{};
|
||||||
|
u32 m_num_frames{};
|
||||||
|
Atrac9CodecInfo m_codec_info{};
|
||||||
|
std::vector<u8> m_pcm_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
461
src/core/libraries/ajm/ajm_batch.cpp
Normal file
461
src/core/libraries/ajm/ajm_batch.cpp
Normal file
@ -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 s_dynamic_extent = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
AjmBatchBuffer(u8* begin, u8* end)
|
||||||
|
: m_p_begin(begin), m_p_current(begin), m_size(end - begin) {}
|
||||||
|
AjmBatchBuffer(u8* begin, size_t size = s_dynamic_extent)
|
||||||
|
: m_p_begin(begin), m_p_current(m_p_begin), m_size(size) {}
|
||||||
|
AjmBatchBuffer(std::span<u8> data)
|
||||||
|
: m_p_begin(data.data()), m_p_current(m_p_begin), m_size(data.size()) {}
|
||||||
|
|
||||||
|
AjmBatchBuffer SubBuffer(size_t size = s_dynamic_extent) {
|
||||||
|
auto current = m_p_current;
|
||||||
|
if (size != s_dynamic_extent) {
|
||||||
|
m_p_current += size;
|
||||||
|
}
|
||||||
|
return AjmBatchBuffer(current, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T& Peek() const {
|
||||||
|
DEBUG_ASSERT(m_size == s_dynamic_extent ||
|
||||||
|
(m_p_current + sizeof(T)) <= (m_p_begin + m_size));
|
||||||
|
return *reinterpret_cast<T*>(m_p_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T& Consume() {
|
||||||
|
auto* const result = reinterpret_cast<T*>(m_p_current);
|
||||||
|
m_p_current += sizeof(T);
|
||||||
|
DEBUG_ASSERT(m_size == s_dynamic_extent || m_p_current <= (m_p_begin + m_size));
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void Skip() {
|
||||||
|
Advance(sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Advance(size_t size) {
|
||||||
|
m_p_current += size;
|
||||||
|
DEBUG_ASSERT(m_size == s_dynamic_extent || m_p_current <= (m_p_begin + m_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEmpty() {
|
||||||
|
return m_size != s_dynamic_extent && 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 == s_dynamic_extent) {
|
||||||
|
return s_dynamic_extent;
|
||||||
|
}
|
||||||
|
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<AjmJobFlags> job_flags = {};
|
||||||
|
std::optional<AjmChunkBuffer> input_control_buffer = {};
|
||||||
|
std::optional<AjmChunkBuffer> output_control_buffer = {};
|
||||||
|
std::optional<AjmChunkBuffer> inline_buffer = {};
|
||||||
|
|
||||||
|
AjmJob job;
|
||||||
|
job.instance_id = instance_id;
|
||||||
|
|
||||||
|
// Read parameters of a job
|
||||||
|
while (!batch_buffer.IsEmpty()) {
|
||||||
|
auto& header = batch_buffer.Peek<AjmChunkHeader>();
|
||||||
|
switch (header.ident) {
|
||||||
|
case Identifier::AjmIdentInputRunBuf: {
|
||||||
|
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
u8* p_begin = reinterpret_cast<u8*>(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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkFlags>();
|
||||||
|
job_flags = AjmJobFlags{
|
||||||
|
.raw = (u64(chunk.header.payload) << 32) + chunk.flags_low,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentReturnAddressBuf: {
|
||||||
|
// Ignore return address buffers.
|
||||||
|
batch_buffer.Skip<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Identifier::AjmIdentOutputRunBuf: {
|
||||||
|
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
|
||||||
|
job.output.buffers.emplace_back(std::span<u8>(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<AjmChunkBuffer>();
|
||||||
|
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<u8*>(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<AjmSidebandFormat>();
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && !input_batch.IsEmpty()) {
|
||||||
|
job.input.gapless_decode = input_batch.Consume<AjmSidebandGaplessDecode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<u8*>(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<AjmSidebandResampleParameters>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sideband output parameters
|
||||||
|
if (output_control_buffer.has_value()) {
|
||||||
|
AjmBatchBuffer output_batch(reinterpret_cast<u8*>(output_control_buffer->p_address),
|
||||||
|
output_control_buffer->size);
|
||||||
|
|
||||||
|
job.output.p_result = &output_batch.Consume<AjmSidebandResult>();
|
||||||
|
*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<AjmSidebandStream>();
|
||||||
|
*job.output.p_stream = AjmSidebandStream{};
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::Format) && !output_batch.IsEmpty()) {
|
||||||
|
job.output.p_format = &output_batch.Consume<AjmSidebandFormat>();
|
||||||
|
*job.output.p_format = AjmSidebandFormat{};
|
||||||
|
}
|
||||||
|
if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode) && !output_batch.IsEmpty()) {
|
||||||
|
job.output.p_gapless_decode = &output_batch.Consume<AjmSidebandGaplessDecode>();
|
||||||
|
*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<AjmSidebandMFrame>();
|
||||||
|
*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> AjmBatch::FromBatchBuffer(std::span<u8> data) {
|
||||||
|
auto batch = std::make_shared<AjmBatch>();
|
||||||
|
|
||||||
|
AjmBatchBuffer buffer(data);
|
||||||
|
while (!buffer.IsEmpty()) {
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
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<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkFlags>();
|
||||||
|
chunk_flags.header.ident = AjmIdentControlFlags;
|
||||||
|
chunk_flags.header.payload = u32(flags >> 32);
|
||||||
|
chunk_flags.flags_low = u32(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
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<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
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<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkFlags>();
|
||||||
|
chunk_flags.header.ident = AjmIdentRunFlags;
|
||||||
|
chunk_flags.header.payload = u32(flags >> 32);
|
||||||
|
chunk_flags.flags_low = u32(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto& chunk_output = job_buffer.Consume<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<u8*>(p_buffer));
|
||||||
|
|
||||||
|
auto& job_chunk = buffer.Consume<AjmChunkJob>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkFlags>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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<AjmChunkBuffer>();
|
||||||
|
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
|
74
src/core/libraries/ajm/ajm_batch.h
Normal file
74
src/core/libraries/ajm/ajm_batch.h
Normal file
@ -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 <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <semaphore>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
struct AjmJob {
|
||||||
|
struct Input {
|
||||||
|
std::optional<AjmDecAt9InitializeParameters> init_params;
|
||||||
|
std::optional<AjmSidebandResampleParameters> resample_parameters;
|
||||||
|
std::optional<AjmSidebandFormat> format;
|
||||||
|
std::optional<AjmSidebandGaplessDecode> gapless_decode;
|
||||||
|
std::vector<u8> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
boost::container::small_vector<std::span<u8>, 8> 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<AjmJob, 16> jobs;
|
||||||
|
|
||||||
|
static std::shared_ptr<AjmBatch> FromBatchBuffer(std::span<u8> 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
|
174
src/core/libraries/ajm/ajm_context.cpp
Normal file
174
src/core/libraries/ajm/ajm_context.cpp
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
// 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.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 <span>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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<AjmBatch> 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<AjmJob> jobs) {
|
||||||
|
// Perform operation requested by control flags.
|
||||||
|
for (auto& job : jobs) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, job.instance_id,
|
||||||
|
job.flags.raw);
|
||||||
|
|
||||||
|
std::shared_ptr<AjmInstance> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->ExecuteJob(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 AjmContext::BatchWait(const u32 batch_id, const u32 timeout, AjmBatchError* const batch_error) {
|
||||||
|
std::shared_ptr<AjmBatch> 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<u32> 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;
|
||||||
|
}
|
||||||
|
std::optional<u32> opt_index;
|
||||||
|
{
|
||||||
|
std::unique_lock lock(instances_mutex);
|
||||||
|
opt_index = instances.Create(std::move(std::make_unique<AjmInstance>(codec_type, flags)));
|
||||||
|
}
|
||||||
|
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
|
57
src/core/libraries/ajm/ajm_context.h
Normal file
57
src/core/libraries/ajm/ajm_context.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// 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 <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <span>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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<AjmJob> jobs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u32 MaxInstances = 0x2fff;
|
||||||
|
static constexpr u32 MaxBatches = 0x0400;
|
||||||
|
static constexpr u32 NumAjmCodecs = std::to_underlying(AjmCodecType::Max);
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsRegistered(AjmCodecType type) const;
|
||||||
|
|
||||||
|
std::array<bool, NumAjmCodecs> registered_codecs{};
|
||||||
|
|
||||||
|
std::shared_mutex instances_mutex;
|
||||||
|
Common::SlotArray<u32, std::shared_ptr<AjmInstance>, MaxInstances, 1> instances;
|
||||||
|
|
||||||
|
std::shared_mutex batches_mutex;
|
||||||
|
Common::SlotArray<u32, std::shared_ptr<AjmBatch>, MaxBatches, 1> batches;
|
||||||
|
|
||||||
|
std::jthread worker_thread{};
|
||||||
|
Common::MPSCQueue<std::shared_ptr<AjmBatch>> batch_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
120
src/core/libraries/ajm/ajm_instance.cpp
Normal file
120
src/core/libraries/ajm/ajm_instance.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "core/libraries/ajm/ajm_at9.h"
|
||||||
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
#include "core/libraries/ajm/ajm_mp3.h"
|
||||||
|
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||||
|
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||||
|
|
||||||
|
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||||
|
switch (codec_type) {
|
||||||
|
case AjmCodecType::At9Dec: {
|
||||||
|
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
|
||||||
|
AjmAt9CodecFlags(flags.codec));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AjmCodecType::Mp3Dec: {
|
||||||
|
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unimplemented codec type {}", magic_enum::enum_name(codec_type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||||
|
const auto control_flags = job.flags.control_flags;
|
||||||
|
if (True(control_flags & AjmJobControlFlags::Reset)) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
|
||||||
|
m_format = {};
|
||||||
|
m_gapless = {};
|
||||||
|
m_resample_parameters = {};
|
||||||
|
m_gapless_samples = 0;
|
||||||
|
m_total_samples = 0;
|
||||||
|
m_codec->Reset();
|
||||||
|
}
|
||||||
|
if (job.input.init_params.has_value()) {
|
||||||
|
LOG_TRACE(Lib_Ajm, "Initializing instance {}", job.instance_id);
|
||||||
|
auto& params = job.input.init_params.value();
|
||||||
|
m_codec->Initialize(¶ms, sizeof(params));
|
||||||
|
}
|
||||||
|
if (job.input.resample_parameters.has_value()) {
|
||||||
|
UNREACHABLE_MSG("Unimplemented: resample parameters");
|
||||||
|
m_resample_parameters = job.input.resample_parameters.value();
|
||||||
|
}
|
||||||
|
if (job.input.format.has_value()) {
|
||||||
|
UNREACHABLE_MSG("Unimplemented: format parameters");
|
||||||
|
m_format = job.input.format.value();
|
||||||
|
}
|
||||||
|
if (job.input.gapless_decode.has_value()) {
|
||||||
|
auto& params = job.input.gapless_decode.value();
|
||||||
|
m_gapless.total_samples = params.total_samples;
|
||||||
|
m_gapless.skip_samples = params.skip_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
|
||||||
|
u32 frames_decoded = 0;
|
||||||
|
std::span<u8> in_buf(job.input.buffer);
|
||||||
|
SparseOutputBuffer out_buf(job.output.buffers);
|
||||||
|
|
||||||
|
auto in_size = in_buf.size();
|
||||||
|
auto out_size = out_buf.Size();
|
||||||
|
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
|
||||||
|
const auto samples_remain =
|
||||||
|
m_gapless.total_samples != 0
|
||||||
|
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples}
|
||||||
|
: std::optional<u32>{};
|
||||||
|
const auto [nframes, nsamples] =
|
||||||
|
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
|
||||||
|
frames_decoded += nframes;
|
||||||
|
m_total_samples += nsamples;
|
||||||
|
m_gapless_samples += nsamples;
|
||||||
|
}
|
||||||
|
if (job.output.p_mframe) {
|
||||||
|
job.output.p_mframe->num_frames = frames_decoded;
|
||||||
|
}
|
||||||
|
if (job.output.p_stream) {
|
||||||
|
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||||
|
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||||
|
job.output.p_stream->total_decoded_samples = m_total_samples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_flags.gapless_loop && m_gapless.total_samples != 0 &&
|
||||||
|
m_gapless_samples >= m_gapless.total_samples) {
|
||||||
|
m_gapless_samples = 0;
|
||||||
|
m_gapless.skipped_samples = 0;
|
||||||
|
m_codec->Reset();
|
||||||
|
}
|
||||||
|
if (job.output.p_format != nullptr) {
|
||||||
|
*job.output.p_format = m_codec->GetFormat();
|
||||||
|
}
|
||||||
|
if (job.output.p_gapless_decode != nullptr) {
|
||||||
|
*job.output.p_gapless_decode = m_gapless;
|
||||||
|
}
|
||||||
|
if (job.output.p_codec_info != nullptr) {
|
||||||
|
m_codec->GetInfo(job.output.p_codec_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AjmInstance::IsGaplessEnd() {
|
||||||
|
return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
89
src/core/libraries/ajm/ajm_instance.h
Normal file
89
src/core/libraries/ajm/ajm_instance.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/enum.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/ajm/ajm.h"
|
||||||
|
#include "core/libraries/ajm/ajm_batch.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
class SparseOutputBuffer {
|
||||||
|
public:
|
||||||
|
SparseOutputBuffer(std::span<std::span<u8>> chunks)
|
||||||
|
: m_chunks(chunks), m_current(m_chunks.begin()) {}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
size_t Write(std::span<T> pcm) {
|
||||||
|
size_t samples_written = 0;
|
||||||
|
while (!pcm.empty() && !IsEmpty()) {
|
||||||
|
auto size = std::min(pcm.size() * sizeof(T), m_current->size());
|
||||||
|
std::memcpy(m_current->data(), pcm.data(), size);
|
||||||
|
const auto nsamples = size / sizeof(T);
|
||||||
|
samples_written += nsamples;
|
||||||
|
pcm = pcm.subspan(nsamples);
|
||||||
|
*m_current = m_current->subspan(size);
|
||||||
|
if (m_current->empty()) {
|
||||||
|
++m_current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return samples_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEmpty() {
|
||||||
|
return m_current == m_chunks.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Size() {
|
||||||
|
size_t result = 0;
|
||||||
|
for (auto it = m_current; it != m_chunks.end(); ++it) {
|
||||||
|
result += it->size();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::span<std::span<u8>> m_chunks;
|
||||||
|
std::span<std::span<u8>>::iterator m_current;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AjmCodec {
|
||||||
|
public:
|
||||||
|
virtual ~AjmCodec() = default;
|
||||||
|
|
||||||
|
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
|
||||||
|
virtual void Reset() = 0;
|
||||||
|
virtual void GetInfo(void* out_info) = 0;
|
||||||
|
virtual AjmSidebandFormat GetFormat() = 0;
|
||||||
|
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
|
AjmSidebandGaplessDecode& gapless,
|
||||||
|
std::optional<u32> max_samples_per_channel) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AjmInstance {
|
||||||
|
public:
|
||||||
|
AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags);
|
||||||
|
|
||||||
|
void ExecuteJob(AjmJob& job);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool IsGaplessEnd();
|
||||||
|
|
||||||
|
AjmInstanceFlags m_flags{};
|
||||||
|
AjmSidebandFormat m_format{};
|
||||||
|
AjmSidebandGaplessDecode m_gapless{};
|
||||||
|
AjmSidebandResampleParameters m_resample_parameters{};
|
||||||
|
|
||||||
|
u32 m_gapless_samples{};
|
||||||
|
u32 m_total_samples{};
|
||||||
|
|
||||||
|
std::unique_ptr<AjmCodec> m_codec;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
200
src/core/libraries/ajm/ajm_mp3.cpp
Normal file
200
src/core/libraries/ajm/ajm_mp3.cpp
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "core/libraries/ajm/ajm_error.h"
|
||||||
|
#include "core/libraries/ajm/ajm_mp3.h"
|
||||||
|
#include "core/libraries/error_codes.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <libswresample/swresample.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
// Following tables have been reversed from AJM library
|
||||||
|
static constexpr std::array<std::array<s32, 3>, 3> SamplerateTable = {{
|
||||||
|
{0x5622, 0x5DC0, 0x3E80},
|
||||||
|
{0xAC44, 0xBB80, 0x7D00},
|
||||||
|
{0x2B11, 0x2EE0, 0x1F40},
|
||||||
|
}};
|
||||||
|
|
||||||
|
static constexpr std::array<std::array<s32, 15>, 2> BitrateTable = {{
|
||||||
|
{0, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0xA0, 0xC0, 0xE0, 0x100, 0x140},
|
||||||
|
{0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0},
|
||||||
|
}};
|
||||||
|
|
||||||
|
static constexpr std::array<s32, 2> UnkTable = {0x48, 0x90};
|
||||||
|
|
||||||
|
SwrContext* swr_context{};
|
||||||
|
|
||||||
|
static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) {
|
||||||
|
switch (format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
return AV_SAMPLE_FMT_S16;
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
return AV_SAMPLE_FMT_S32;
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
return AV_SAMPLE_FMT_FLT;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||||
|
AVSampleFormat format = AjmToAVSampleFormat(m_format);
|
||||||
|
if (frame->format == format) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pcm16_frame = av_frame_clone(frame);
|
||||||
|
pcm16_frame->format = format;
|
||||||
|
|
||||||
|
if (swr_context) {
|
||||||
|
swr_free(&swr_context);
|
||||||
|
swr_context = nullptr;
|
||||||
|
}
|
||||||
|
AVChannelLayout in_ch_layout = frame->ch_layout;
|
||||||
|
AVChannelLayout out_ch_layout = pcm16_frame->ch_layout;
|
||||||
|
swr_alloc_set_opts2(&swr_context, &out_ch_layout, AVSampleFormat(pcm16_frame->format),
|
||||||
|
frame->sample_rate, &in_ch_layout, AVSampleFormat(frame->format),
|
||||||
|
frame->sample_rate, 0, nullptr);
|
||||||
|
swr_init(swr_context);
|
||||||
|
const auto res = swr_convert_frame(swr_context, pcm16_frame, frame);
|
||||||
|
if (res < 0) {
|
||||||
|
LOG_ERROR(Lib_AvPlayer, "Could not convert to S16: {}", av_err2str(res));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
av_frame_free(&frame);
|
||||||
|
return pcm16_frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format)
|
||||||
|
: m_format(format), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||||
|
m_parser(av_parser_init(m_codec->id)) {
|
||||||
|
AjmMp3Decoder::Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
AjmMp3Decoder::~AjmMp3Decoder() {
|
||||||
|
avcodec_free_context(&m_codec_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmMp3Decoder::Reset() {
|
||||||
|
if (m_codec_context) {
|
||||||
|
avcodec_free_context(&m_codec_context);
|
||||||
|
}
|
||||||
|
m_codec_context = avcodec_alloc_context3(m_codec);
|
||||||
|
ASSERT_MSG(m_codec_context, "Could not allocate audio m_codec context");
|
||||||
|
int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
|
||||||
|
ASSERT_MSG(ret >= 0, "Could not open m_codec");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AjmMp3Decoder::GetInfo(void* out_info) {
|
||||||
|
auto* info = reinterpret_cast<AjmSidebandDecMp3CodecInfo*>(out_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||||
|
AjmSidebandGaplessDecode& gapless,
|
||||||
|
std::optional<u32> max_samples_per_channel) {
|
||||||
|
AVPacket* pkt = av_packet_alloc();
|
||||||
|
|
||||||
|
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||||
|
in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||||
|
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||||
|
in_buf = in_buf.subspan(ret);
|
||||||
|
|
||||||
|
u32 frames_decoded = 0;
|
||||||
|
u32 samples_decoded = 0;
|
||||||
|
|
||||||
|
if (pkt->size) {
|
||||||
|
// Send the packet with the compressed data to the decoder
|
||||||
|
pkt->pts = m_parser->pts;
|
||||||
|
pkt->dts = m_parser->dts;
|
||||||
|
pkt->flags = (m_parser->key_frame == 1) ? AV_PKT_FLAG_KEY : 0;
|
||||||
|
ret = avcodec_send_packet(m_codec_context, pkt);
|
||||||
|
ASSERT_MSG(ret >= 0, "Error submitting the packet to the decoder {}", ret);
|
||||||
|
|
||||||
|
// Read all the output frames (in general there may be any number of them
|
||||||
|
while (ret >= 0) {
|
||||||
|
AVFrame* frame = av_frame_alloc();
|
||||||
|
ret = avcodec_receive_frame(m_codec_context, frame);
|
||||||
|
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||||
|
break;
|
||||||
|
} else if (ret < 0) {
|
||||||
|
UNREACHABLE_MSG("Error during decoding");
|
||||||
|
}
|
||||||
|
frame = ConvertAudioFrame(frame);
|
||||||
|
|
||||||
|
frames_decoded += 1;
|
||||||
|
u32 skipped_samples = 0;
|
||||||
|
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||||
|
skipped_samples = std::min(u32(frame->nb_samples),
|
||||||
|
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||||
|
gapless.skipped_samples += skipped_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto max_samples =
|
||||||
|
max_samples_per_channel.has_value()
|
||||||
|
? max_samples_per_channel.value() * frame->ch_layout.nb_channels
|
||||||
|
: std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
switch (m_format) {
|
||||||
|
case AjmFormatEncoding::S16:
|
||||||
|
samples_decoded +=
|
||||||
|
WriteOutputSamples<s16>(frame, output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::S32:
|
||||||
|
samples_decoded +=
|
||||||
|
WriteOutputSamples<s32>(frame, output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
case AjmFormatEncoding::Float:
|
||||||
|
samples_decoded +=
|
||||||
|
WriteOutputSamples<float>(frame, output, skipped_samples, max_samples);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
av_frame_free(&frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
av_packet_free(&pkt);
|
||||||
|
|
||||||
|
return {frames_decoded, samples_decoded};
|
||||||
|
}
|
||||||
|
|
||||||
|
int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
|
AjmDecMp3ParseFrame* frame) {
|
||||||
|
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||||
|
if (buf == nullptr || stream_size < 4 || frame == nullptr) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
if ((buf[0] & SYNCWORDH) != SYNCWORDH || (buf[1] & SYNCWORDL) != SYNCWORDL) {
|
||||||
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 unk_idx = buf[1] >> 3 & 1;
|
||||||
|
const s32 version_idx = (buf[1] >> 3 & 3) ^ 2;
|
||||||
|
const s32 sr_idx = buf[2] >> 2 & 3;
|
||||||
|
const s32 br_idx = (buf[2] >> 4) & 0xf;
|
||||||
|
const s32 padding_bit = (buf[2] >> 1) & 0x1;
|
||||||
|
|
||||||
|
frame->sample_rate = SamplerateTable[version_idx][sr_idx];
|
||||||
|
frame->bitrate = BitrateTable[version_idx != 1][br_idx] * 1000;
|
||||||
|
frame->num_channels = (buf[3] < 0xc0) + 1;
|
||||||
|
frame->frame_size = (UnkTable[unk_idx] * frame->bitrate) / frame->sample_rate + padding_bit;
|
||||||
|
frame->samples_per_channel = UnkTable[unk_idx] * 8;
|
||||||
|
frame->encoder_delay = 0;
|
||||||
|
|
||||||
|
return ORBIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
AjmSidebandFormat AjmMp3Decoder::GetFormat() {
|
||||||
|
LOG_ERROR(Lib_Ajm, "Unimplemented");
|
||||||
|
return AjmSidebandFormat{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
87
src/core/libraries/ajm/ajm_mp3.h
Normal file
87
src/core/libraries/ajm/ajm_mp3.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
enum class AjmDecMp3OflType : u32 { None = 0, Lame = 1, Vbri = 2, Fgh = 3, VbriAndFgh = 4 };
|
||||||
|
|
||||||
|
// 11-bit syncword if MPEG 2.5 extensions are enabled
|
||||||
|
static constexpr u8 SYNCWORDH = 0xff;
|
||||||
|
static constexpr u8 SYNCWORDL = 0xe0;
|
||||||
|
|
||||||
|
struct AjmDecMp3ParseFrame {
|
||||||
|
u64 frame_size;
|
||||||
|
u32 num_channels;
|
||||||
|
u32 samples_per_channel;
|
||||||
|
u32 bitrate;
|
||||||
|
u32 sample_rate;
|
||||||
|
u32 encoder_delay;
|
||||||
|
u32 num_frames;
|
||||||
|
u32 total_samples;
|
||||||
|
AjmDecMp3OflType ofl_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ChannelMode : u8 {
|
||||||
|
Stereo = 0,
|
||||||
|
JointStero = 1,
|
||||||
|
Dual = 2,
|
||||||
|
Mono = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AjmSidebandDecMp3CodecInfo {
|
||||||
|
u32 header;
|
||||||
|
bool has_crc;
|
||||||
|
ChannelMode channel_mode;
|
||||||
|
u8 mode_extension;
|
||||||
|
u8 copyright;
|
||||||
|
u8 original;
|
||||||
|
u8 emphasis;
|
||||||
|
u16 reserved[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
class AjmMp3Decoder : public AjmCodec {
|
||||||
|
public:
|
||||||
|
explicit AjmMp3Decoder(AjmFormatEncoding format);
|
||||||
|
~AjmMp3Decoder() override;
|
||||||
|
|
||||||
|
void Reset() override;
|
||||||
|
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||||
|
void GetInfo(void* out_info) override;
|
||||||
|
AjmSidebandFormat GetFormat() override;
|
||||||
|
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
|
AjmSidebandGaplessDecode& gapless,
|
||||||
|
std::optional<u32> max_samples_per_channel) override;
|
||||||
|
|
||||||
|
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
|
AjmDecMp3ParseFrame* frame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class T>
|
||||||
|
size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples,
|
||||||
|
u32 max_samples) {
|
||||||
|
const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(T);
|
||||||
|
std::span<T> pcm_data(reinterpret_cast<T*>(frame->data[0]), size >> 1);
|
||||||
|
pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels);
|
||||||
|
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
|
||||||
|
const auto samples_written = output.Write(pcm_data.subspan(0, pcm_size));
|
||||||
|
return samples_written / frame->ch_layout.nb_channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVFrame* ConvertAudioFrame(AVFrame* frame);
|
||||||
|
|
||||||
|
const AjmFormatEncoding m_format;
|
||||||
|
const AVCodec* m_codec = nullptr;
|
||||||
|
AVCodecContext* m_codec_context = nullptr;
|
||||||
|
AVCodecParserContext* m_parser = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::Ajm
|
@ -8,6 +8,17 @@
|
|||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
|
|
||||||
|
// The av_err2str macro in libavutil/error.h does not play nice with C++
|
||||||
|
#ifdef av_err2str
|
||||||
|
#undef av_err2str
|
||||||
|
#include <string>
|
||||||
|
av_always_inline std::string av_err2string(int errnum) {
|
||||||
|
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
||||||
|
return av_make_error_string(errbuf, AV_ERROR_MAX_STRING_SIZE, errnum);
|
||||||
|
}
|
||||||
|
#define av_err2str(err) av_err2string(err).c_str()
|
||||||
|
#endif // av_err2str
|
||||||
|
|
||||||
namespace Libraries::Vdec2 {
|
namespace Libraries::Vdec2 {
|
||||||
|
|
||||||
std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
|
std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
|
||||||
|
@ -274,7 +274,7 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string game_serial) {
|
void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string game_serial) {
|
||||||
constexpr std::array<SysModules, 12> ModulesToLoad{
|
constexpr std::array<SysModules, 11> ModulesToLoad{
|
||||||
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2},
|
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2},
|
||||||
{"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber},
|
{"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber},
|
||||||
{"libSceUlt.sprx", nullptr},
|
{"libSceUlt.sprx", nullptr},
|
||||||
@ -285,8 +285,7 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string
|
|||||||
{"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc},
|
{"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc},
|
||||||
{"libSceJpegEnc.sprx", nullptr},
|
{"libSceJpegEnc.sprx", nullptr},
|
||||||
{"libSceRazorCpu.sprx", nullptr},
|
{"libSceRazorCpu.sprx", nullptr},
|
||||||
{"libSceCesCs.sprx", nullptr},
|
{"libSceCesCs.sprx", nullptr}}};
|
||||||
{"libSceRudp.sprx", nullptr}}};
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> found_modules;
|
std::vector<std::filesystem::path> found_modules;
|
||||||
const auto& sys_module_path = Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir);
|
const auto& sys_module_path = Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir);
|
||||||
|
@ -434,7 +434,9 @@ QCheckBox* CheatsPatches::findCheckBoxByName(const QString& name) {
|
|||||||
QWidget* widget = item->widget();
|
QWidget* widget = item->widget();
|
||||||
QCheckBox* checkBox = qobject_cast<QCheckBox*>(widget);
|
QCheckBox* checkBox = qobject_cast<QCheckBox*>(widget);
|
||||||
if (checkBox) {
|
if (checkBox) {
|
||||||
if (checkBox->text().toStdString().find(name.toStdString()) != std::string::npos) {
|
const auto patchName = checkBox->property("patchName");
|
||||||
|
if (patchName.isValid() && patchName.toString().toStdString().find(
|
||||||
|
name.toStdString()) != std::string::npos) {
|
||||||
return checkBox;
|
return checkBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1176,6 +1178,7 @@ void CheatsPatches::addPatchesToLayout(const QString& filePath) {
|
|||||||
|
|
||||||
if (!patchName.isEmpty() && !patchLines.isEmpty()) {
|
if (!patchName.isEmpty() && !patchLines.isEmpty()) {
|
||||||
QCheckBox* patchCheckBox = new QCheckBox(patchName);
|
QCheckBox* patchCheckBox = new QCheckBox(patchName);
|
||||||
|
patchCheckBox->setProperty("patchName", patchName);
|
||||||
patchCheckBox->setChecked(isEnabled);
|
patchCheckBox->setChecked(isEnabled);
|
||||||
patchesGroupBoxLayout->addWidget(patchCheckBox);
|
patchesGroupBoxLayout->addWidget(patchCheckBox);
|
||||||
|
|
||||||
@ -1349,8 +1352,10 @@ bool CheatsPatches::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
|
|
||||||
void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) {
|
void CheatsPatches::onPatchCheckBoxHovered(QCheckBox* checkBox, bool hovered) {
|
||||||
if (hovered) {
|
if (hovered) {
|
||||||
QString text = checkBox->text();
|
const auto patchName = checkBox->property("patchName");
|
||||||
updateNoteTextEdit(text);
|
if (patchName.isValid()) {
|
||||||
|
updateNoteTextEdit(patchName.toString());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
instructionsTextEdit->setText(defaultTextEdit);
|
instructionsTextEdit->setText(defaultTextEdit);
|
||||||
}
|
}
|
||||||
|
@ -673,6 +673,12 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||||||
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
|
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!psf.Open(pkg.sfo)) {
|
||||||
|
QMessageBox::critical(this, tr("PKG ERROR"),
|
||||||
|
"Could not read SFO. Check log for details");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto category = psf.GetString("CATEGORY");
|
||||||
InstallDirSelect ids;
|
InstallDirSelect ids;
|
||||||
ids.exec();
|
ids.exec();
|
||||||
auto game_install_dir = ids.getSelectedDirectory();
|
auto game_install_dir = ids.getSelectedDirectory();
|
||||||
@ -689,12 +695,6 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||||||
QMessageBox msgBox;
|
QMessageBox msgBox;
|
||||||
msgBox.setWindowTitle(tr("PKG Extraction"));
|
msgBox.setWindowTitle(tr("PKG Extraction"));
|
||||||
|
|
||||||
if (!psf.Open(pkg.sfo)) {
|
|
||||||
QMessageBox::critical(this, tr("PKG ERROR"),
|
|
||||||
"Could not read SFO. Check log for details");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string content_id;
|
std::string content_id;
|
||||||
if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) {
|
if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) {
|
||||||
content_id = std::string{*value};
|
content_id = std::string{*value};
|
||||||
@ -709,7 +709,6 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||||||
QString addonDirPath;
|
QString addonDirPath;
|
||||||
Common::FS::PathToQString(addonDirPath, addon_extract_path);
|
Common::FS::PathToQString(addonDirPath, addon_extract_path);
|
||||||
QDir addon_dir(addonDirPath);
|
QDir addon_dir(addonDirPath);
|
||||||
auto category = psf.GetString("CATEGORY");
|
|
||||||
|
|
||||||
if (pkgType.contains("PATCH")) {
|
if (pkgType.contains("PATCH")) {
|
||||||
QString pkg_app_version;
|
QString pkg_app_version;
|
||||||
@ -802,9 +801,10 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Do nothing;
|
// Do nothing;
|
||||||
if (pkgType.contains("PATCH")) {
|
if (pkgType.contains("PATCH") || category == "ac") {
|
||||||
QMessageBox::information(this, tr("PKG Extraction"),
|
QMessageBox::information(
|
||||||
tr("PKG is a patch, please install the game first!"));
|
this, tr("PKG Extraction"),
|
||||||
|
tr("PKG is a patch or DLC, please install the game first!"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// what else?
|
// what else?
|
||||||
|
@ -42,10 +42,11 @@ QStringList languageNames = {"Arabic",
|
|||||||
"Thai",
|
"Thai",
|
||||||
"Traditional Chinese",
|
"Traditional Chinese",
|
||||||
"Turkish",
|
"Turkish",
|
||||||
|
"Ukrainian",
|
||||||
"Vietnamese"};
|
"Vietnamese"};
|
||||||
|
|
||||||
const QVector<int> languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0,
|
const QVector<int> languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0, 9,
|
||||||
9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 28};
|
15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 30, 28};
|
||||||
|
|
||||||
SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidget* parent)
|
SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidget* parent)
|
||||||
: QDialog(parent), ui(new Ui::SettingsDialog) {
|
: QDialog(parent), ui(new Ui::SettingsDialog) {
|
||||||
|
1476
src/qt_gui/translations/uk_UA.ts
Normal file
1476
src/qt_gui/translations/uk_UA.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -176,6 +176,7 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const
|
|||||||
ImageOperands operands;
|
ImageOperands operands;
|
||||||
operands.AddOffset(ctx, offset);
|
operands.AddOffset(ctx, offset);
|
||||||
operands.Add(spv::ImageOperandsMask::Lod, lod);
|
operands.Add(spv::ImageOperandsMask::Lod, lod);
|
||||||
|
operands.Add(spv::ImageOperandsMask::Sample, ms);
|
||||||
const Id texel =
|
const Id texel =
|
||||||
texture.is_storage
|
texture.is_storage
|
||||||
? ctx.OpImageRead(result_type, image, coords, operands.mask, operands.operands)
|
? ctx.OpImageRead(result_type, image, coords, operands.mask, operands.operands)
|
||||||
|
@ -86,6 +86,13 @@ struct SamplerResource {
|
|||||||
};
|
};
|
||||||
using SamplerResourceList = boost::container::small_vector<SamplerResource, 16>;
|
using SamplerResourceList = boost::container::small_vector<SamplerResource, 16>;
|
||||||
|
|
||||||
|
struct FMaskResource {
|
||||||
|
u32 sharp_idx;
|
||||||
|
|
||||||
|
constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept;
|
||||||
|
};
|
||||||
|
using FMaskResourceList = boost::container::small_vector<FMaskResource, 16>;
|
||||||
|
|
||||||
struct PushData {
|
struct PushData {
|
||||||
static constexpr u32 BufOffsetIndex = 2;
|
static constexpr u32 BufOffsetIndex = 2;
|
||||||
static constexpr u32 UdRegsIndex = 4;
|
static constexpr u32 UdRegsIndex = 4;
|
||||||
@ -178,6 +185,7 @@ struct Info {
|
|||||||
TextureBufferResourceList texture_buffers;
|
TextureBufferResourceList texture_buffers;
|
||||||
ImageResourceList images;
|
ImageResourceList images;
|
||||||
SamplerResourceList samplers;
|
SamplerResourceList samplers;
|
||||||
|
FMaskResourceList fmasks;
|
||||||
|
|
||||||
PersistentSrtInfo srt_info;
|
PersistentSrtInfo srt_info;
|
||||||
std::vector<u32> flattened_ud_buf;
|
std::vector<u32> flattened_ud_buf;
|
||||||
@ -282,6 +290,10 @@ constexpr AmdGpu::Sampler SamplerResource::GetSharp(const Info& info) const noex
|
|||||||
return inline_sampler ? inline_sampler : info.ReadUdSharp<AmdGpu::Sampler>(sharp_idx);
|
return inline_sampler ? inline_sampler : info.ReadUdSharp<AmdGpu::Sampler>(sharp_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr AmdGpu::Image FMaskResource::GetSharp(const Info& info) const noexcept {
|
||||||
|
return info.ReadUdSharp<AmdGpu::Image>(sharp_idx);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Shader
|
} // namespace Shader
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
@ -142,7 +142,7 @@ public:
|
|||||||
explicit Descriptors(Info& info_)
|
explicit Descriptors(Info& info_)
|
||||||
: info{info_}, buffer_resources{info_.buffers},
|
: info{info_}, buffer_resources{info_.buffers},
|
||||||
texture_buffer_resources{info_.texture_buffers}, image_resources{info_.images},
|
texture_buffer_resources{info_.texture_buffers}, image_resources{info_.images},
|
||||||
sampler_resources{info_.samplers} {}
|
sampler_resources{info_.samplers}, fmask_resources(info_.fmasks) {}
|
||||||
|
|
||||||
u32 Add(const BufferResource& desc) {
|
u32 Add(const BufferResource& desc) {
|
||||||
const u32 index{Add(buffer_resources, desc, [&desc](const auto& existing) {
|
const u32 index{Add(buffer_resources, desc, [&desc](const auto& existing) {
|
||||||
@ -183,6 +183,13 @@ public:
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 Add(const FMaskResource& desc) {
|
||||||
|
u32 index = Add(fmask_resources, desc, [&desc](const auto& existing) {
|
||||||
|
return desc.sharp_idx == existing.sharp_idx;
|
||||||
|
});
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename Descriptors, typename Descriptor, typename Func>
|
template <typename Descriptors, typename Descriptor, typename Func>
|
||||||
static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) {
|
static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) {
|
||||||
@ -199,6 +206,7 @@ private:
|
|||||||
TextureBufferResourceList& texture_buffer_resources;
|
TextureBufferResourceList& texture_buffer_resources;
|
||||||
ImageResourceList& image_resources;
|
ImageResourceList& image_resources;
|
||||||
SamplerResourceList& sampler_resources;
|
SamplerResourceList& sampler_resources;
|
||||||
|
FMaskResourceList& fmask_resources;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
@ -618,6 +626,40 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
|
|||||||
}
|
}
|
||||||
ASSERT(image.GetType() != AmdGpu::ImageType::Invalid);
|
ASSERT(image.GetType() != AmdGpu::ImageType::Invalid);
|
||||||
const bool is_storage = IsImageStorageInstruction(inst);
|
const bool is_storage = IsImageStorageInstruction(inst);
|
||||||
|
|
||||||
|
// Patch image instruction if image is FMask.
|
||||||
|
if (image.IsFmask()) {
|
||||||
|
ASSERT_MSG(!is_storage, "FMask storage instructions are not supported");
|
||||||
|
|
||||||
|
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::ImageFetch:
|
||||||
|
case IR::Opcode::ImageSampleRaw: {
|
||||||
|
IR::F32 fmaskx = ir.BitCast<IR::F32>(ir.Imm32(0x76543210));
|
||||||
|
IR::F32 fmasky = ir.BitCast<IR::F32>(ir.Imm32(0xfedcba98));
|
||||||
|
inst.ReplaceUsesWith(ir.CompositeConstruct(fmaskx, fmasky));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case IR::Opcode::ImageQueryLod:
|
||||||
|
inst.ReplaceUsesWith(ir.Imm32(1));
|
||||||
|
return;
|
||||||
|
case IR::Opcode::ImageQueryDimensions: {
|
||||||
|
IR::Value dims = ir.CompositeConstruct(ir.Imm32(static_cast<u32>(image.width)), // x
|
||||||
|
ir.Imm32(static_cast<u32>(image.width)), // y
|
||||||
|
ir.Imm32(1), ir.Imm32(1)); // depth, mip
|
||||||
|
inst.ReplaceUsesWith(dims);
|
||||||
|
|
||||||
|
// Track FMask resource to do specialization.
|
||||||
|
descriptors.Add(FMaskResource{
|
||||||
|
.sharp_idx = tsharp,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Can't patch fmask instruction {}", inst.GetOpcode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto type = image.IsPartialCubemap() ? AmdGpu::ImageType::Color2DArray : image.GetType();
|
const auto type = image.IsPartialCubemap() ? AmdGpu::ImageType::Color2DArray : image.GetType();
|
||||||
u32 image_binding = descriptors.Add(ImageResource{
|
u32 image_binding = descriptors.Add(ImageResource{
|
||||||
.sharp_idx = tsharp,
|
.sharp_idx = tsharp,
|
||||||
@ -652,11 +694,14 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
|
|||||||
return {body->Arg(0), body->Arg(1)};
|
return {body->Arg(0), body->Arg(1)};
|
||||||
case AmdGpu::ImageType::Color1DArray: // x, slice
|
case AmdGpu::ImageType::Color1DArray: // x, slice
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case AmdGpu::ImageType::Color2D: // x, y
|
case AmdGpu::ImageType::Color2D: // x, y, [lod]
|
||||||
return {ir.CompositeConstruct(body->Arg(0), body->Arg(1)), body->Arg(2)};
|
|
||||||
case AmdGpu::ImageType::Color2DArray: // x, y, slice
|
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case AmdGpu::ImageType::Color2DMsaa: // x, y, frag
|
case AmdGpu::ImageType::Color2DMsaa: // x, y. (sample is passed on different argument)
|
||||||
|
return {ir.CompositeConstruct(body->Arg(0), body->Arg(1)), body->Arg(2)};
|
||||||
|
case AmdGpu::ImageType::Color2DArray: // x, y, slice, [lod]
|
||||||
|
[[fallthrough]];
|
||||||
|
case AmdGpu::ImageType::Color2DMsaaArray: // x, y, slice. (sample is passed on different
|
||||||
|
// argument)
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case AmdGpu::ImageType::Color3D: // x, y, z
|
case AmdGpu::ImageType::Color3D: // x, y, z
|
||||||
return {ir.CompositeConstruct(body->Arg(0), body->Arg(1), body->Arg(2)), body->Arg(3)};
|
return {ir.CompositeConstruct(body->Arg(0), body->Arg(1), body->Arg(2)), body->Arg(3)};
|
||||||
@ -672,7 +717,12 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
|
|||||||
|
|
||||||
if (inst_info.has_lod) {
|
if (inst_info.has_lod) {
|
||||||
ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch);
|
ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch);
|
||||||
|
ASSERT(image.GetType() == AmdGpu::ImageType::Color2D ||
|
||||||
|
image.GetType() == AmdGpu::ImageType::Color2DArray);
|
||||||
inst.SetArg(3, arg);
|
inst.SetArg(3, arg);
|
||||||
|
} else if (image.GetType() == AmdGpu::ImageType::Color2DMsaa ||
|
||||||
|
image.GetType() == AmdGpu::ImageType::Color2DMsaaArray) {
|
||||||
|
inst.SetArg(4, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,13 @@ struct ImageSpecialization {
|
|||||||
auto operator<=>(const ImageSpecialization&) const = default;
|
auto operator<=>(const ImageSpecialization&) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FMaskSpecialization {
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
|
||||||
|
auto operator<=>(const FMaskSpecialization&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alongside runtime information, this structure also checks bound resources
|
* Alongside runtime information, this structure also checks bound resources
|
||||||
* for compatibility. Can be used as a key for storing shader permutations.
|
* for compatibility. Can be used as a key for storing shader permutations.
|
||||||
@ -47,6 +54,7 @@ struct StageSpecialization {
|
|||||||
boost::container::small_vector<BufferSpecialization, 16> buffers;
|
boost::container::small_vector<BufferSpecialization, 16> buffers;
|
||||||
boost::container::small_vector<TextureBufferSpecialization, 8> tex_buffers;
|
boost::container::small_vector<TextureBufferSpecialization, 8> tex_buffers;
|
||||||
boost::container::small_vector<ImageSpecialization, 16> images;
|
boost::container::small_vector<ImageSpecialization, 16> images;
|
||||||
|
boost::container::small_vector<FMaskSpecialization, 8> fmasks;
|
||||||
Backend::Bindings start{};
|
Backend::Bindings start{};
|
||||||
|
|
||||||
explicit StageSpecialization(const Shader::Info& info_, RuntimeInfo runtime_info_,
|
explicit StageSpecialization(const Shader::Info& info_, RuntimeInfo runtime_info_,
|
||||||
@ -71,6 +79,11 @@ struct StageSpecialization {
|
|||||||
: sharp.GetType();
|
: sharp.GetType();
|
||||||
spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt());
|
spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt());
|
||||||
});
|
});
|
||||||
|
ForEachSharp(binding, fmasks, info->fmasks,
|
||||||
|
[](auto& spec, const auto& desc, AmdGpu::Image sharp) {
|
||||||
|
spec.width = sharp.width;
|
||||||
|
spec.height = sharp.height;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ForEachSharp(u32& binding, auto& spec_list, auto& desc_list, auto&& func) {
|
void ForEachSharp(u32& binding, auto& spec_list, auto& desc_list, auto&& func) {
|
||||||
@ -115,6 +128,11 @@ struct StageSpecialization {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (u32 i = 0; i < fmasks.size(); i++) {
|
||||||
|
if (other.bitset[binding++] && fmasks[i] != other.fmasks[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -295,6 +295,11 @@ struct Image {
|
|||||||
return GetTilingMode() != TilingMode::Display_Linear;
|
return GetTilingMode() != TilingMode::Display_Linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsFmask() const noexcept {
|
||||||
|
return GetDataFmt() >= DataFormat::FormatFmask8_1 &&
|
||||||
|
GetDataFmt() <= DataFormat::FormatFmask64_8;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsPartialCubemap() const {
|
bool IsPartialCubemap() const {
|
||||||
const auto viewed_slice = last_array - base_array + 1;
|
const auto viewed_slice = last_array - base_array + 1;
|
||||||
return GetType() == ImageType::Cube && viewed_slice < 6;
|
return GetType() == ImageType::Cube && viewed_slice < 6;
|
||||||
|
Loading…
Reference in New Issue
Block a user