aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2022-02-27 19:47:47 +0100
committerspicyjpeg <88942473+spicyjpeg@users.noreply.github.com>2022-02-27 19:47:47 +0100
commitc297c02652575e2affccbba16be0176d30d5ff06 (patch)
tree6f9ae00bff86811ffbffadd7c64248cde088c0f9
parent69a364049a3958396d2d083c660591dad9ec257d (diff)
downloadpsn00bsdk-c297c02652575e2affccbba16be0176d30d5ff06.tar.gz
Add psxpress MDEC API and psn00bsdk_target_incbin_a
-rw-r--r--doc/cmake_reference.md36
-rw-r--r--libpsn00b/cmake/internal_setup.cmake48
-rw-r--r--libpsn00b/include/psxpress.h152
-rw-r--r--libpsn00b/psxpress/mdec.c172
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;
+}