From 7153838c4ec8b3363fd3e72a8c58880b142eb439 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:43:12 -0800 Subject: [PATCH] libraries: Add initial HLE JPEG encoder skeleton (#1607) * libraries: Add initial HLE JPEG encoder skeleton * jpegenc: Finish adding parameter validation. * updated enums , added logging * jpegenc: Clean up parameter validations. * jpegenc: Fix missing log. * externals: Update ffmpeg-core --------- Co-authored-by: georgemoralis --- CMakeLists.txt | 6 + externals/ffmpeg-core | 2 +- src/common/alignment.h | 6 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/jpeg/jpeg_error.h | 9 ++ src/core/libraries/jpeg/jpegenc.cpp | 207 +++++++++++++++++++++++++++ src/core/libraries/jpeg/jpegenc.h | 84 +++++++++++ src/emulator.cpp | 3 +- 9 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 src/core/libraries/jpeg/jpeg_error.h create mode 100644 src/core/libraries/jpeg/jpegenc.cpp create mode 100644 src/core/libraries/jpeg/jpegenc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 55f1172f..c4f639bd 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,6 +354,11 @@ set(PNG_LIB src/core/libraries/libpng/pngdec.cpp src/core/libraries/libpng/pngdec.h ) +set(JPEG_LIB src/core/libraries/jpeg/jpeg_error.h + src/core/libraries/jpeg/jpegenc.cpp + src/core/libraries/jpeg/jpegenc.h +) + set(PLAYGO_LIB src/core/libraries/playgo/playgo.cpp src/core/libraries/playgo/playgo.h src/core/libraries/playgo/playgo_types.h @@ -536,6 +541,7 @@ set(CORE src/core/aerolib/stubs.cpp ${VIDEOOUT_LIB} ${NP_LIBS} ${PNG_LIB} + ${JPEG_LIB} ${PLAYGO_LIB} ${RANDOM_LIB} ${USBD_LIB} diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core index e30b7d7f..27de97c8 160000 --- a/externals/ffmpeg-core +++ b/externals/ffmpeg-core @@ -1 +1 @@ -Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0 +Subproject commit 27de97c826b6b40c255891c37ac046a25836a575 diff --git a/src/common/alignment.h b/src/common/alignment.h index 8480fae2..3fb961c6 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -22,6 +22,12 @@ template return static_cast(value - value % size); } +template + requires std::is_integral_v +[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) { + return (value & (alignment - 1)) == 0; +} + template requires std::is_integral_v [[nodiscard]] constexpr bool Is16KBAligned(T value) { diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index fc9fa055..5f44658f 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -105,6 +105,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, Rtc) \ SUB(Lib, DiscMap) \ SUB(Lib, Png) \ + SUB(Lib, Jpeg) \ SUB(Lib, PlayGo) \ SUB(Lib, Random) \ SUB(Lib, Usbd) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 1422c392..a373e8f1 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -72,6 +72,7 @@ enum class Class : u8 { Lib_Rtc, ///< The LibSceRtc implementation. Lib_DiscMap, ///< The LibSceDiscMap implementation. Lib_Png, ///< The LibScePng implementation. + Lib_Jpeg, ///< The LibSceJpeg implementation. Lib_PlayGo, ///< The LibScePlayGo implementation. Lib_Random, ///< The libSceRandom implementation. Lib_Usbd, ///< The LibSceUsbd implementation. diff --git a/src/core/libraries/jpeg/jpeg_error.h b/src/core/libraries/jpeg/jpeg_error.h new file mode 100644 index 00000000..b9aa7483 --- /dev/null +++ b/src/core/libraries/jpeg/jpeg_error.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +constexpr int ORBIS_JPEG_ENC_ERROR_INVALID_ADDR = 0x80650101; +constexpr int ORBIS_JPEG_ENC_ERROR_INVALID_SIZE = 0x80650102; +constexpr int ORBIS_JPEG_ENC_ERROR_INVALID_PARAM = 0x80650103; +constexpr int ORBIS_JPEG_ENC_ERROR_INVALID_HANDLE = 0x80650104; diff --git a/src/core/libraries/jpeg/jpegenc.cpp b/src/core/libraries/jpeg/jpegenc.cpp new file mode 100644 index 00000000..b664a233 --- /dev/null +++ b/src/core/libraries/jpeg/jpegenc.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "jpeg_error.h" +#include "jpegenc.h" + +namespace Libraries::JpegEnc { + +constexpr s32 ORBIS_JPEG_ENC_MINIMUM_MEMORY_SIZE = 0x800; +constexpr u32 ORBIS_JPEG_ENC_MAX_IMAGE_DIMENSION = 0xFFFF; +constexpr u32 ORBIS_JPEG_ENC_MAX_IMAGE_PITCH = 0xFFFFFFF; +constexpr u32 ORBIS_JPEG_ENC_MAX_IMAGE_SIZE = 0x7FFFFFFF; + +static s32 ValidateJpegEncCreateParam(const OrbisJpegEncCreateParam* param) { + if (!param) { + return ORBIS_JPEG_ENC_ERROR_INVALID_ADDR; + } + if (param->size != sizeof(OrbisJpegEncCreateParam)) { + return ORBIS_JPEG_ENC_ERROR_INVALID_SIZE; + } + if (param->attr != ORBIS_JPEG_ENC_ATTRIBUTE_NONE) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + return ORBIS_OK; +} + +static s32 ValidateJpegEncMemory(const void* memory, const u32 memory_size) { + if (!memory) { + return ORBIS_JPEG_ENC_ERROR_INVALID_ADDR; + } + if (memory_size < ORBIS_JPEG_ENC_MINIMUM_MEMORY_SIZE) { + return ORBIS_JPEG_ENC_ERROR_INVALID_SIZE; + } + return ORBIS_OK; +} + +static s32 ValidateJpegEncEncodeParam(const OrbisJpegEncEncodeParam* param) { + + // Validate addresses + if (!param) { + return ORBIS_JPEG_ENC_ERROR_INVALID_ADDR; + } + if (!param->image || (param->pixel_format != ORBIS_JPEG_ENC_PIXEL_FORMAT_Y8 && + !Common::IsAligned(reinterpret_cast(param->image), 4))) { + return ORBIS_JPEG_ENC_ERROR_INVALID_ADDR; + } + if (!param->jpeg) { + return ORBIS_JPEG_ENC_ERROR_INVALID_ADDR; + } + + // Validate sizes + if (param->image_size == 0 || param->jpeg_size == 0) { + return ORBIS_JPEG_ENC_ERROR_INVALID_SIZE; + } + + // Validate parameters + if (param->image_width > ORBIS_JPEG_ENC_MAX_IMAGE_DIMENSION || + param->image_height > ORBIS_JPEG_ENC_MAX_IMAGE_DIMENSION) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + if (param->image_pitch == 0 || param->image_pitch > ORBIS_JPEG_ENC_MAX_IMAGE_PITCH || + (param->pixel_format != ORBIS_JPEG_ENC_PIXEL_FORMAT_Y8 && + !Common::IsAligned(param->image_pitch, 4))) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + const auto calculated_size = param->image_height * param->image_pitch; + if (calculated_size > ORBIS_JPEG_ENC_MAX_IMAGE_SIZE || calculated_size > param->image_size) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + if (param->encode_mode != ORBIS_JPEG_ENC_ENCODE_MODE_NORMAL && + param->encode_mode != ORBIS_JPEG_ENC_ENCODE_MODE_MJPEG) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + if (param->color_space != ORBIS_JPEG_ENC_COLOR_SPACE_YCC && + param->color_space != ORBIS_JPEG_ENC_COLOR_SPACE_GRAYSCALE) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + if (param->sampling_type != ORBIS_JPEG_ENC_SAMPLING_TYPE_FULL && + param->sampling_type != ORBIS_JPEG_ENC_SAMPLING_TYPE_422 && + param->sampling_type != ORBIS_JPEG_ENC_SAMPLING_TYPE_420) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + if (param->restart_interval > ORBIS_JPEG_ENC_MAX_IMAGE_DIMENSION) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + switch (param->pixel_format) { + case ORBIS_JPEG_ENC_PIXEL_FORMAT_R8G8B8A8: + case ORBIS_JPEG_ENC_PIXEL_FORMAT_B8G8R8A8: + if (param->image_pitch >> 2 < param->image_width || + param->color_space != ORBIS_JPEG_ENC_COLOR_SPACE_YCC || + param->sampling_type == ORBIS_JPEG_ENC_SAMPLING_TYPE_FULL) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + break; + case ORBIS_JPEG_ENC_PIXEL_FORMAT_Y8U8Y8V8: + if (param->image_pitch >> 1 < Common::AlignUp(param->image_width, 2) || + param->color_space != ORBIS_JPEG_ENC_COLOR_SPACE_YCC || + param->sampling_type == ORBIS_JPEG_ENC_SAMPLING_TYPE_FULL) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + break; + case ORBIS_JPEG_ENC_PIXEL_FORMAT_Y8: + if (param->image_pitch < param->image_width || + param->color_space != ORBIS_JPEG_ENC_COLOR_SPACE_GRAYSCALE || + param->sampling_type != ORBIS_JPEG_ENC_SAMPLING_TYPE_FULL) { + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + break; + default: + return ORBIS_JPEG_ENC_ERROR_INVALID_PARAM; + } + + return ORBIS_OK; +} + +static s32 ValidateJpecEngHandle(OrbisJpegEncHandle handle) { + if (!handle || !Common::IsAligned(reinterpret_cast(handle), 0x20) || + handle->handle != handle) { + return ORBIS_JPEG_ENC_ERROR_INVALID_HANDLE; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceJpegEncCreate(const OrbisJpegEncCreateParam* param, void* memory, + const u32 memory_size, OrbisJpegEncHandle* handle) { + if (auto param_ret = ValidateJpegEncCreateParam(param); param_ret != ORBIS_OK) { + LOG_ERROR(Lib_Jpeg, "Invalid create param"); + return param_ret; + } + if (auto memory_ret = ValidateJpegEncMemory(memory, memory_size); memory_ret != ORBIS_OK) { + LOG_ERROR(Lib_Jpeg, "Invalid memory"); + return memory_ret; + } + if (!handle) { + LOG_ERROR(Lib_Jpeg, "Invalid handle output"); + return ORBIS_JPEG_ENC_ERROR_INVALID_ADDR; + } + + auto* handle_internal = reinterpret_cast( + Common::AlignUp(reinterpret_cast(memory), 0x20)); + handle_internal->handle = handle_internal; + handle_internal->handle_size = sizeof(OrbisJpegEncHandleInternal*); + *handle = handle_internal; + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceJpegEncDelete(OrbisJpegEncHandle handle) { + if (auto handle_ret = ValidateJpecEngHandle(handle); handle_ret != ORBIS_OK) { + LOG_ERROR(Lib_Jpeg, "Invalid handle"); + return handle_ret; + } + handle->handle = nullptr; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceJpegEncEncode(OrbisJpegEncHandle handle, const OrbisJpegEncEncodeParam* param, + OrbisJpegEncOutputInfo* output_info) { + if (auto handle_ret = ValidateJpecEngHandle(handle); handle_ret != ORBIS_OK) { + LOG_ERROR(Lib_Jpeg, "Invalid handle"); + return handle_ret; + } + if (auto param_ret = ValidateJpegEncEncodeParam(param); param_ret != ORBIS_OK) { + LOG_ERROR(Lib_Jpeg, "Invalid encode param"); + return param_ret; + } + + LOG_ERROR(Lib_Jpeg, + "(STUBBED) image_size = {} , jpeg_size = {} , image_width = {} , image_height = {} , " + "image_pitch = {} , pixel_format = {} , encode_mode = {} , color_space = {} , " + "sampling_type = {} , compression_ratio = {} , restart_interval = {}", + param->image_size, param->jpeg_size, param->image_width, param->image_height, + param->image_pitch, magic_enum::enum_name(param->pixel_format), + magic_enum::enum_name(param->encode_mode), magic_enum::enum_name(param->color_space), + magic_enum::enum_name(param->sampling_type), param->compression_ratio, + param->restart_interval); + + if (output_info) { + output_info->size = param->jpeg_size; + output_info->height = param->image_height; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceJpegEncQueryMemorySize(const OrbisJpegEncCreateParam* param) { + if (auto param_ret = ValidateJpegEncCreateParam(param); param_ret != ORBIS_OK) { + LOG_ERROR(Lib_Jpeg, "Invalid create param"); + return param_ret; + } + return ORBIS_JPEG_ENC_MINIMUM_MEMORY_SIZE; +} + +void RegisterlibSceJpegEnc(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("K+rocojkr-I", "libSceJpegEnc", 1, "libSceJpegEnc", 1, 1, sceJpegEncCreate); + LIB_FUNCTION("j1LyMdaM+C0", "libSceJpegEnc", 1, "libSceJpegEnc", 1, 1, sceJpegEncDelete); + LIB_FUNCTION("QbrU0cUghEM", "libSceJpegEnc", 1, "libSceJpegEnc", 1, 1, sceJpegEncEncode); + LIB_FUNCTION("o6ZgXfFdWXQ", "libSceJpegEnc", 1, "libSceJpegEnc", 1, 1, + sceJpegEncQueryMemorySize); +}; + +} // namespace Libraries::JpegEnc diff --git a/src/core/libraries/jpeg/jpegenc.h b/src/core/libraries/jpeg/jpegenc.h new file mode 100644 index 00000000..a6b4d311 --- /dev/null +++ b/src/core/libraries/jpeg/jpegenc.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::JpegEnc { + +enum OrbisJpegEncCreateParamAttributes : u32 { ORBIS_JPEG_ENC_ATTRIBUTE_NONE = 0 }; + +enum OrbisJpegEncEncodeParamPixelFormat : u16 { + ORBIS_JPEG_ENC_PIXEL_FORMAT_R8G8B8A8 = 0, + ORBIS_JPEG_ENC_PIXEL_FORMAT_B8G8R8A8 = 1, + ORBIS_JPEG_ENC_PIXEL_FORMAT_Y8U8Y8V8 = 10, + ORBIS_JPEG_ENC_PIXEL_FORMAT_Y8 = 11 +}; + +enum OrbisJpengEncEncodeParamEncodeMode : u16 { + ORBIS_JPEG_ENC_ENCODE_MODE_NORMAL = 0, + ORBIS_JPEG_ENC_ENCODE_MODE_MJPEG = 1 +}; + +enum OrbisJpengEncEncodeParamColorSpace : u16 { + ORBIS_JPEG_ENC_COLOR_SPACE_YCC = 1, + ORBIS_JPEG_ENC_COLOR_SPACE_GRAYSCALE = 2 +}; + +enum OrbisJpengEncEncodeParamSamplingType : u8 { + ORBIS_JPEG_ENC_SAMPLING_TYPE_FULL = 0, + ORBIS_JPEG_ENC_SAMPLING_TYPE_422 = 1, + ORBIS_JPEG_ENC_SAMPLING_TYPE_420 = 2 +}; + +struct OrbisJpegEncHandleInternal { + OrbisJpegEncHandleInternal* handle; + u32 handle_size; +}; +static_assert(sizeof(OrbisJpegEncHandleInternal) == 0x10); + +typedef OrbisJpegEncHandleInternal* OrbisJpegEncHandle; + +struct OrbisJpegEncCreateParam { + u32 size; + OrbisJpegEncCreateParamAttributes attr; +}; +static_assert(sizeof(OrbisJpegEncCreateParam) == 0x8); + +struct OrbisJpegEncEncodeParam { + void* image; + void* jpeg; + u32 image_size; + u32 jpeg_size; + u32 image_width; + u32 image_height; + u32 image_pitch; + OrbisJpegEncEncodeParamPixelFormat pixel_format; + OrbisJpengEncEncodeParamEncodeMode encode_mode; + OrbisJpengEncEncodeParamColorSpace color_space; + OrbisJpengEncEncodeParamSamplingType sampling_type; + u8 compression_ratio; + s32 restart_interval; +}; +static_assert(sizeof(OrbisJpegEncEncodeParam) == 0x30); + +struct OrbisJpegEncOutputInfo { + u32 size; + u32 height; +}; +static_assert(sizeof(OrbisJpegEncOutputInfo) == 0x8); + +s32 PS4_SYSV_ABI sceJpegEncCreate(const OrbisJpegEncCreateParam* param, void* memory, + u32 memory_size, OrbisJpegEncHandle* handle); +s32 PS4_SYSV_ABI sceJpegEncDelete(OrbisJpegEncHandle handle); +s32 PS4_SYSV_ABI sceJpegEncEncode(OrbisJpegEncHandle handle, const OrbisJpegEncEncodeParam* param, + OrbisJpegEncOutputInfo* output_info); +s32 PS4_SYSV_ABI sceJpegEncQueryMemorySize(const OrbisJpegEncCreateParam* param); + +void RegisterlibSceJpegEnc(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::JpegEnc diff --git a/src/emulator.cpp b/src/emulator.cpp index 27291707..a01eb816 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -29,6 +29,7 @@ #include "core/file_sys/fs.h" #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/fiber/fiber.h" +#include "core/libraries/jpeg/jpegenc.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" @@ -278,7 +279,7 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterlibSceLibcInternal}, {"libSceDiscMap.sprx", &Libraries::DiscMap::RegisterlibSceDiscMap}, {"libSceRtc.sprx", &Libraries::Rtc::RegisterlibSceRtc}, - {"libSceJpegEnc.sprx", nullptr}, + {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterlibSceJpegEnc}, {"libSceCesCs.sprx", nullptr}}}; std::vector found_modules;