rpcsx/rpcs3/Emu/Cell/Modules/cellPngDec.cpp
Elad 575a245f8d
IDM: Implement lock-free smart pointers (#16403)
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`.

Notes to programmers:

* This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking).
* Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type.
* `idm::check/get_unlocked` can now be used anywhere.

Misc fixes:
* Fixes some segfaults with RPCN with interaction with IDM.
* Fix deadlocks in access violation handler due locking recursion.
* Fixes race condition in process exit-spawn on memory containers read.
* Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes.
* Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation)
* Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
2024-12-22 20:59:48 +02:00

1043 lines
34 KiB
C++

#include "stdafx.h"
#include "Emu/VFS.h"
#include "Emu/IdManager.h"
#include "Emu/Cell/PPUModule.h"
#include "Emu/Cell/lv2/sys_fs.h"
#include "png.h"
#include "cellPng.h"
#include "cellPngDec.h"
#if PNG_LIBPNG_VER_MAJOR >= 1 && (PNG_LIBPNG_VER_MINOR < 5 \
|| (PNG_LIBPNG_VER_MINOR == 5 && PNG_LIBPNG_VER_RELEASE < 7))
#define PNG_ERROR_ACTION_NONE 1
#define PNG_RGB_TO_GRAY_DEFAULT (-1)
#endif
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
typedef png_bytep iCCP_profile_type;
#else
typedef png_charp iCCP_profile_type;
#endif
// Temporarily
#ifndef _MSC_VER
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
LOG_CHANNEL(cellPngDec);
template <>
void fmt_class_string<CellPngDecError>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](CellPngDecError value)
{
switch (value)
{
STR_CASE(CELL_PNGDEC_ERROR_HEADER);
STR_CASE(CELL_PNGDEC_ERROR_STREAM_FORMAT);
STR_CASE(CELL_PNGDEC_ERROR_ARG);
STR_CASE(CELL_PNGDEC_ERROR_SEQ);
STR_CASE(CELL_PNGDEC_ERROR_BUSY);
STR_CASE(CELL_PNGDEC_ERROR_FATAL);
STR_CASE(CELL_PNGDEC_ERROR_OPEN_FILE);
STR_CASE(CELL_PNGDEC_ERROR_SPU_UNSUPPORT);
STR_CASE(CELL_PNGDEC_ERROR_SPU_ERROR);
STR_CASE(CELL_PNGDEC_ERROR_CB_PARAM);
}
return unknown;
});
}
// cellPngDec aliases to improve readability
using PPHandle = vm::pptr<PngHandle>;
using PHandle = vm::ptr<PngHandle>;
using PThreadInParam = vm::cptr<CellPngDecThreadInParam>;
using PThreadOutParam = vm::ptr<CellPngDecThreadOutParam>;
using PExtThreadInParam = vm::cptr<CellPngDecExtThreadInParam>;
using PExtThreadOutParam = vm::ptr<CellPngDecExtThreadOutParam>;
using PPStream = vm::pptr<PngStream>;
using PStream = vm::ptr<PngStream>;
using PSrc = vm::cptr<CellPngDecSrc>;
using POpenInfo = vm::ptr<CellPngDecOpnInfo>;
using POpenParam = vm::cptr<CellPngDecOpnParam>;
using PInfo = vm::ptr<CellPngDecInfo>;
using PExtInfo = vm::ptr<CellPngDecExtInfo>;
using PInParam = vm::cptr<CellPngDecInParam>;
using POutParam = vm::ptr<CellPngDecOutParam>;
using PExtInParam = vm::cptr<CellPngDecExtInParam>;
using PExtOutParam = vm::ptr<CellPngDecExtOutParam>;
using PDataControlParam = vm::cptr<CellPngDecDataCtrlParam>;
using PDataOutInfo = vm::ptr<CellPngDecDataOutInfo>;
using PCbControlDisp = vm::cptr<CellPngDecCbCtrlDisp>;
using PCbControlStream = vm::cptr<CellPngDecCbCtrlStrm>;
using PDispParam = vm::ptr<CellPngDecDispParam>;
// Custom read function for libpng, so we could decode images from a buffer
void pngDecReadBuffer(png_structp png_ptr, png_bytep out, png_size_t length)
{
// Get the IO pointer
png_voidp io_ptr = png_get_io_ptr(png_ptr);
// Check if obtaining of the IO pointer failed
if (!io_ptr)
{
cellPngDec.error("Failed to obtain the io_ptr failed.");
return;
}
// Cast the IO pointer to our custom structure
PngBuffer& buffer = *static_cast<PngBuffer*>(io_ptr);
// Read froma file or a buffer
if (buffer.file)
{
// Get the file
auto file = idm::get_unlocked<lv2_fs_object, lv2_file>(buffer.fd);
// Read the data
file->file.read(out, length);
}
else
{
// Get the current data pointer, including the current cursor position
void* data = static_cast<u8*>(buffer.data.get_ptr()) + buffer.cursor;
// Copy the length of the current data pointer to the output
memcpy(out, data, length);
// Increment the cursor for the next time
buffer.cursor += length;
}
}
void pngDecRowCallback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass)
{
PngStream* stream = static_cast<PngStream*>(png_get_progressive_ptr(png_ptr));
if (!stream)
{
cellPngDec.error("Failed to obtain streamPtr in rowCallback.");
return;
}
// we have to check this everytime as this func can be called multiple times per row, and/or only once per row
if (stream->nextRow + stream->outputCounts == row_num)
stream->nextRow = row_num;
if (stream->ppuContext && (stream->nextRow == row_num || pass > 0))
{
if (pass > 0 )
{
stream->cbDispInfo->scanPassCount = pass;
stream->cbDispInfo->nextOutputStartY = row_num;
}
else {
stream->cbDispInfo->scanPassCount = 0;
stream->cbDispInfo->nextOutputStartY = 0;
}
stream->cbDispInfo->outputImage = stream->cbDispParam->nextOutputImage;
stream->cbCtrlDisp.cbCtrlDispFunc(*stream->ppuContext, stream->cbDispInfo, stream->cbDispParam, stream->cbCtrlDisp.cbCtrlDispArg);
stream->cbDispInfo->outputStartY = row_num;
}
u8* data;
if (pass > 0)
data = static_cast<u8*>(stream->cbDispParam->nextOutputImage.get_ptr());
else
data = static_cast<u8*>(stream->cbDispParam->nextOutputImage.get_ptr()) + ((row_num - stream->cbDispInfo->outputStartY) * stream->cbDispInfo->outputFrameWidthByte);
png_progressive_combine_row(png_ptr, data, new_row);
}
void pngDecInfoCallback(png_structp png_ptr, png_infop info)
{
PngStream* stream = static_cast<PngStream*>(png_get_progressive_ptr(png_ptr));
if (!stream)
{
cellPngDec.error("Failed to obtain streamPtr in rowCallback.");
return;
}
const usz remaining = png_process_data_pause(png_ptr, false);
stream->buffer->cursor += (stream->buffer->length - remaining);
}
void pngDecEndCallback(png_structp png_ptr, png_infop info)
{
PngStream* stream = static_cast<PngStream*>(png_get_progressive_ptr(png_ptr));
if (!stream)
{
cellPngDec.error("Failed to obtain streamPtr in endCallback.");
return;
}
stream->endOfFile = true;
}
// Custom error handler for libpng
[[noreturn]] void pngDecError(png_structp png_ptr, png_const_charp error_message)
{
cellPngDec.error("%s", error_message);
// we can't return here or libpng blows up
fmt::throw_exception("Fatal Error in libpng: %s", error_message);
}
// Custom warning handler for libpng
void pngDecWarning(png_structp png_ptr, png_const_charp error_message)
{
cellPngDec.warning("%s", error_message);
}
// Get the chunk information of the PNG file. IDAT is marked as existing, only after decoding or reading the header.
// Bits (if set indicates existence of the chunk):
// 0 - gAMA
// 1 - sBIT
// 2 - cHRM
// 3 - PLTE
// 4 - tRNS
// 5 - bKGD
// 6 - hIST
// 7 - pHYs
// 8 - oFFs
// 9 - tIME
// 10 - pCAL
// 11 - sRGB
// 12 - iCCP
// 13 - sPLT
// 14 - sCAL
// 15 - IDAT
// 16:30 - reserved
be_t<u32> pngDecGetChunkInformation(PngStream* stream, bool IDAT = false)
{
// The end result of the chunk information (bigger-endian)
be_t<u32> chunk_information = 0;
// Needed pointers for getting the chunk information
f64 gamma;
f64 red_x;
f64 red_y;
f64 green_x;
f64 green_y;
f64 blue_x;
f64 blue_y;
f64 white_x;
f64 white_y;
f64 width;
f64 height;
s32 intent;
s32 num_trans;
s32 num_palette;
s32 unit_type;
s32 type;
s32 nparams;
s32 compression_type;
s32 unit;
u16* hist;
png_uint_32 proflen;
iCCP_profile_type profile;
png_bytep trans_alpha;
png_charp units;
png_charp name;
png_charp purpose;
png_charpp params;
png_int_32 X0;
png_int_32 X1;
png_int_32 offset_x;
png_int_32 offset_y;
png_uint_32 res_x;
png_uint_32 res_y;
png_colorp palette;
png_color_8p sig_bit;
png_color_16p background;
png_color_16p trans_color;
png_sPLT_tp entries;
png_timep mod_time;
// Get chunk information and set the appropriate bits
if (png_get_gAMA(stream->png_ptr, stream->info_ptr, &gamma))
{
chunk_information |= 1 << 0; // gAMA
}
if (png_get_sBIT(stream->png_ptr, stream->info_ptr, &sig_bit))
{
chunk_information |= 1 << 1; // sBIT
}
if (png_get_cHRM(stream->png_ptr, stream->info_ptr, &white_x, &white_y, &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y))
{
chunk_information |= 1 << 2; // cHRM
}
if (png_get_PLTE(stream->png_ptr, stream->info_ptr, &palette, &num_palette))
{
chunk_information |= 1 << 3; // PLTE
}
if (png_get_tRNS(stream->png_ptr, stream->info_ptr, &trans_alpha, &num_trans, &trans_color))
{
chunk_information |= 1 << 4; // tRNS
}
if (png_get_bKGD(stream->png_ptr, stream->info_ptr, &background))
{
chunk_information |= 1 << 5; // bKGD
}
if (png_get_hIST(stream->png_ptr, stream->info_ptr, &hist))
{
chunk_information |= 1 << 6; // hIST
}
if (png_get_pHYs(stream->png_ptr, stream->info_ptr, &res_x, &res_y, &unit_type))
{
chunk_information |= 1 << 7; // pHYs
}
if (png_get_oFFs(stream->png_ptr, stream->info_ptr, &offset_x, &offset_y, &unit_type))
{
chunk_information |= 1 << 8; // oFFs
}
if (png_get_tIME(stream->png_ptr, stream->info_ptr, &mod_time))
{
chunk_information |= 1 << 9; // tIME
}
if (png_get_pCAL(stream->png_ptr, stream->info_ptr, &purpose, &X0, &X1, &type, &nparams, &units, &params))
{
chunk_information |= 1 << 10; // pCAL
}
if (png_get_sRGB(stream->png_ptr, stream->info_ptr, &intent))
{
chunk_information |= 1 << 11; // sRGB
}
if (png_get_iCCP(stream->png_ptr, stream->info_ptr, &name, &compression_type, &profile, &proflen))
{
chunk_information |= 1 << 12; // iCCP
}
if (png_get_sPLT(stream->png_ptr, stream->info_ptr, &entries))
{
chunk_information |= 1 << 13; // sPLT
}
if (png_get_sCAL(stream->png_ptr, stream->info_ptr, &unit, &width, &height))
{
chunk_information |= 1 << 14; // sCAL
}
if (IDAT)
{
chunk_information |= 1 << 15; // IDAT
}
return chunk_information;
}
error_code pngDecCreate(ppu_thread& ppu, PPHandle png_handle, PThreadInParam thread_in_param, PThreadOutParam thread_out_param, PExtThreadInParam extra_thread_in_param = vm::null, PExtThreadOutParam extra_thread_out_param = vm::null)
{
// Check if partial image decoding is used
if (extra_thread_out_param)
{
fmt::throw_exception("Partial image decoding is not supported.");
}
// Allocate memory for the decoder handle
auto handle = vm::ptr<PngHandle>::make(thread_in_param->cbCtrlMallocFunc(ppu, sizeof(PngHandle), thread_in_param->cbCtrlMallocArg).addr());
// Check if the memory allocation for the handle failed
if (!handle)
{
cellPngDec.error("PNG decoder creation failed.");
return CELL_PNGDEC_ERROR_FATAL;
}
// Set the allocation functions in the handle
handle->malloc_ = thread_in_param->cbCtrlMallocFunc;
handle->malloc_arg = thread_in_param->cbCtrlMallocArg;
handle->free_ = thread_in_param->cbCtrlFreeFunc;
handle->free_arg = thread_in_param->cbCtrlFreeArg;
// Set handle pointer
*png_handle = handle;
// Set the version information
thread_out_param->pngCodecVersion = PNGDEC_CODEC_VERSION;
return CELL_OK;
}
error_code pngDecDestroy(ppu_thread& ppu, PHandle handle)
{
// Deallocate the decoder handle memory
if (handle->free_(ppu, handle, handle->free_arg) != 0)
{
cellPngDec.error("PNG decoder deallocation failed.");
return CELL_PNGDEC_ERROR_FATAL;
}
return CELL_OK;
}
error_code pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc source, POpenInfo open_info, PCbControlStream control_stream = vm::null, POpenParam open_param = vm::null)
{
// partial decoding only supported with buffer type
if (source->srcSelect != CELL_PNGDEC_BUFFER && control_stream)
{
cellPngDec.error("Attempted partial image decode with file.");
return CELL_PNGDEC_ERROR_STREAM_FORMAT;
}
// Allocate memory for the stream structure
auto stream = vm::ptr<PngStream>::make(handle->malloc_(ppu, sizeof(PngStream), handle->malloc_arg).addr());
// Check if the allocation of memory for the stream structure failed
if (!stream)
{
cellPngDec.error("PNG stream creation failed.");
return CELL_PNGDEC_ERROR_FATAL;
}
// Set memory info
open_info->initSpaceAllocated = sizeof(PngStream);
// Set the stream source to the source give by the game
stream->source = *source;
// Use virtual memory address as a handle
*png_stream = stream;
// Allocate memory for the PNG buffer for decoding
auto buffer = vm::ptr<PngBuffer>::make(handle->malloc_(ppu, sizeof(PngBuffer), handle->malloc_arg).addr());
// Check for if the buffer structure allocation failed
if (!buffer)
{
fmt::throw_exception("Memory allocation for the PNG buffer structure failed.");
}
// We might not be reading from a file stream
buffer->file = false;
// Set the buffer pointer in the stream structure, so we can later deallocate it
stream->buffer = buffer;
// Open the buffer/file and check the header
u8 header[8];
// Need to test it somewhere
if (stream->source.fileOffset != 0)
{
fmt::throw_exception("Non-0 file offset not supported.");
}
// Depending on the source type, get the first 8 bytes
if (source->srcSelect == CELL_PNGDEC_FILE)
{
const auto real_path = vfs::get(stream->source.fileName.get_ptr());
// Open a file stream
fs::file file_stream(real_path);
// Check if opening of the PNG file failed
if (!file_stream)
{
cellPngDec.error("Opening of PNG failed. (%s)", stream->source.fileName.get_ptr());
return CELL_PNGDEC_ERROR_OPEN_FILE;
}
// Read the header
if (file_stream.read(header, 8) != 8)
{
cellPngDec.error("PNG header is too small.");
return CELL_PNGDEC_ERROR_HEADER;
}
// Get the file descriptor
buffer->fd = idm::make<lv2_fs_object, lv2_file>(stream->source.fileName.get_ptr(), std::move(file_stream), 0, 0, real_path);
// Indicate that we need to read from a file stream
buffer->file = true;
}
else
{
// We can simply copy the first 8 bytes
memcpy(header, stream->source.streamPtr.get_ptr(), 8);
}
// Check if the header indicates a valid PNG file
if (png_sig_cmp(header, 0, 8))
{
cellPngDec.error("PNG signature is invalid.");
return CELL_PNGDEC_ERROR_HEADER;
}
// Create a libpng structure, also pass our custom error/warning functions
stream->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, pngDecError, pngDecWarning);
// Check if the creation of the structure failed
if (!stream->png_ptr)
{
cellPngDec.error("Creation of png_structp failed.");
return CELL_PNGDEC_ERROR_FATAL;
}
// Create a libpng info structure
stream->info_ptr = png_create_info_struct(stream->png_ptr);
// Check if the creation of the structure failed
if (!stream->info_ptr)
{
fmt::throw_exception("Creation of png_infop failed.");
}
// We must indicate, that we allocated more memory
open_info->initSpaceAllocated += u32{sizeof(PngBuffer)};
if (source->srcSelect == CELL_PNGDEC_BUFFER)
{
buffer->length = stream->source.streamSize;
buffer->data = stream->source.streamPtr;
buffer->cursor = 8;
}
// Set the custom read function for decoding
if (control_stream)
{
if (open_param && open_param->selectChunk != 0u)
fmt::throw_exception("Partial Decoding with selectChunk not supported yet.");
stream->cbCtrlStream.cbCtrlStrmArg = control_stream->cbCtrlStrmArg;
stream->cbCtrlStream.cbCtrlStrmFunc = control_stream->cbCtrlStrmFunc;
png_set_progressive_read_fn(stream->png_ptr, stream.get_ptr(), pngDecInfoCallback, pngDecRowCallback, pngDecEndCallback);
// push header tag to libpng to keep us in sync
png_process_data(stream->png_ptr, stream->info_ptr, header, 8);
}
else
{
png_set_read_fn(stream->png_ptr, buffer.get_ptr(), pngDecReadBuffer);
// We need to tell libpng, that we already read 8 bytes
png_set_sig_bytes(stream->png_ptr, 8);
}
return CELL_OK;
}
error_code pngDecClose(ppu_thread& ppu, PHandle handle, PStream stream)
{
// Remove the file descriptor, if a file descriptor was used for decoding
if (stream->buffer->file)
{
idm::remove<lv2_fs_object, lv2_file>(stream->buffer->fd);
}
// Deallocate the PNG buffer structure used to decode from memory, if we decoded from memory
if (stream->buffer)
{
if (handle->free_(ppu, stream->buffer, handle->free_arg) != 0)
{
cellPngDec.error("PNG buffer decoding structure deallocation failed.");
return CELL_PNGDEC_ERROR_FATAL;
}
}
// Free the memory allocated by libpng
png_destroy_read_struct(&stream->png_ptr, &stream->info_ptr, nullptr);
// Deallocate the stream memory
if (handle->free_(ppu, stream, handle->free_arg) != 0)
{
cellPngDec.error("PNG stream deallocation failed.");
return CELL_PNGDEC_ERROR_FATAL;
}
return CELL_OK;
}
void pngSetHeader(PngStream* stream)
{
stream->info.imageWidth = png_get_image_width(stream->png_ptr, stream->info_ptr);
stream->info.imageHeight = png_get_image_height(stream->png_ptr, stream->info_ptr);
stream->info.numComponents = png_get_channels(stream->png_ptr, stream->info_ptr);
stream->info.colorSpace = getPngDecColourType(png_get_color_type(stream->png_ptr, stream->info_ptr));
stream->info.bitDepth = png_get_bit_depth(stream->png_ptr, stream->info_ptr);
stream->info.interlaceMethod = png_get_interlace_type(stream->png_ptr, stream->info_ptr);
stream->info.chunkInformation = pngDecGetChunkInformation(stream);
}
error_code pngDecSetParameter(PStream stream, PInParam in_param, POutParam out_param, PExtInParam extra_in_param = vm::null, PExtOutParam extra_out_param = vm::null)
{
if (in_param->outputPackFlag == CELL_PNGDEC_1BYTE_PER_NPIXEL)
{
fmt::throw_exception("Packing not supported! (%d)", in_param->outputPackFlag);
}
// flag to keep unknown chunks
png_set_keep_unknown_chunks(stream->png_ptr, PNG_HANDLE_CHUNK_IF_SAFE, nullptr, 0);
// Scale 16 bit depth down to 8 bit depth.
if (stream->info.bitDepth == 16u && in_param->outputBitDepth == 8u)
{
// PS3 uses png_set_strip_16, since png_set_scale_16 wasn't available back then.
png_set_strip_16(stream->png_ptr);
}
// This shouldnt ever happen, but not sure what to do if it does, just want it logged for now
if (stream->info.bitDepth != 16u && in_param->outputBitDepth == 16u)
cellPngDec.error("Output depth of 16 with non input depth of 16 specified!");
if (in_param->commandPtr)
cellPngDec.warning("Ignoring CommandPtr.");
if (stream->info.colorSpace != in_param->outputColorSpace)
{
// check if we need to set alpha
const bool inputHasAlpha = cellPngColorSpaceHasAlpha(stream->info.colorSpace);
const bool outputWantsAlpha = cellPngColorSpaceHasAlpha(in_param->outputColorSpace);
if (outputWantsAlpha && !inputHasAlpha)
{
if (in_param->outputAlphaSelect == CELL_PNGDEC_FIX_ALPHA)
png_set_add_alpha(stream->png_ptr, in_param->outputColorAlpha, in_param->outputColorSpace == CELL_PNGDEC_ARGB ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
else
{
// Check if we can steal the alpha from a trns block
if (png_get_valid(stream->png_ptr, stream->info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(stream->png_ptr);
// if not, just set default of 0xff
else
png_set_add_alpha(stream->png_ptr, 0xff, in_param->outputColorSpace == CELL_PNGDEC_ARGB ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
}
}
else if (inputHasAlpha && !outputWantsAlpha)
png_set_strip_alpha(stream->png_ptr);
else if (in_param->outputColorSpace == CELL_PNGDEC_ARGB && stream->info.colorSpace == CELL_PNGDEC_RGBA)
png_set_swap_alpha(stream->png_ptr);
// Handle gray<->rgb colorspace conversions
// rgb output
if (in_param->outputColorSpace == CELL_PNGDEC_ARGB
|| in_param->outputColorSpace == CELL_PNGDEC_RGBA
|| in_param->outputColorSpace == CELL_PNGDEC_RGB)
{
if (stream->info.colorSpace == CELL_PNGDEC_PALETTE)
png_set_palette_to_rgb(stream->png_ptr);
if ((stream->info.colorSpace == CELL_PNGDEC_GRAYSCALE || stream->info.colorSpace == CELL_PNGDEC_GRAYSCALE_ALPHA)
&& stream->info.bitDepth < 8)
png_set_expand_gray_1_2_4_to_8(stream->png_ptr);
}
// grayscale output
else
{
if (stream->info.colorSpace == CELL_PNGDEC_ARGB
|| stream->info.colorSpace == CELL_PNGDEC_RGBA
|| stream->info.colorSpace == CELL_PNGDEC_RGB)
{
png_set_rgb_to_gray(stream->png_ptr, PNG_ERROR_ACTION_NONE, PNG_RGB_TO_GRAY_DEFAULT, PNG_RGB_TO_GRAY_DEFAULT);
}
else {
// not sure what to do here
cellPngDec.error("Grayscale / Palette to Grayscale / Palette conversion currently unsupported.");
}
}
}
stream->passes = png_set_interlace_handling(stream->png_ptr);
// Update the info structure
png_read_update_info(stream->png_ptr, stream->info_ptr);
stream->out_param.outputWidth = stream->info.imageWidth;
stream->out_param.outputHeight = stream->info.imageHeight;
stream->out_param.outputBitDepth = in_param->outputBitDepth;
stream->out_param.outputColorSpace = in_param->outputColorSpace;
stream->out_param.outputMode = in_param->outputMode;
stream->out_param.outputWidthByte = png_get_rowbytes(stream->png_ptr, stream->info_ptr);
stream->out_param.outputComponents = png_get_channels(stream->png_ptr, stream->info_ptr);
stream->packing = in_param->outputPackFlag;
// Set the memory usage. We currently don't actually allocate memory for libpng through the callbacks, due to libpng needing a lot more memory compared to PS3 variant.
stream->out_param.useMemorySpace = 0;
if (extra_in_param)
{
if (extra_in_param->bufferMode != CELL_PNGDEC_LINE_MODE)
{
cellPngDec.error("Invalid Buffermode specified.");
return CELL_PNGDEC_ERROR_ARG;
}
if (stream->passes > 1)
{
stream->outputCounts = 1;
}
else
stream->outputCounts = extra_in_param->outputCounts;
if (extra_out_param)
{
if (stream->outputCounts == 0)
extra_out_param->outputHeight = stream->out_param.outputHeight;
else
extra_out_param->outputHeight = std::min(stream->outputCounts, stream->out_param.outputHeight.value());
extra_out_param->outputWidthByte = stream->out_param.outputWidthByte;
}
}
*out_param = stream->out_param;
return CELL_OK;
}
error_code pngDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr<u8> data, PDataControlParam data_control_param, PDataOutInfo data_out_info, PCbControlDisp cb_control_disp = vm::null, PDispParam disp_param = vm::null)
{
// Indicate, that the PNG decoding is stopped/failed. This is incase, we return an error code in the middle of decoding
data_out_info->status = CELL_PNGDEC_DEC_STATUS_STOP;
const u32 bytes_per_line = ::narrow<u32>(data_control_param->outputBytesPerLine);
// Log this for now
if (bytes_per_line < stream->out_param.outputWidthByte)
{
fmt::throw_exception("Bytes per line less than expected output! Got: %d, expected: %d", bytes_per_line, stream->out_param.outputWidthByte);
}
// partial decoding
if (cb_control_disp && stream->outputCounts > 0)
{
// get data from cb
auto streamInfo = vm::ptr<CellPngDecStrmInfo>::make(handle->malloc_(ppu, sizeof(CellPngDecStrmInfo), handle->malloc_arg).addr());
auto streamParam = vm::ptr<CellPngDecStrmParam>::make(handle->malloc_(ppu, sizeof(CellPngDecStrmParam), handle->malloc_arg).addr());
stream->cbDispInfo = vm::ptr<CellPngDecDispInfo>::make(handle->malloc_(ppu, sizeof(CellPngDecDispInfo), handle->malloc_arg).addr());
stream->cbDispParam = vm::ptr<CellPngDecDispParam>::make(handle->malloc_(ppu, sizeof(CellPngDecDispParam), handle->malloc_arg).addr());
auto freeMem = [&]()
{
handle->free_(ppu, streamInfo, handle->free_arg);
handle->free_(ppu, streamParam, handle->free_arg);
handle->free_(ppu, stream->cbDispInfo, handle->free_arg);
handle->free_(ppu, stream->cbDispParam, handle->free_arg);
};
// set things that won't change between callbacks
stream->cbDispInfo->outputFrameWidthByte = bytes_per_line;
stream->cbDispInfo->outputFrameHeight = stream->out_param.outputHeight;
stream->cbDispInfo->outputWidthByte = stream->out_param.outputWidthByte;
stream->cbDispInfo->outputBitDepth = stream->out_param.outputBitDepth;
stream->cbDispInfo->outputComponents = stream->out_param.outputComponents;
stream->cbDispInfo->outputHeight = stream->outputCounts;
stream->cbDispInfo->outputStartXByte = 0;
stream->cbDispInfo->outputStartY = 0;
stream->cbDispInfo->scanPassCount = 0;
stream->cbDispInfo->nextOutputStartY = 0;
stream->ppuContext = &ppu;
stream->nextRow = stream->cbDispInfo->outputHeight;
stream->cbCtrlDisp.cbCtrlDispArg = cb_control_disp->cbCtrlDispArg;
stream->cbCtrlDisp.cbCtrlDispFunc = cb_control_disp->cbCtrlDispFunc;
stream->cbDispParam->nextOutputImage = disp_param->nextOutputImage;
streamInfo->decodedStrmSize = ::narrow<u32>(stream->buffer->cursor);
// push the rest of the buffer we have
if (stream->buffer->length > stream->buffer->cursor)
{
u8* data = static_cast<u8*>(stream->buffer->data.get_ptr()) + stream->buffer->cursor;
png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length - stream->buffer->cursor);
streamInfo->decodedStrmSize = ::narrow<u32>(stream->buffer->length);
}
// todo: commandPtr
// then just loop until the end, the callbacks should take care of the rest
while (stream->endOfFile != true)
{
stream->cbCtrlStream.cbCtrlStrmFunc(ppu, streamInfo, streamParam, stream->cbCtrlStream.cbCtrlStrmArg);
streamInfo->decodedStrmSize += streamParam->strmSize;
png_process_data(stream->png_ptr, stream->info_ptr, static_cast<u8*>(streamParam->strmPtr.get_ptr()), streamParam->strmSize);
}
freeMem();
}
else
{
// Check if the image needs to be flipped
const bool flip = stream->out_param.outputMode == CELL_PNGDEC_BOTTOM_TO_TOP;
// Decode the image
// todo: commandptr
{
for (u32 j = 0; j < stream->passes; j++)
{
for (u32 i = 0; i < stream->out_param.outputHeight; ++i)
{
const u32 line = flip ? stream->out_param.outputHeight - i - 1 : i;
png_read_row(stream->png_ptr, &data[line*bytes_per_line], nullptr);
}
}
png_read_end(stream->png_ptr, stream->info_ptr);
}
}
// Get the number of iTXt, tEXt and zTXt chunks
const s32 text_chunks = png_get_text(stream->png_ptr, stream->info_ptr, nullptr, nullptr);
// Set the chunk information and the previously obtained number of text chunks
data_out_info->numText = static_cast<u32>(text_chunks);
data_out_info->chunkInformation = pngDecGetChunkInformation(stream.get_ptr(), true);
png_unknown_chunkp unknowns;
const int num_unknowns = png_get_unknown_chunks(stream->png_ptr, stream->info_ptr, &unknowns);
data_out_info->numUnknownChunk = num_unknowns;
// Indicate that the decoding succeeded
data_out_info->status = CELL_PNGDEC_DEC_STATUS_FINISH;
return CELL_OK;
}
error_code cellPngDecCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInParam, PThreadOutParam threadOutParam)
{
cellPngDec.warning("cellPngDecCreate(handle=**0x%x, threadInParam=*0x%x, threadOutParam=*0x%x)", handle, threadInParam, threadOutParam);
return pngDecCreate(ppu, handle, threadInParam, threadOutParam);
}
error_code cellPngDecExtCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInParam, PThreadOutParam threadOutParam, PExtThreadInParam extThreadInParam, PExtThreadOutParam extThreadOutParam)
{
cellPngDec.warning("cellPngDecExtCreate(mainHandle=**0x%x, threadInParam=*0x%x, threadOutParam=*0x%x, extThreadInParam=*0x%x, extThreadOutParam=*0x%x)", handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam);
return pngDecCreate(ppu, handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam);
}
error_code cellPngDecDestroy(ppu_thread& ppu, PHandle handle)
{
cellPngDec.warning("cellPngDecDestroy(mainHandle=*0x%x)", handle);
return pngDecDestroy(ppu, handle);
}
error_code cellPngDecOpen(ppu_thread& ppu, PHandle handle, PPStream stream, PSrc src, POpenInfo openInfo)
{
cellPngDec.warning("cellPngDecOpen(handle=*0x%x, stream=**0x%x, src=*0x%x, openInfo=*0x%x)", handle, stream, src, openInfo);
return pngDecOpen(ppu, handle, stream, src, openInfo);
}
error_code cellPngDecExtOpen(ppu_thread& ppu, PHandle handle, PPStream stream, PSrc src, POpenInfo openInfo, PCbControlStream cbCtrlStrm, POpenParam opnParam)
{
cellPngDec.warning("cellPngDecExtOpen(handle=*0x%x, stream=**0x%x, src=*0x%x, openInfo=*0x%x, cbCtrlStrm=*0x%x, opnParam=*0x%x)", handle, stream, src, openInfo, cbCtrlStrm, opnParam);
return pngDecOpen(ppu, handle, stream, src, openInfo, cbCtrlStrm, opnParam);
}
error_code cellPngDecClose(ppu_thread& ppu, PHandle handle, PStream stream)
{
cellPngDec.warning("cellPngDecClose(handle=*0x%x, stream=*0x%x)", handle, stream);
return pngDecClose(ppu, handle, stream);
}
error_code cellPngDecReadHeader(PHandle handle, PStream stream, PInfo info)
{
cellPngDec.warning("cellPngDecReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x)", handle, stream, info);
// Read the header info
png_read_info(stream->png_ptr, stream->info_ptr);
pngSetHeader(stream.get_ptr());
// Set the pointer to stream info
*info = stream->info;
return CELL_OK;
}
error_code cellPngDecExtReadHeader(PHandle handle, PStream stream, PInfo info, PExtInfo extInfo)
{
cellPngDec.warning("cellPngDecExtReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x, extInfo=*0x%x)", handle, stream, info, extInfo);
// Set the reserved value to 0, if passed to the function. (Should this be arg error if they dont pass?)
if (extInfo)
{
extInfo->reserved = 0;
}
// lets push what we have so far
u8* data = static_cast<u8*>(stream->buffer->data.get_ptr()) + stream->buffer->cursor;
png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length);
// lets hope we pushed enough for callback
pngSetHeader(stream.get_ptr());
// png doesnt allow empty image, so quick check for 0 verifys if we got the header
// not sure exactly what should happen if we dont have header, ask for more data with callback?
if (stream->info.imageWidth == 0u)
{
fmt::throw_exception("Invalid or not enough data sent to get header");
return CELL_PNGDEC_ERROR_HEADER;
}
// Set the pointer to stream info
*info = stream->info;
return CELL_OK;
}
error_code cellPngDecSetParameter(PHandle handle, PStream stream, PInParam inParam, POutParam outParam)
{
cellPngDec.warning("cellPngDecSetParameter(handle=*0x%x, stream=*0x%x, inParam=*0x%x, outParam=*0x%x)", handle, stream, inParam, outParam);
return pngDecSetParameter(stream, inParam, outParam);
}
error_code cellPngDecExtSetParameter(PHandle handle, PStream stream, PInParam inParam, POutParam outParam, PExtInParam extInParam, PExtOutParam extOutParam)
{
cellPngDec.warning("cellPngDecExtSetParameter(handle=*0x%x, stream=*0x%x, inParam=*0x%x, outParam=*0x%x, extInParam=*0x%x, extOutParam=*0x%x", handle, stream, inParam, outParam, extInParam, extOutParam);
return pngDecSetParameter(stream, inParam, outParam, extInParam, extOutParam);
}
error_code cellPngDecDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr<u8> data, PDataControlParam dataCtrlParam, PDataOutInfo dataOutInfo)
{
cellPngDec.warning("cellPngDecDecodeData(handle=*0x%x, stream=*0x%x, data=*0x%x, dataCtrlParam=*0x%x, dataOutInfo=*0x%x)", handle, stream, data, dataCtrlParam, dataOutInfo);
return pngDecodeData(ppu, handle, stream, data, dataCtrlParam, dataOutInfo);
}
error_code cellPngDecExtDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr<u8> data, PDataControlParam dataCtrlParam, PDataOutInfo dataOutInfo, PCbControlDisp cbCtrlDisp, PDispParam dispParam)
{
cellPngDec.warning("cellPngDecExtDecodeData(handle=*0x%x, stream=*0x%x, data=*0x%x, dataCtrlParam=*0x%x, dataOutInfo=*0x%x, cbCtrlDisp=*0x%x, dispParam=*0x%x)", handle, stream, data, dataCtrlParam, dataOutInfo, cbCtrlDisp, dispParam);
return pngDecodeData(ppu, handle, stream, data, dataCtrlParam, dataOutInfo, cbCtrlDisp, dispParam);
}
error_code cellPngDecGetUnknownChunks(PHandle handle, PStream stream, vm::pptr<CellPngUnknownChunk> unknownChunk, vm::ptr<u32> unknownChunkNumber)
{
cellPngDec.todo("cellPngDecGetUnknownChunks()");
return CELL_OK;
}
error_code cellPngDecGetpCAL(PHandle handle, PStream stream, vm::ptr<CellPngPCAL> pcal)
{
cellPngDec.todo("cellPngDecGetpCAL()");
return CELL_OK;
}
error_code cellPngDecGetcHRM(PHandle handle, PStream stream, vm::ptr<CellPngCHRM> chrm)
{
cellPngDec.todo("cellPngDecGetcHRM()");
return CELL_OK;
}
error_code cellPngDecGetsCAL(PHandle handle, PStream stream, vm::ptr<CellPngSCAL> scal)
{
cellPngDec.todo("cellPngDecGetsCAL()");
return CELL_OK;
}
error_code cellPngDecGetpHYs(PHandle handle, PStream stream, vm::ptr<CellPngPHYS> phys)
{
cellPngDec.todo("cellPngDecGetpHYs()");
return CELL_OK;
}
error_code cellPngDecGetoFFs(PHandle handle, PStream stream, vm::ptr<CellPngOFFS> offs)
{
cellPngDec.todo("cellPngDecGetoFFs()");
return CELL_OK;
}
error_code cellPngDecGetsPLT(PHandle handle, PStream stream, vm::ptr<CellPngSPLT> splt)
{
cellPngDec.todo("cellPngDecGetsPLT()");
return CELL_OK;
}
error_code cellPngDecGetbKGD(PHandle handle, PStream stream, vm::ptr<CellPngBKGD> bkgd)
{
cellPngDec.todo("cellPngDecGetbKGD()");
return CELL_OK;
}
error_code cellPngDecGettIME(PHandle handle, PStream stream, vm::ptr<CellPngTIME> time)
{
cellPngDec.todo("cellPngDecGettIME()");
return CELL_OK;
}
error_code cellPngDecGethIST(PHandle handle, PStream stream, vm::ptr<CellPngHIST> hist)
{
cellPngDec.todo("cellPngDecGethIST()");
return CELL_OK;
}
error_code cellPngDecGettRNS(PHandle handle, PStream stream, vm::ptr<CellPngTRNS> trns)
{
cellPngDec.todo("cellPngDecGettRNS()");
return CELL_OK;
}
error_code cellPngDecGetsBIT(PHandle handle, PStream stream, vm::ptr<CellPngSBIT> sbit)
{
cellPngDec.todo("cellPngDecGetsBIT()");
return CELL_OK;
}
error_code cellPngDecGetiCCP(PHandle handle, PStream stream, vm::ptr<CellPngICCP> iccp)
{
cellPngDec.todo("cellPngDecGetiCCP()");
return CELL_OK;
}
error_code cellPngDecGetsRGB(PHandle handle, PStream stream, vm::ptr<CellPngSRGB> srgb)
{
cellPngDec.todo("cellPngDecGetsRGB()");
return CELL_OK;
}
error_code cellPngDecGetgAMA(PHandle handle, PStream stream, vm::ptr<CellPngGAMA> gama)
{
cellPngDec.todo("cellPngDecGetgAMA()");
return CELL_OK;
}
error_code cellPngDecGetPLTE(PHandle handle, PStream stream, vm::ptr<CellPngPLTE> plte)
{
cellPngDec.todo("cellPngDecGetPLTE()");
return CELL_OK;
}
error_code cellPngDecGetTextChunk(PHandle handle, PStream stream, vm::ptr<u32> textInfoNum, vm::pptr<CellPngTextInfo> textInfo)
{
cellPngDec.todo("cellPngDecGetTextChunk()");
return CELL_OK;
}
DECLARE(ppu_module_manager::cellPngDec)("cellPngDec", []()
{
REG_FUNC(cellPngDec, cellPngDecGetUnknownChunks);
REG_FUNC(cellPngDec, cellPngDecClose);
REG_FUNC(cellPngDec, cellPngDecGetpCAL);
REG_FUNC(cellPngDec, cellPngDecGetcHRM);
REG_FUNC(cellPngDec, cellPngDecGetsCAL);
REG_FUNC(cellPngDec, cellPngDecGetpHYs);
REG_FUNC(cellPngDec, cellPngDecGetoFFs);
REG_FUNC(cellPngDec, cellPngDecGetsPLT);
REG_FUNC(cellPngDec, cellPngDecGetbKGD);
REG_FUNC(cellPngDec, cellPngDecGettIME);
REG_FUNC(cellPngDec, cellPngDecGethIST);
REG_FUNC(cellPngDec, cellPngDecGettRNS);
REG_FUNC(cellPngDec, cellPngDecGetsBIT);
REG_FUNC(cellPngDec, cellPngDecGetiCCP);
REG_FUNC(cellPngDec, cellPngDecGetsRGB);
REG_FUNC(cellPngDec, cellPngDecGetgAMA);
REG_FUNC(cellPngDec, cellPngDecGetPLTE);
REG_FUNC(cellPngDec, cellPngDecGetTextChunk);
REG_FUNC(cellPngDec, cellPngDecDestroy);
REG_FUNC(cellPngDec, cellPngDecCreate);
REG_FUNC(cellPngDec, cellPngDecExtCreate);
REG_FUNC(cellPngDec, cellPngDecExtSetParameter);
REG_FUNC(cellPngDec, cellPngDecSetParameter);
REG_FUNC(cellPngDec, cellPngDecExtReadHeader);
REG_FUNC(cellPngDec, cellPngDecReadHeader);
REG_FUNC(cellPngDec, cellPngDecExtOpen);
REG_FUNC(cellPngDec, cellPngDecOpen);
REG_FUNC(cellPngDec, cellPngDecExtDecodeData);
REG_FUNC(cellPngDec, cellPngDecDecodeData);
});