diff options
| author | spicyjpeg <88942473+spicyjpeg@users.noreply.github.com> | 2022-02-27 19:47:47 +0100 |
|---|---|---|
| committer | spicyjpeg <88942473+spicyjpeg@users.noreply.github.com> | 2022-02-27 19:47:47 +0100 |
| commit | c297c02652575e2affccbba16be0176d30d5ff06 (patch) | |
| tree | 6f9ae00bff86811ffbffadd7c64248cde088c0f9 | |
| parent | 69a364049a3958396d2d083c660591dad9ec257d (diff) | |
| download | psn00bsdk-c297c02652575e2affccbba16be0176d30d5ff06.tar.gz | |
Add psxpress MDEC API and psn00bsdk_target_incbin_a
| -rw-r--r-- | doc/cmake_reference.md | 36 | ||||
| -rw-r--r-- | libpsn00b/cmake/internal_setup.cmake | 48 | ||||
| -rw-r--r-- | libpsn00b/include/psxpress.h | 152 | ||||
| -rw-r--r-- | libpsn00b/psxpress/mdec.c | 172 |
4 files changed, 383 insertions, 25 deletions
diff --git a/doc/cmake_reference.md b/doc/cmake_reference.md index 60c46c0..25a89ec 100644 --- a/doc/cmake_reference.md +++ b/doc/cmake_reference.md @@ -4,17 +4,17 @@ ## Setup The only requirement to use the SDK in CMake is to set the -`CMAKE_TOOLCHAIN_FILE` variable to `INSTALL_PATH/lib/libpsn00b/cmake/sdk.cmake` -(where `INSTALL_PATH` is the install prefix PSn00bSDK is installed to). This -can be done on the command line (`-DCMAKE_TOOLCHAIN_FILE=...`), in -`CMakeLists.txt` (before calling `project()`) or using a -[preset](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html). +`CMAKE_TOOLCHAIN_FILE` variable to the absolute path to +`lib/libpsn00b/cmake/sdk.cmake` within the PSn00bSDK installation directory. +This can be done on the command line (`-DCMAKE_TOOLCHAIN_FILE=...`), in +`CMakeLists.txt` (`set(CMAKE_TOOLCHAIN_FILE ...)` before `project()`) or using +[presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html). It's suggested to have a default preset that sets `CMAKE_TOOLCHAIN_FILE` to -`$env{PSN00BSDK_LIBS}/cmake/sdk.cmake`, so the `PSN00BSDK_LIBS` environment -variable (used by former PSn00bSDK versions) is respected. Such a preset can be -created by placing a `CMakePresets.json` file in the project's root with the -following contents: +`$env{PSN00BSDK_LIBS}/cmake/sdk.cmake`, taking advantage of the +`PSN00BSDK_LIBS` environment variable (used by former PSn00bSDK versions) to +automatically find the SDK. Such a preset can be created by placing a +`CMakePresets.json` file in the project's root with the following contents: ```json { @@ -135,9 +135,12 @@ of these names. A new symbol/object will be created with the given name, escaped by replacing non-alphanumeric characters with underscores. The contents of the file will - be aligned to 4 bytes and placed in the `.data` section. Once added the file - and its size can be accessed by C/C++ code by declaring the symbol as an - extern array and `<symbol name>_size` as an integer, like this: + be aligned to 4 bytes and placed in the `.data` section. An unsigned 32-bit + integer named `<symbol name>_size` will also be defined and set to the length + of the file in bytes (without taking alignment/padding into account). + + Once added the file and its size can be accessed by C/C++ code by declaring + the respective symbols as an extern array and as an integer, like this: ```c extern const uint8_t my_file[]; @@ -154,6 +157,13 @@ of these names. must be enabled by specifying `LANGUAGES C ASM` (or `LANGUAGES C CXX ASM` if C++ is also used) when invoking `project()`. +- `psn00bsdk_target_incbin_a(<target> <PRIVATE|PUBLIC|INTERFACE> <symbol name> <size symbol name> <binary file> <alignment>)` + + Advanced variant of `psn00bsdk_target_incbin()` that allows specifying a + custom name for the size symbol and changing the default alignment setting. + Note that the size integer is always aligned to a multiple of 4 bytes as the + MIPS architecture doesn't support unaligned reads. + ## Definitions When compiling executables and libraries using the above commands the following @@ -259,4 +269,4 @@ the build script. LZP archives as part of the build pipeline. ----------------------------------------- -_Last updated on 2021-12-29 by spicyjpeg_ +_Last updated on 2022-02-26 by spicyjpeg_ diff --git a/libpsn00b/cmake/internal_setup.cmake b/libpsn00b/cmake/internal_setup.cmake index 8fb6482..d293127 100644 --- a/libpsn00b/cmake/internal_setup.cmake +++ b/libpsn00b/cmake/internal_setup.cmake @@ -32,7 +32,7 @@ set( psxgte psxspu psxcd - #psxpress + psxpress psxsio psxetc psxapi @@ -186,7 +186,7 @@ function(psn00bsdk_add_cd_image name image_name config_file) add_custom_target( ${name} ALL - COMMAND ${MKPSXISO} -y -q ${CD_CONFIG_FILE} + COMMAND ${MKPSXISO} -y ${CD_CONFIG_FILE} BYPRODUCTS ${image_name}.bin ${image_name}.cue COMMENT "Building CD image ${image_name}" ${ARGN} @@ -195,13 +195,17 @@ endfunction() ## Helper functions for assets -# psn00bsdk_target_incbin( +# psn00bsdk_target_incbin_a( # <existing target name> <PRIVATE|PUBLIC|INTERFACE> -# <symbol name> +# <data symbol name> +# <size symbol name> # <path to binary file> +# <linker section name> +# <alignment> # ) -function(psn00bsdk_target_incbin name type symbol_name path) +function(psn00bsdk_target_incbin_a name type symbol_name size_name path section align) string(MAKE_C_IDENTIFIER ${symbol_name} _id) + string(MAKE_C_IDENTIFIER ${size_name} _size) cmake_path(ABSOLUTE_PATH path OUTPUT_VARIABLE _path) # Generate an assembly source file that includes the binary file and add it @@ -212,26 +216,27 @@ function(psn00bsdk_target_incbin name type symbol_name path) CONFIGURE OUTPUT ${_asm_file} CONTENT [[ -.section .data.${_id} -.balign 4 +.section ${section} +.balign ${align} .global ${_id} .type ${_id}, @object -${_id}: +${_id}: .incbin "${_path}" .local ${_id}_end ${_id}_end: +.balign ${align} .balign 4 -.global ${_id}_size -.type ${_id}_size, @object -${_id}_size: +.global ${_size} +.type ${_size}, @object +${_size}: .int (${_id}_end - ${_id}) .size ${_id}, (${_id}_end - ${_id}) -.size ${_id}_size, 4 +.size ${_size}, 4 ]] ESCAPE_QUOTES NEWLINE_STYLE LF @@ -240,3 +245,22 @@ ${_id}_size: target_sources(${name} ${type} ${_asm_file}) set_source_files_properties(${_asm_file} PROPERTIES OBJECT_DEPENDS ${_path}) endfunction() + +# psn00bsdk_target_incbin( +# <existing target name> <PRIVATE|PUBLIC|INTERFACE> +# <symbol name> +# <path to binary file> +# ) +function(psn00bsdk_target_incbin name type symbol_name path) + string(MAKE_C_IDENTIFIER ${symbol_name} _id) + + psn00bsdk_target_incbin_a( + ${name} + ${type} + ${_id} + ${_id}_size + ${path} + .data.${_id} + 4 + ) +endfunction() diff --git a/libpsn00b/include/psxpress.h b/libpsn00b/include/psxpress.h new file mode 100644 index 0000000..ad5f6a3 --- /dev/null +++ b/libpsn00b/include/psxpress.h @@ -0,0 +1,152 @@ +/* + * PSn00bSDK MDEC library + * (C) 2022 spicyjpeg - MPL licensed + */ + +#ifndef __PSXPRESS_H +#define __PSXPRESS_H + +#include <stdint.h> + +/* Structure definitions */ + +typedef struct _DECDCTENV { + uint8_t iq_y[64]; // Luma quantization table, stored in zigzag order + uint8_t iq_c[64]; // Chroma quantization table, stored in zigzag order + int16_t dct[64]; // Inverse DCT matrix (2.14 fixed-point) +} DECDCTENV; + +typedef enum _DECDCTMODE { + DECDCT_MODE_24BPP = 1, + DECDCT_MODE_16BPP = 0, + DECDCT_MODE_16BPP_BIT15 = 2, + DECDCT_MODE_RAW = -1 +} DECDCTMODE; + +/* Public API */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Resets the MDEC and aborts any MDEC DMA transfers. If mode = 0, the + * default IDCT matrix and quantization tables are also loaded and the MDEC is + * put into color output mode, discarding any custom environment previously set + * with DecDCTPutEnv(). + * + * DecDCTReset(0) must be called at least once prior to using the MDEC. + * + * @param mode + */ +void DecDCTReset(int32_t mode); + +/** + * @brief Uploads the specified decoding environment's quantization tables and + * IDCT matrix to the MDEC, or restores the default tables if a null pointer is + * passed. Calling this function is normally not required as DecDCTReset(0) + * initializes the MDEC with the default tables, but it may be useful for e.g. + * decoding JPEG or a format with custom quantization tables. + * + * The second argument, not present in the official SDK, specifies whether the + * MDEC shall be put into color (0) or monochrome (1) output mode. In + * monochrome mode each DCT block decoded from the input stream is transformed + * into an 8x8x8bpp bitmap, while in color mode each group of 6 DCT blocks (Cr, + * Cb, Y1-4) is used to form a 16x16 RGB bitmap. + * + * This function uses DecDCTinSync() to wait for the MDEC to become ready and + * should not be called during decoding or after calling DecDCTin(). + * + * @param env Pointer to DECDCTENV or 0 for default tables + * @param mono 0 for color (normal), 1 for monochrome + */ +void DecDCTPutEnv(const DECDCTENV *env, int32_t mono); + +/** + * @brief Sets up the MDEC to start fetching and decoding a stream from the + * given address in main RAM. The first 32-bit word is initially copied to the + * MDEC0 register, then all subsequent data is read in 128-byte (32-word) + * chunks. The length of the stream (in 32-bit units, minus the first word) + * must be encoded in the lower 16 bits of the first word, as expected by the + * MDEC. + * + * The mode argument optionally specifies the output color depth (0 for 16bpp, + * 1 for 24bpp) if not already set in the first word. Passing -1 will result in + * DecDCTin() copying the first word as-is to MDEC0 without manipulating any of + * its bits. + * + * @param data + * @param mode DECDCT_MODE_* or -1 + */ +void DecDCTin(const uint32_t *data, int32_t mode); + +/** + * @brief Configures the MDEC to automatically fetch data (the input stream, + * IDCT matrix or quantization tables) in 128-byte (32-word) chunks from the + * specified address in main RAM. The transfer is stopped, and any callback + * registered with DMACallback(0) is fired, once a certain number of 32-bit + * words have been read; usually the length should match the number of input + * words expected by the MDEC. If the MDEC expects more data its operation will + * be paused and can be resumed by calling DecDCTinRaw() again. + * + * This is a low-level variant of DecDCTin() that only sets up the DMA transfer + * and does not write anything to the MDEC0 register. The actual transfer won't + * start until the MDEC is given a valid command. + * + * @param data + * @param length Number of 32-bit words to read (must be multiple of 32) + */ +void DecDCTinRaw(const uint32_t *data, size_t length); + +/** + * @brief Waits for the MDEC to finish decoding the input stream (if mode = 0) + * or returns whether it is busy (if mode = 1). MDEC commands can be issued + * only when the MDEC isn't busy. + * + * WARNING: DecDCTinSync(0) might time out and return -1 if the MDEC can't + * output decoded data, e.g. if the length passed DecDCTout() was too small and + * no callback is registered to set up further transfers. DecDCTinSync(0) shall + * only be used alongside DMACallback(1) or if the entirety of the decoded + * stream (usually a whole frame) is being written to main RAM. + * + * @param mode + * @return 0 or -1 in case of a timeout (mode = 0) / MDEC busy flag (mode = 1) + */ +int32_t DecDCTinSync(int32_t mode); + +/** + * @brief Configures the MDEC to automatically transfer decoded image data in + * 128-byte (32-word) chunks to the specified address in main RAM. MDEC + * operation is paused once a certain number of 32-bit words have been output + * and can be resumed by calling DecDCTout() again: the MDEC will continue + * decoding the input stream from where it left off. Any callback registered + * with DMACallback(1) is also fired whenever the transfer ends. + * + * This behavior allows the MDEC's output to be buffered into 16-pixel-wide + * vertical strips in main RAM, which can then be uploaded to VRAM using + * LoadImage(). + * + * @param data + * @param length Number of 32-bit words to output (must be multiple of 32) + */ +void DecDCTout(uint32_t *data, size_t length); + +/** + * @brief Waits until the transfer set up by DecDCTout() finishes (if mode = 0) + * or returns whether it is still in progress (if mode = 1). + * + * WARNING: DecDCToutSync(0) might time out and return -1 if the MDEC is unable + * to consume enough input data in order to produce the desired amount of data. + * If the input stream isn't contiguous in memory, DMACallback(0) shall be used + * to register a callback that calls DecDCTin() to feed the MDEC. + * + * @param mode + * @return 0 or -1 in case of a timeout (mode = 0) / DMA busy flag (mode = 1) + */ +int32_t DecDCToutSync(int32_t mode); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libpsn00b/psxpress/mdec.c b/libpsn00b/psxpress/mdec.c new file mode 100644 index 0000000..82e2465 --- /dev/null +++ b/libpsn00b/psxpress/mdec.c @@ -0,0 +1,172 @@ +/* + * PSn00bSDK MDEC library (low-level MDEC/DMA API) + * (C) 2022 spicyjpeg - MPL licensed + */ + +#include <stdint.h> +#include <stdio.h> +#include <psxapi.h> +#include <psxpress.h> +#include <hwregs_c.h> + +#define MDEC_SYNC_TIMEOUT 0x1000000 + +/* Default IDCT matrix */ + +#define S0 0x5a82 // 0x4000 * cos(0/16 * pi) * sqrt(2) +#define S1 0x7d8a // 0x4000 * cos(1/16 * pi) * 2 +#define S2 0x7641 // 0x4000 * cos(2/16 * pi) * 2 +#define S3 0x6a6d // 0x4000 * cos(3/16 * pi) * 2 +#define S4 0x5a82 // 0x4000 * cos(4/16 * pi) * 2 +#define S5 0x471c // 0x4000 * cos(5/16 * pi) * 2 +#define S6 0x30fb // 0x4000 * cos(6/16 * pi) * 2 +#define S7 0x18f8 // 0x4000 * cos(7/16 * pi) * 2 + +static const int16_t _default_idct_matrix[] = { + S0, S0, S0, S0, S0, S0, S0, S0, + S1, S3, S5, S7, -S7, -S5, -S3, -S1, + S2, S6, -S6, -S2, -S2, -S6, S6, S2, + S3, -S7, -S1, -S5, S5, S1, S7, -S3, + S4, -S4, -S4, S4, S4, -S4, -S4, S4, + S5, -S1, S7, S3, -S3, -S7, S1, -S5, + S6, -S2, S2, -S6, -S6, S2, -S2, S6, + S7, -S5, S3, -S1, S1, -S3, S5, -S7 +}; + +/* Default quantization tables */ + +// The default luma and chroma quantization table is based on the MPEG-1 +// quantization table, with the only difference being the first value (2 +// instead of 8). Note that quantization tables are stored in zigzag order +// rather than row- or column-major. +// https://problemkaputt.de/psx-spx.htm#mdecdecompression +static const uint8_t _default_quant_table[] = { + 2, 16, 16, 19, 16, 19, 22, 22, + 22, 22, 22, 22, 26, 24, 26, 27, + 27, 27, 26, 26, 26, 26, 27, 27, + 27, 29, 29, 29, 34, 34, 34, 29, + 29, 29, 27, 27, 29, 29, 32, 32, + 34, 34, 37, 38, 37, 35, 35, 34, + 35, 38, 38, 40, 40, 40, 48, 48, + 46, 46, 56, 56, 58, 69, 69, 83 +}; +/*static const uint8_t _jpeg_y_quant_table[] = { + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99 +}; +static const uint8_t _jpeg_c_quant_table[] = { + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +};*/ + +/* Public API */ + +void DecDCTReset(int32_t mode) { + EnterCriticalSection(); + + DMA_DPCR |= 0x000000bb; // Enable DMA0 and DMA1 + DMA_CHCR(0) = 0x00000201; // Stop DMA0 + DMA_CHCR(1) = 0x00000200; // Stop DMA1 + MDEC1 = 0x80000000; // Reset MDEC + MDEC1 = 0x60000000; // Enable DMA in/out requests + + ExitCriticalSection(); + if (!mode) + DecDCTPutEnv(0, 0); +} + +void DecDCTPutEnv(const DECDCTENV *env, int32_t mono) { + const int16_t *dct = env ? env->dct : _default_idct_matrix; + const uint8_t *iq_y = env ? env->iq_y : _default_quant_table; + const uint8_t *iq_c = env ? env->iq_c : _default_quant_table; + + DecDCTinSync(0); + + MDEC0 = 0x60000000; // Set IDCT matrix + DecDCTinRaw((const uint32_t *) dct, 32); + DecDCTinSync(0); + + MDEC0 = 0x40000000 | (mono ? 0 : 1); // Set table(s) + DecDCTinRaw((const uint32_t *) iq_y, 16); + DecDCTinSync(0); + + if (!mono) { + DecDCTinRaw((const uint32_t *) iq_c, 16); + DecDCTinSync(0); + } +} + +void DecDCTin(const uint32_t *data, int32_t mode) { + uint32_t header = *data; + if (mode == DECDCT_MODE_RAW) + MDEC0 = header; + else if (mode & DECDCT_MODE_24BPP) + MDEC0 = header | 0x30000000; + else + MDEC0 = header | 0x38000000 | ((mode & 2) << 24); // Bit 25 = mask + + DecDCTinRaw((const uint32_t *) &(data[1]), header & 0xffff); +} + +// This is a PSn00bSDK-only function that behaves like DecDCTout(), taking the +// data length as an argument rather than parsing it from the first 4 bytes of +// the stream. +void DecDCTinRaw(const uint32_t *data, size_t length) { + DMA_MADR(0) = (uint32_t) data; + if (length < 32) + DMA_BCR(0) = 0x00010000 | length; + else + DMA_BCR(0) = 0x00000020 | ((length / 32) << 16); + + DMA_CHCR(0) = 0x01000201; +} + +int32_t DecDCTinSync(int32_t mode) { + if (mode) + return (MDEC1 >> 29) & 1; + + for (uint32_t i = MDEC_SYNC_TIMEOUT; i; i--) { + if (!(MDEC1 & (1 << 29))) + return 0; + } + + printf("psxpress: DecDCTinSync() timeout\n"); + return -1; +} + +void DecDCTout(uint32_t *data, size_t length) { + DecDCToutSync(0); + + DMA_MADR(1) = (uint32_t) data; + if (length < 32) + DMA_BCR(1) = 0x00010000 | length; + else + DMA_BCR(1) = 0x00000020 | ((length / 32) << 16); + + DMA_CHCR(1) = 0x01000200; +} + +int32_t DecDCToutSync(int32_t mode) { + if (mode) + return (DMA_CHCR(1) >> 24) & 1; + + for (uint32_t i = MDEC_SYNC_TIMEOUT; i; i--) { + if (!(DMA_CHCR(1) & (1 << 24))) + return 0; + } + + printf("psxpress: DecDCToutSync() timeout\n"); + return -1; +} |
