/**
 * @file rdpq.h
 * @author Dennis Heinze <dennisjp.heinze@gmail.com>
 * @author Giovanni Bajo <giovannibajo@gmail.com>
 * @brief RDP Command queue
 * @ingroup rdpq
 */

/**
 * @defgroup rdpq RDPQ: Hardware-accelerated drawing API
 * @brief Interface to the RDP (graphics hardware) for 2D/3D rasterization
 * @ingroup display
 * 
 * The RDPQ ("RDP command queue") is a library that allows to interface with
 * the RDP ("Reality Display Processor"), the GPU on the N64, through the RSP.
 * 
 * This library is quite vast because RDP is a complex chip to program and full
 * of quirks. Moreover, the needs for 2D vs 3D are quite different, and the library
 * copes with both. An important effort has been made to make this library
 * "just work".
 * 
 * Since the API is wide, the library is split in several header files. Make
 * sure to read them all to have a general overview:
 * 
 *    * rdpq.h: General low-level RDP command generation.
 *    * rdpq_tri.h: Low-level screen-space triangle drawing API.
 *    * rdpq_rect.h: Low-level screen-space rectangle drawing API.
 *    * rdpq_attach.h: Attachment API (optional), to simplify configuring the render target
 *    * rdpq_mode.h: Mode API (optional), to simplify configuring the render modes
 *    * rdpq_tex.h: Texture API (optional), to simplify uploading to TMEM and blitting 2D surfaces 
 *    * rdpq_sprite.h: Sprite API (optional), to simplify uploading to TMEM and blitting sprites
 *    * rdpq_debug.h: Debugging API (optional), to help catching bugs.
 * 
 * ## Goals of this library
 * 
 * This library is meant to be used directly for two tasks:
 * 
 *  * 2D hardware-assisted rasterization: drawing tiles, sprites, text.
 *  * 3D rasterization of triangles computed on the CPU. This is mostly the case
 *    if you are porting a 3D engine that runs T&L on the CPU but you want
 *    to draw triangles using RDP.
 * 
 * For a full 3D project, libdragon offers a full 3D API via the OpenGL API
 * (see gl.h); OpenGL internally uses rdpq, but it is unlikely that you will
 * need to call rdpq directly when you are using OpenGL.
 * 
 * ## Architecture and rationale
 * 
 * Normally, RDP commands are generated by both the CPU and the RSP. The normal
 * split is that CPU is in charge of render mode changes (eg: loading textures,
 * defining the alpha blending behavior, etc.), while the RSP executes a full
 * T&L pipeline which terminates with the generation of RDP triangle primitives.
 * 
 * This library allows the CPU to enqueue RDP commands. It covers the full
 * RDP command set, including triangles. Even for RDP commands generated by CPU,
 * the RSP is involved: in fact, this library is a rspq overlay (see rspq.h).
 * All RDP commands are enqueued in the main RSP command queue, and they are sent
 * to the RDP by the RSP.
 * 
 * There are two main reasons for this design (rather than letting the CPU directly
 * send the commands to the RDP):
 * 
 *  * Given that CPU and RSP usually work in parallel (with as few as possible
 *    syncing points), it is necessary to make sure that the CPU is able to
 *    schedule RDP commands that will be executed in the right order with
 *    respect to commands generated by RSP. This is easy to do if CPU-generated
 *    RDP commands always go through RSP in main command queue.
 *    
 *  * Most of the commands are sent unchanged to the RDP (we call them "passthrough").
 *    Some commands, instead, are manipulated by the RSP and changed before
 *    they hit the RDP (we call these "fixups"). This is done to achieve a saner
 *    semantic for the programmer, hiding a few dark corners of the RDP hardware.
 * 
 * The documentation of the public API of this library describes the final
 * behavior of each rdpq command, without explicitly mentioning whether it is
 * obtained via fixups or not. For more information on these, see the
 * documentation of rdpq.c, which gives an overview of many implementation details.
 * 
 * ## Render modes
 * 
 * The most complicated part of programming RDP is getting the correct render mode
 * configuration. At the lowest level (hardware commands), this can be done via
 * two functions: #rdpq_set_other_modes_raw (that maps to the RDP command `SET_OTHER_MODES`,
 * usually shortened as "SOM") and #rdpq_set_combiner_raw (that maps to the RDP
 * command `SET_COMBINE`). These functions are meant for programmers already
 * familiar with the RDP hardware, and allow you to manipulate configurations
 * freely.
 * 
 * To help with partial SOM changes, rdpq also offers #rdpq_change_other_modes_raw that
 * allows to change only some bits of the SOM state. This is done by tracking the
 * current SOM state (within the RSP) so that a partial update can be sent. It is
 * useful to make programming more modular, so that for instance a portion of code
 * can temporarily enable (eg.) fogging, without having to restate the full render
 * mode.
 * 
 * Alternatively, rdpq offers a higher level render mode API, which is hopefully
 * clearer to understand and more accessible, that tries to hide some of the most
 * common pitfalls. This API can be found in the rdpq_mode.h file. It is possible
 * to switch from this the higher level API to the lower level one at any time
 * in the code with no overhead, so that it can be adopted wherever it is a good
 * fit, falling back to lower level programming if/when necessary.
 * 
 * Beginners of RDP programming are strongly encouraged to use rdpq_mode.h, and
 * only later dive into lower-level RDP programming, if necessary.
 * 
 * ## Blocks and address lookups
 * 
 * Being a RSPQ overlay, it is possible to record rdpq commands in blocks (via
 * #rspq_block_begin / #rspq_block_end, like for any other overlay), to quickly
 * replay them with zero CPU time.
 * 
 * rdpq has also some special memory-bandwidth optimizations that are used
 * when commands are compiled into blocks (for more details, see documentation
 * of rdpq.c). In general, it is advised to use blocks whenever possible,
 * especially in case of a sequence of 3 or more rdpq function calls.
 * 
 *    TO BE COMPLETED....
 * 
 * 
 * ## Debugging: tracer and validator
 * 
 * To help writing correct code, rdpq comes with two very important features:
 * 
 *   * A command tracer with disassembler. rdpq is able to intercept all commands
 *     sent to RDP (including commands assembled directly by third-party rspq
 *     overlays), and log them via #debugf. The log includes a full disassembly
 *     of the commands, to help readability.
 *   * A validator. rdpq can re-interpret all commands sent to RDP and validate
 *     that they are correct, not only syntactically but also semantically. It is
 *     extremely easy to make mistakes in programming RDP by setting wrong mode
 *     flags or forgetting to configure a register, so the validator tries to help by
 *     flagging potential problems. All validation errors and warnings are sent
 *     via #debugf.
 * 
 * To initialize the debugging engine, call #rdpq_debug_start just after #rdpq_init
 * (or as early as possible). This will start intercepting and validating all
 * commands sent to RDP, showing validation errors on the debug spew.
 * 
 * To see a log of RDP commands, call #rdpq_debug_log passing true or false. You
 * can activate/deactivate logging around portions of code that you want to analyze,
 * as keeping the log active for a whole frame can produce too much information.
 * 
 */

#ifndef __LIBDRAGON_RDPQ_H
#define __LIBDRAGON_RDPQ_H

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "graphics.h"
#include "n64sys.h"
#include "rdpq_macros.h"
#include "surface.h"
#include "debug.h"

///@cond

// Static overlay ID for RDP/RDPQ commands
#define RDPQ_OVL_ID (0xC << 28)

// List of RDPQ internal RSP commands.
// NOTE: this is *NOT* part of the public API of rdpq.
enum {
    RDPQ_CMD_NOOP                       = 0x00,
    RDPQ_CMD_SET_LOOKUP_ADDRESS         = 0x01,
    RDPQ_CMD_FILL_RECTANGLE_EX          = 0x02,
    RDPQ_CMD_CLEAR_ZBUFFER              = 0x03,
    RDPQ_CMD_RESET_RENDER_MODE          = 0x04,
    RDPQ_CMD_SET_COMBINE_MODE_2PASS     = 0x05,
    RDPQ_CMD_PUSH_RENDER_MODE           = 0x06,
    RDPQ_CMD_POP_RENDER_MODE            = 0x07,
    RDPQ_CMD_TRI                        = 0x08,
    RDPQ_CMD_TRI_ZBUF                   = 0x09,
    RDPQ_CMD_TRI_TEX                    = 0x0A,
    RDPQ_CMD_TRI_TEX_ZBUF               = 0x0B,
    RDPQ_CMD_TRI_SHADE                  = 0x0C,
    RDPQ_CMD_TRI_SHADE_ZBUF             = 0x0D,
    RDPQ_CMD_TRI_SHADE_TEX              = 0x0E,
    RDPQ_CMD_TRI_SHADE_TEX_ZBUF         = 0x0F,

    RDPQ_CMD_TEXTURE_RECTANGLE_EX       = 0x10,
    RDPQ_CMD_SET_DEBUG_MODE             = 0x11,
    RDPQ_CMD_SET_SCISSOR_EX             = 0x12,
    RDPQ_CMD_SET_PRIM_COLOR_COMPONENT   = 0x13,
    RDPQ_CMD_MODIFY_OTHER_MODES         = 0x14,
    RDPQ_CMD_SET_FILL_COLOR_32          = 0x16,
    RDPQ_CMD_SET_BLENDING_MODE          = 0x18,
    RDPQ_CMD_SET_FOG_MODE               = 0x19,
    RDPQ_CMD_WAIT_IDLE                  = 0x1A,
    RDPQ_CMD_SET_COMBINE_MODE_1PASS     = 0x1B,
    RDPQ_CMD_AUTOTMEM_SET_ADDR          = 0x1C,
    RDPQ_CMD_AUTOTMEM_SET_TILE          = 0x1D,
    RDPQ_CMD_TRIANGLE                   = 0x1E,
    RDPQ_CMD_TRIANGLE_DATA              = 0x1F,

    RDPQ_CMD_TEXTURE_RECTANGLE          = 0x24,
    RDPQ_CMD_TEXTURE_RECTANGLE_FLIP     = 0x25,
    RDPQ_CMD_SYNC_LOAD                  = 0x26,
    RDPQ_CMD_SYNC_PIPE                  = 0x27,
    RDPQ_CMD_SYNC_TILE                  = 0x28,
    RDPQ_CMD_SYNC_FULL                  = 0x29,
    RDPQ_CMD_SET_KEY_GB                 = 0x2A,
    RDPQ_CMD_SET_KEY_R                  = 0x2B,
    RDPQ_CMD_SET_CONVERT                = 0x2C,
    RDPQ_CMD_SET_SCISSOR                = 0x2D,
    RDPQ_CMD_SET_PRIM_DEPTH             = 0x2E,
    RDPQ_CMD_SET_OTHER_MODES            = 0x2F,

    RDPQ_CMD_LOAD_TLUT                  = 0x30,
    RDPQ_CMD_DEBUG                      = 0x31,
    RDPQ_CMD_SET_TILE_SIZE              = 0x32,
    RDPQ_CMD_LOAD_BLOCK                 = 0x33,
    RDPQ_CMD_LOAD_TILE                  = 0x34,
    RDPQ_CMD_SET_TILE                   = 0x35,
    RDPQ_CMD_FILL_RECTANGLE             = 0x36,
    RDPQ_CMD_SET_FILL_COLOR             = 0x37,
    RDPQ_CMD_SET_FOG_COLOR              = 0x38,
    RDPQ_CMD_SET_BLEND_COLOR            = 0x39,
    RDPQ_CMD_SET_PRIM_COLOR             = 0x3A,
    RDPQ_CMD_SET_ENV_COLOR              = 0x3B,
    RDPQ_CMD_SET_COMBINE_MODE_RAW       = 0x3C,
    RDPQ_CMD_SET_TEXTURE_IMAGE          = 0x3D,
    RDPQ_CMD_SET_Z_IMAGE                = 0x3E,
    RDPQ_CMD_SET_COLOR_IMAGE            = 0x3F,
};
///@endcond

#define RDPQ_CFG_AUTOSYNCPIPE   (1 << 0)     ///< Configuration flag: enable automatic generation of SYNC_PIPE commands
#define RDPQ_CFG_AUTOSYNCLOAD   (1 << 1)     ///< Configuration flag: enable automatic generation of SYNC_LOAD commands
#define RDPQ_CFG_AUTOSYNCTILE   (1 << 2)     ///< Configuration flag: enable automatic generation of SYNC_TILE commands
#define RDPQ_CFG_AUTOSCISSOR    (1 << 3)     ///< Configuration flag: enable automatic generation of SET_SCISSOR commands on render target change
#define RDPQ_CFG_DEFAULT        (0xFFFF)     ///< Configuration flag: default configuration

///@cond
// Used in inline functions as part of the autosync engine. Not part of public API.
#define AUTOSYNC_TILE(n)  (1    << (0+(n)))     // Autosync state: Bit used for tile N
#define AUTOSYNC_TILES    (0xFF << 0)           // Autosync state: Mask for all bits regarding tile
#define AUTOSYNC_TMEM(n)  (1    << (8+(n)))     // Autosync state: Bit used for tmem portion N
#define AUTOSYNC_TMEMS    (0xFF << 8)           // Autosync state: Mask for all bits regarding TMEM
#define AUTOSYNC_PIPE     (1    << 16)          // Autosync state: Bit used for pipe
///@endcond

///@cond
/* Used internally for bit-packing RDP commands. Not part of public API. */
#define _carg(value, mask, shift) (((uint32_t)((value) & (mask))) << (shift))
///@endcond

/** @brief Tile descriptors. 
 * 
 * These are enums that map to integers 0-7, but they can be used in place of the
 * integers for code readability.
 */
typedef enum {
    TILE0 = 0,  ///< Tile #0 (for code readability)
    TILE1 = 1,  ///< Tile #1 (for code readability)
    TILE2 = 2,  ///< Tile #2 (for code readability)
    TILE3 = 3,  ///< Tile #3 (for code readability)
    TILE4 = 4,  ///< Tile #4 (for code readability)
    TILE5 = 5,  ///< Tile #5 (for code readability)
    TILE6 = 6,  ///< Tile #6 (for code readability)
    TILE7 = 7,  ///< Tile #7 (for code readability)
} rdpq_tile_t;


/**
 * @brief Tile parameters for #rdpq_set_tile.
 * 
 * This structure contains all possible parameters for #rdpq_set_tile.
 * All fields have been made so that the 0 value is always the most
 * reasonable default (clamped with default scale, no mirroring). 
 * This means that you can simply initialize the structure to 0 and then
 * change only the fields you need (for instance, through a compound literal).
 * 
 */
typedef struct {
	uint8_t  palette;	 ///< Optional palette associated to the texture. For textures in #FMT_CI4 format, specify the palette index (0-15), otherwise use 0.
	
	// Additional mapping parameters; Leave them as 0 if not required;
	
    struct{
        bool     clamp; 	///< True if texture needs to be clamped. Otherwise wrap the texture around;
        bool     mirror;	///< True if texture needs to be mirrored. Otherwise wrap the texture without mirroring;
        uint8_t  mask;	    	///< Power of 2 boundary of the texture in pixels to wrap. (Important note: Mask value of 0 will force clamping to be ON regardless of clamp value);
        int8_t   shift;	    	///< Power of 2 scale of the texture to wrap on. Range is [-5..10];
    } s,t; // S/T directions of the tile descriptor
	
} rdpq_tileparms_t;

/** @brief Tile descriptor internally used by some RDPQ functions. Avoid using if possible */
#define RDPQ_TILE_INTERNAL           TILE7

#ifdef __cplusplus
extern "C" {
#endif

/// @private
extern void __rdpq_set_scissor(uint32_t, uint32_t);

/**
 * @brief Initialize the RDPQ library.
 * 
 * This should be called by the initialization functions of the higher-level
 * libraries using RDPQ to emit RDP commands, and/or by the application main
 * if the application itself calls rdpq functions.
 * 
 * It is safe to call this function multiple times (it does nothing), so that
 * multiple independent libraries using rdpq can call #rdpq_init with no side
 * effects.
 */
void rdpq_init(void);

/**
 * @brief Shutdown the RDPQ library.
 *
 * This is mainly used for testing.
 */
void rdpq_close(void);


/**
 * @brief Set the configuration of the RDPQ module.
 * 
 * This function allows you to change the configuration of rdpq to enable/disable
 * features. This is useful mainly for advanced users that want to manually tune
 * RDP programming, disabling some automatisms performed by rdpq.
 * 
 * The configuration is a bitmask that can be composed using the `RDPQ_CFG_*` macros.
 * 
 * To enable or disable specific configuration options use #rdpq_config_enable or
 * #rdpq_config_disable.
 * 
 * @param cfg         The new configuration to set
 * @return            The previous configuration
 * 
 * @see #rdpq_config_enable
 * @see #rdpq_config_disable
 */
uint32_t rdpq_config_set(uint32_t cfg);

/**
 * @brief Enable a specific set of configuration flags
 * 
 * This function allows you to modify the configuration of rdpq activating a specific
 * set of features. It can be useful to temporarily modify the configuration and then
 * restore it.
 * 
 * @param cfg_enable_bits    Configuration flags to enable
 * @return                   The previous configuration
 * 
 * @see #rdpq_config_set
 * @see #rdpq_config_disable
 */
uint32_t rdpq_config_enable(uint32_t cfg_enable_bits);


/**
 * @brief Disable a specific set of configuration flags
 * 
 * This function allows you to modify the configuration of rdpq disabling a specific
 * set of features. It can be useful to temporarily modify the configuration and then
 * restore it.
 * 
 * @code{.c}
 *      // Disable automatic scissor generation
 *      uint32_t old_cfg = rdpq_config_disable(RDPQ_CFG_AUTOSCISSOR);
 *  
 *      // This will change the render target but will NOT issue a corresponding SET_SCISSOR.
 *      // This is dangerous as the currently-configured scissor might allow to draw outside of
 *      // the surface boundary, but an advanced user will know if this is correct.
 *      rdpq_set_color_image(surface);
 * 
 *      [...]
 * 
 *      // Restore the previous configuration
 *      rdpq_config_set(old_cfg);
 * @endcode
 * 
 * @param cfg_disable_bits   Configuration flags to disable
 * @return                   The previous configuration
 * 
 * @see #rdpq_config_set
 * @see #rdpq_config_enable
 */
uint32_t rdpq_config_disable(uint32_t cfg_disable_bits);

/**
 * @brief Low level function to set the green and blue components of the chroma key
 */
inline void rdpq_set_chromakey_parms(color_t color, 
    int edge_r, int edge_g, int edge_b,
    int width_r, int width_g, int width_b)
{
    float fsr = 1.0f / edge_r;
    float fsg = 1.0f / edge_g;
    float fsb = 1.0f / edge_b;
    uint8_t sr = fsr * 255.0f;
    uint8_t sg = fsg * 255.0f;
    uint8_t sb = fsb * 255.0f;
    float fwr = width_r * fsr;
    float fwg = width_g * fsg;
    float fwb = width_b * fsb;
    uint16_t wr = fwr * 255.0f;
    uint16_t wg = fwg * 255.0f;
    uint16_t wb = fwb * 255.0f;

    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_KEY_R,
        0, 
        _carg(wr, 0xFFF, 16) | _carg(color.r, 0xFF, 8) | _carg(sr, 0xFF, 0),
        AUTOSYNC_PIPE);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_KEY_GB,
        _carg(wg, 0xFFF, 12) | _carg(wb, 0xFFF, 0),
        _carg(color.g, 0xFF, 24) | _carg(sg, 0xFF, 16) | _carg(color.b, 0xFF, 8) | _carg(sb, 0xFF, 0),
        AUTOSYNC_PIPE);
}

/**
 * @brief Low level functions to set the matrix coefficients for texture format conversion
 */
inline void rdpq_set_yuv_parms(uint16_t k0, uint16_t k1, uint16_t k2, uint16_t k3, uint16_t k4, uint16_t k5)
{
    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_CONVERT,
        _carg(k0, 0x1FF, 13) | _carg(k1, 0x1FF, 4) | (((uint32_t)(k2 & 0x1FF)) >> 5),
        _carg(k2, 0x1F, 27) | _carg(k3, 0x1FF, 18) | _carg(k4, 0x1FF, 9) | _carg(k5, 0x1FF, 0),
        AUTOSYNC_PIPE);
}

/**
 * @brief Configure a scissoring rectangle in screen coordinates (RDP command: SET_SCISSOR)
 * 
 * This function is used to configure a scissor region that the RDP with adhere to
 * while drawing primitives (triangles or rectangles). Any points that fall outside
 * of the specified scissoring rectangle will be ignored.
 * 
 * The scissoring capability is also the only one that prevents the RDP from drawing
 * outside of the current framebuffer (color surface) extents. As such, rdpq actually
 * calls #rdpq_set_scissor automatically any time a new render target is configured
 * (eg: via #rdpq_attach or #rdpq_set_color_image), because forgetting to do so might
 * easily cause crashes.
 * 
 * Because #rdpq_set_color_image will configure a scissoring region automatically,
 * it is normally not required to call this function. Use this function if you want
 * to restrict drawing to a smaller area of the framebuffer.
 * 
 * The scissoring rectangle is defined using unsigned coordinates, and thus negative
 * coordinates will always be clipped. Rectangle-drawing primitives do not allow to
 * specify them at all, but triangle-drawing primitives do.
 * 
 * @param[in]   x0      Top-left X coordinate of the rectangle
 * @param[in]   y0      Top-left Y coordinate of the rectangle
 * @param[in]   x1      Bottom-right *exclusive* X coordinate of the rectangle
 * @param[in]   y1      Bottom-right *exclusive* Y coordinate of the rectangle
 * 
 * @see #rdpq_attach
 * @see #rdpq_set_color_image
 */
#define rdpq_set_scissor(x0, y0, x1, y1) ({ \
    int32_t x0fx = (x0)*4; \
    int32_t y0fx = (y0)*4; \
    int32_t x1fx = (x1)*4; \
    int32_t y1fx = (y1)*4; \
    assertf(x0fx <= x1fx, "x1 must be greater or equal to x0"); \
    assertf(y0fx <= y1fx, "y1 must be greater or equal to y0"); \
    assertf(x0fx >= 0, "x0 must be positive"); \
    assertf(y0fx >= 0, "y0 must be positive"); \
    assertf(x1fx <= 0xFFF, "x1 must be less than 1024"); \
    assertf(y1fx <= 0xFFF, "y1 must be less than 1024"); \
    __rdpq_set_scissor( \
        _carg(x0fx, 0xFFF, 12) | _carg(y0fx, 0xFFF, 0), \
        _carg(x1fx, 0xFFF, 12) | _carg(y1fx, 0xFFF, 0)); \
})

/**
 * @brief Set a fixed Z value to be used instead of a per-pixel value (RDP command; SET_PRIM_DEPTH)
 * 
 * When using z-buffering, normally the Z value used for z-buffering is
 * calculated by interpolating the Z of each vertex onto each pixel.
 * The RDP allows for usage of a fixed Z value instead, for special
 * effects like particles or decals.
 * 
 * This function allows to configure the RDP register that
 * holds the fixed Z value. It is then necessary to activate this
 * special RDP mode: either manually turning on SOM_ZSOURCE_PRIM via
 * #rdpq_change_other_modes_raw.
 * 
 * For beginners, it is suggested to use the mode API instead, via
 * #rdpq_mode_zoverride.
 * 
 * @param[in] prim_z     Fixed Z value (in range 0..0x7FFF)
 * @param[in] prim_dz    Delta Z value (must be a signed power of two).
 *                       Pass 0 initially, and increment to next power of two
 *                       in case of problems with objects with the same Z.
 * 
 * @note Pending further investigation of the exact usage of this function,
 *       and specifically the prim_dz parameter, rdpq does not currently
 *       offer a higher-level function (`rdpq_set_prim_depth`).
 */
 inline void rdpq_set_prim_depth_raw(uint16_t prim_z, int16_t prim_dz)
{
    // NOTE: this does not require a pipe sync
    extern void __rdpq_write8(uint32_t, uint32_t, uint32_t);
    assertf(prim_z <= 0x7FFF, "prim_z must be in [0..0x7FFF]");
    assertf((prim_dz & -prim_dz) == (prim_dz >= 0 ? prim_dz : -prim_dz),
        "prim_dz must be a power of 2");
    __rdpq_write8(RDPQ_CMD_SET_PRIM_DEPTH, 0, _carg(prim_z, 0xFFFF, 16) | _carg(prim_dz, 0xFFFF, 0));
}

/**
 * @brief Load a portion of a texture into TMEM (RDP command: LOAD_TILE)
 * 
 * This is the main command to load data from RDRAM into TMEM. It is
 * normally used to load a texture (or a portion of it), before using
 * it for drawing.
 * 
 * @note Beginners are advised to use the rdpq texture API (rdpq_tex.h), 
 * for instance #rdpq_tex_upload that takes care of everything required.
 * 
 * Before calling #rdpq_load_tile, the tile must have been configured
 * using #rdpq_set_tile to specify the TMEM address and pitch, and the 
 * texture in RDRAM must have been set via #rdpq_set_texture_image.
 * 
 * In addition to loading TMEM, this command also records into the
 * tile descriptor the extents of the loaded texture (that is, the
 * texture coordinates), so that subsequence draw commands can still
 * refer to original texture's coordinates to draw. For instance,
 * if you have a large 512x128 texture and you load only a small
 * portion into TMEM, for instance the rectangle at coordinates
 * (16,16) - (48,48), the RDP will remember (through the tile descriptor)
 * that the TMEM contains that specific rectangle, and subsequent
 * triangles or rectangles commands can specify S,T texture
 * coordinates within the range (16,16)-(48,48).
 * 
 * If the portion being loaded is consecutive in RDRAM (rather
 * than being a rectangle within a wider image), prefer using
 * #rdpq_load_block for increased performance.
 * 
 * @param[in]   tile        Tile descriptor to use (TILE0-TILE7).
 * @param[in]   s0          Upper-left X coordinate of the portion of the texture to load (integer or float).
 *                          Range: 0-1024
 * @param[in]   t0          Upper-left Y coordinate of the portion of the texture to load (integer or float),
 *                          Range: 0-1024
 * @param[in]   s1          Bottom-right X coordinate of the portion of the texture to load (integer or float),
 *                          Range: 0-1024
 * @param[in]   t1          Bottom-right Y coordinate of the portion of the texture to load (integer or float),
 *                          Range: 0-1024
 * 
 * @see #rdpq_tex_upload
 * @see #rdpq_set_texture_image
 * @see #rdpq_load_block
 * @see #rdpq_set_tile
 * @see #rdpq_load_tile_fx
 */
#define rdpq_load_tile(tile, s0, t0, s1, t1) ({ \
    assertf((s0) >= 0 && (t0) >= 0 && (s1) >= 0 && (t1) >= 0, "texture coordinates must be positive"); \
    assertf((s0) < 1024 && (t0) < 1024 && (s1) < 1024 && (t1) < 1024, "texture coordinates must be smaller than 1024"); \
    rdpq_load_tile_fx((tile), (s0)*4, (t0)*4, (s1)*4, (t1)*4); \
})

/**
 * @brief Load a portion of a texture into TMEM -- fixed point version (RDP command: LOAD_TILE)
 * 
 * This function is similar to #rdpq_load_tile, but coordinates can be specified
 * in fixed point format (0.10.2). Refer to #rdpq_load_tile for increased performance
 * 
 * @note Beginners are advised to use the rdpq texture API (rdpq_tex.h), 
 * for instance #rdpq_tex_upload that takes care of everything required.
 * 
 * 
 * @param[in]   tile        Tile descriptor to use (TILE0-TILE7).
 * @param[in]   s0          Upper-left X coordinate of the portion of the texture to load (fx 0.10.2).
 *                          Range: 0-4096
 * @param[in]   t0          Upper-left Y coordinate of the portion of the texture to load (fx 0.10.2),
 *                          Range: 0-4096
 * @param[in]   s1          Bottom-right X coordinate of the portion of the texture to load (fx 0.10.2),
 *                          Range: 0-4096
 * @param[in]   t1          Bottom-right Y coordinate of the portion of the texture to load (fx 0.10.2),
 *                          Range: 0-4096
 * 
 * @see #rdpq_load_tile
 * @see #rdpq_tex_upload
 */
inline void rdpq_load_tile_fx(rdpq_tile_t tile, uint16_t s0, uint16_t t0, uint16_t s1, uint16_t t1)
{
    extern void __rdpq_write8_syncchangeuse(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_write8_syncchangeuse(RDPQ_CMD_LOAD_TILE,
        _carg(s0, 0xFFF, 12) | _carg(t0, 0xFFF, 0),
        _carg(tile, 0x7, 24) | _carg(s1-4, 0xFFF, 12) | _carg(t1-4, 0xFFF, 0),
        AUTOSYNC_TMEM(0) | AUTOSYNC_TILE(tile),
        AUTOSYNC_TILE(tile));
}


/**
 * @brief Load a palette of colors into TMEM (RDP command: LOAD_TLUT)
 * 
 * This command is used to load a palette into TMEM. TMEM can hold up
 * to 256 16-bit colors in total to be used as palette, and they must be
 * stored in the upper half of TMEM. These colors are arranged as a single
 * 256-color palette when drawing #FMT_CI8 images, or 16 16-colors palettes
 * when drawing #FMT_CI4 images.
 * 
 * Storage of colors in TMEM is a bit wasteful, as each color is replicated
 * four times (in fact, 256 colors * 16-bit + 4 = 2048 bytes, which is
 * in fact half of TMEM). This command should be preferred for palette
 * loading as it automatically handles this replication.
 * 
 * Loading a palette manually is a bit involved. It requires configuring
 * the palette in RDRAM via #rdpq_set_texture_image, and also configure a 
 * tile descriptor with the TMEM destination address (via #rdpq_set_tile).
 * Instead, prefer using the simpler rdpq texture API (rdpq_tex.h), via
 * #rdpq_tex_upload_tlut.
 * 
 * @param[in] tile         Tile descriptor to use (TILE0-TILE7). This is used
 *                         to extract the destination TMEM address (all other fields
 *                         of the descriptor are ignored).
 * @param[in] color_idx    Index of the first color to load into TMEM (0-255).
 *                         This is a 16-bit offset into the RDRAM buffer
 *                         set via #rdpq_set_texture_image.
 * @param[in] num_colors   Number of colors to load (1-256).
 * 
 * @see #rdpq_tex_upload_tlut
 */
inline void rdpq_load_tlut_raw(rdpq_tile_t tile, int color_idx, int num_colors)
{
    extern void __rdpq_write8_syncchangeuse(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
    assertf(num_colors <= 256, "invalid palette length %d: must be smaller than 256", num_colors);
    __rdpq_write8_syncchangeuse(RDPQ_CMD_LOAD_TLUT, 
        _carg(color_idx, 0xFF, 14), 
        _carg(tile, 0x7, 24) | _carg(color_idx+num_colors-1, 0x1FF, 14),
        AUTOSYNC_TMEM(0),
        AUTOSYNC_TILE(tile));
}

/**
 * @brief Configure the extents of a tile descriptor (RDP command: SET_TILE_SIZE)
 * 
 * This function allows to set the extents (s0,s1 - t0,t1) of a tile descriptor.
 * Normally, it is not required to call this function because extents are
 * automatically configured when #rdpq_load_tile is called to load contents
 * in TMEM. This function is mostly useful when loading contents using
 * #rdpq_load_block, or when reinterpreting existing contents of TMEM.
 * 
 * For beginners, it is suggest to use the rdpq texture API (rdpq_tex.h)
 * which automatically configures tile descriptors correctly: for instance,
 * #rdpq_tex_upload.
 * 
 * @param[in] tile          Tile descriptor (TILE0-TILE7)
 * @param[in] s0            Top-left X texture coordinate to store in the descriptor (integer or float).
 *                          Range: 0-1024 (inclusive)
 * @param[in] t0            Top-left Y texture coordinate to store in the descriptor (integer or float).
 *                          Range: 0-1024 (inclusive)
 * @param[in] s1            Bottom-right *exclusive* X texture coordinate to store in the descriptor (integer or float).
 *                          Range: 0-1024 (inclusive)
 * @param[in] t1            Bottom-right *exclusive* Y texture coordinate to store in the descriptor (integer or float).
 *                          Range: 0-1024 (inclusive)
 * 
 * @see #rdpq_tex_upload
 * @see #rdpq_set_tile_size_fx
 */
#define rdpq_set_tile_size(tile, s0, t0, s1, t1) ({ \
    rdpq_set_tile_size_fx((tile), (s0)*4, (t0)*4, (s1)*4, (t1)*4); \
})

/**
 * @brief Configure the extents of a tile descriptor -- fixed point version (RDP command: SET_TILE_SIZE)
 * 
 * This function is similar to #rdpq_set_tile_size, but coordinates must be
 * specified using fixed point numbers (10.2).
 * 
 * @param tile              Tile descriptor (TILE0-TILE7)
 * @param[in] s0            Top-left X texture coordinate to store in the descriptor (fx 10.2)
 * @param[in] t0            Top-left Y texture coordinate to store in the descriptor (fx 10.2)
 * @param[in] s1            Bottom-right *exclusive* X texture coordinate to store in the descriptor (fx 10.2)
 * @param[in] t1            Bottom-right *exclusive* Y texture coordinate to store in the descriptor (fx 10.2)
 *
 * @see #rdpq_tex_upload
 * @see #rdpq_set_tile_size
 */
inline void rdpq_set_tile_size_fx(rdpq_tile_t tile, uint16_t s0, uint16_t t0, uint16_t s1, uint16_t t1)
{
    assertf((s0) >= 0 && (t0) >= 0 && (s1) >= 0 && (t1) >= 0, "texture coordinates must be positive");
    assertf((s0) <= 1024*4 && (t0) <= 1024*4 && (s1) <= 1024*4 && (t1) <= 1024*4, "texture coordinates must be smaller than 1024");

    extern void __rdpq_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_TILE_SIZE,
        _carg(s0, 0xFFF, 12) | _carg(t0, 0xFFF, 0),
        _carg(tile, 0x7, 24) | _carg(s1-4, 0xFFF, 12) | _carg(t1-4, 0xFFF, 0),
        AUTOSYNC_TILE(tile));
}


/**
 * @brief Low level function to load a texture image into TMEM in a single memory transfer
 */
inline void rdpq_load_block_fx(rdpq_tile_t tile, uint16_t s0, uint16_t t0, uint16_t num_texels, uint16_t dxt)
{
    extern void __rdpq_write8_syncchangeuse(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_write8_syncchangeuse(RDPQ_CMD_LOAD_BLOCK,
        _carg(s0, 0xFFF, 12) | _carg(t0, 0xFFF, 0),
        _carg(tile, 0x7, 24) | _carg(num_texels-1, 0xFFF, 12) | _carg(dxt, 0xFFF, 0),
        AUTOSYNC_TMEM(0),
        AUTOSYNC_TILE(tile));
}

/**
 * @brief Load a texture image into TMEM with a single contiguous memory transfer (RDP command: LOAD_BLOCK)
 * 
 * This is a command alternative to #rdpq_load_tile to load data from
 * RDRAM into TMEM. It is faster than #rdpq_load_tile but only allows
 * to transfer a consecutive block of data; the block can cover multiple
 * lines, but not a sub-rectangle of the texture image.
 * 
 * @note Beginners are advised to use the rdpq texture API (rdpq_tex.h), 
 * for instance #rdpq_tex_upload that takes care of everything required,
 * including using #rdpq_load_block for performance whenever possible.
 * 
 * Before calling #rdpq_load_block, the tile must have been configured
 * using #rdpq_set_tile to specify the TMEM address, and the texture 
 * in RDRAM must have been set via #rdpq_set_texture_image. 
 * 
 * @note It is important to notice that the RDP will interpret the tile pitch
 *       configured in the tile descriptor with a different semantic: it is
 *       used as a number of texels that must be skipped between lines
 *       in RDRAM. Normally, for a compact texture, it should then be set to zero
 *       in the call to #rdpq_set_tile. Instead, The *real* pitch of the texture
 *       in TMEM must be provided to #rdpq_load_block itself.
 * 
 * After the call to #rdpq_load_block, it is not possible to reuse the tile
 * descriptor for performing a draw. So a new tile descriptor should be configured
 * from scratch using #rdpq_set_tile.
 * 
 * The maximum number of texels that can be transferred by a single call is
 * 2048. This allows to fill the TMEM only if a 16-bit or 32-bit texture is used.
 * If you need to load a 4-bit or 8-bit texture, consider configuring the tile
 * descriptor as 16-bit and adjusting the number of texels accordingly. For instance,
 * to transfer a 80x64 4-bit texture (5120 texels), do the transfer as if it was a
 * 20x64 16-bit texture (1280 texels). It doesn't matter if you lie to the RDP
 * during the texture load: what it matters is that the tile descriptor that you will
 * later use for drawing is configured with the correct pixel format.
 * 
 * @param[in] tile          Tile descriptor (TILE0-TILE7)
 * @param[in] s0            Top-left X texture coordinate to load
 * @param[in] t0            Top-left Y texture coordinate to load
 * @param[in] num_texels    Number of texels to load (max: 2048)
 * @param[in] tmem_pitch    Pitch of the texture in TMEM (in bytes)
 * 
 * @see #rdpq_load_tile
 * @see #rdpq_load_block_fx
 * @see #rdpq_set_tile
 * @see #rdpq_tex_upload
 */
inline void rdpq_load_block(rdpq_tile_t tile, uint16_t s0, uint16_t t0, uint16_t num_texels, uint16_t tmem_pitch)
{
    assertf(num_texels <= 2048, "invalid num_texels %d: must be smaller than 2048", num_texels);
    assertf((tmem_pitch % 8) == 0, "invalid tmem_pitch %d: must be multiple of 8", tmem_pitch);
    // Dxt is the reciprocal of the number of 64 bit words in a line in 1.11 format, rounded up
    uint32_t words = tmem_pitch / 8;
    rdpq_load_block_fx(tile, s0, t0, num_texels, (2048 + words - 1) / words);
}


/** @brief Special TMEM address to pass to #rdpq_set_tile to use automatic TMEM allocation */
#define RDPQ_AUTOTMEM                 (0x8000)
/** @brief Special TMEM address to pass to #rdpq_set_tile to configure a tile with the same address of previous tile */
#define RDPQ_AUTOTMEM_REUSE(offset)   (0x4000 | ((offset)/8))


/// @brief Enqueue a RDP SET_TILE command (full version)
/// @param[in] tile Tile descriptor index (0-7)
/// @param[in] format Texture format for the tile. Cannot be 0. Should correspond to X_get_format in #surface_t or #sprite_t;
/// @param[in] tmem_addr Address in tmem where the texture is (or will be loaded). Must be multiple of 8;
/// @param[in] tmem_pitch Pitch of the texture in tmem in bytes. Must be multiple of 8. Should correspond to srtide in #surface_t;	
/// @param[in] parms Additional optional parameters for the tile. Can be left NULL or all 0. More information about the struct is in #rdpq_tileparms_t
inline void rdpq_set_tile(rdpq_tile_t tile, 
	tex_format_t format,
    int32_t tmem_addr,
	uint16_t tmem_pitch,
    const rdpq_tileparms_t *parms)
{
    static const rdpq_tileparms_t default_parms = {0};
    if (!parms) parms = &default_parms;
    else {
        assertf(parms->s.shift >= -5 && parms->s.shift <= 10, "invalid s shift %d: must be in [-5..10]", parms->s.shift);
        assertf(parms->t.shift >= -5 && parms->t.shift <= 10, "invalid t shift %d: must be in [-5..10]", parms->t.shift);
        assertf(parms->palette < 16, "invalid palette %d: must be in [0..15]", parms->palette);
    }
    bool fixup = false;
    bool reuse = false;
    uint32_t cmd_id = RDPQ_CMD_SET_TILE;
    if (tmem_addr & (RDPQ_AUTOTMEM | RDPQ_AUTOTMEM_REUSE(0))) {
        cmd_id = RDPQ_CMD_AUTOTMEM_SET_TILE;
        reuse = (tmem_addr & RDPQ_AUTOTMEM_REUSE(0)) != 0;
        fixup = true;
        tmem_addr &= ~(RDPQ_AUTOTMEM | RDPQ_AUTOTMEM_REUSE(0));
    } else {
        assertf((tmem_addr % 8) == 0, "invalid tmem_addr %ld: must be multiple of 8", tmem_addr);
        tmem_addr /= 8;
    }
    assertf((tmem_pitch % 8) == 0, "invalid tmem_pitch %d: must be multiple of 8", tmem_pitch);
    extern void __rdpq_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    extern void __rdpq_fixup_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    (fixup ? __rdpq_fixup_write8_syncchange : __rdpq_write8_syncchange)(cmd_id,
        _carg(format, 0x1F, 19) | _carg(reuse, 0x1, 18) | _carg(tmem_pitch/8, 0x1FF, 9) | _carg(tmem_addr, 0x1FF, 0),
        _carg(tile, 0x7, 24) | _carg(parms->palette, 0xF, 20) | 
        _carg(parms->t.clamp | (parms->t.mask == 0), 0x1, 19) | _carg(parms->t.mirror, 0x1, 18) | _carg(parms->t.mask, 0xF, 14) | _carg(parms->t.shift, 0xF, 10) | 
        _carg(parms->s.clamp | (parms->s.mask == 0), 0x1, 9) | _carg(parms->s.mirror, 0x1, 8) | _carg(parms->s.mask, 0xF, 4) | _carg(parms->s.shift, 0xF, 0),
        AUTOSYNC_TILE(tile));
}

/**
 * @brief Configure the auto-TMEM feature of #rdpq_set_tile
 * 
 * This function is used to manage the auto-TMEM allocation feature for
 * #rdpq_set_tile. It allows to keep track of the allocated space in TMEM,
 * which can be a simplification. It is used by the rdpq_tex module
 * (eg: #rdpq_tex_upload).
 * 
 * The feature works like this:
 *   - First, start auto-TMEM via rdpq_set_tile_autotmem(0)
 *   - Load a texture and configure a tile for it. When configuring the tile,
 *     pass #RDPQ_AUTOTMEM as tmem_addr. This will allocate the texture in the
 *     first available space.
 *   - Call #rdpq_set_tile_autotmem again passing the number of used bytes in
 *     TMEM. Notice that rdpq can't know this by itself.
 *   - Continue loading the other textures/mipmaps just like before, with
 *     #RDPQ_AUTOTMEM.
 *   - If the TMEM is full, a RSP assertion will be triggered.
 *   - When you are done, call #rdpq_set_tile_autotmem passing -1 to finish.
 *     This allows reentrant calls to work, and also helps generating errors
 *     in case of misuses.
 * 
 * While this API might seem as a small simplification over manually tracking
 * TMEM allocation, it might help modularizing the code, and also allows to
 * record rspq blocks that handle texture loading without hardcoding the
 * TMEM position.
 * 
 * @note This function is part of the raw API. For a higher-level API on texture
 * loading, see #rdpq_tex_upload.
 * 
 * @param tmem_bytes     0: begin, -1: end, >0: number of additional bytes
 *                       that were used in TMEM.
 * 
 * @see #rdpq_set_tile
 * @see #rdpq_tex_upload
 */
void rdpq_set_tile_autotmem(int16_t tmem_bytes);

/**
 * @brief Enqueue a SET_FILL_COLOR RDP command.
 * 
 * This command is used to configure the color used by RDP when running in FILL mode
 * (#rdpq_set_mode_fill) and normally used by #rdpq_fill_rectangle.
 * 
 * Notice that #rdpq_set_mode_fill automatically calls this function, because in general
 * it makes no sense to configure the FILL mode without also setting a FILL color.
 * 
 * @code{.c}
 *      // Fill top half of the screen in red
 *      rdpq_set_mode_fill(RGBA32(255, 0, 0, 0));
 *      rdpq_fill_rectangle(0, 0, 320, 120);
 * 
 *      // Fill bottom half of the screen in blue.
 *      // No need to change mode again (it's already in fill mode),
 *      // so just change the fill color.
 *      rdpq_set_fill_color(RGBA32(0, 0, 255, 0));
 *      rdpq_fill_rectangle(0, 120, 320, 240);
 * @endcode
 * 
 * @param[in]    color   The color to use to fill
 * 
 * @see #rdpq_set_mode_fill
 */
inline void rdpq_set_fill_color(color_t color) {
    extern void __rdpq_set_fill_color(uint32_t);
    __rdpq_set_fill_color((color.r << 24) | (color.g << 16) | (color.b << 8) | (color.a << 0));
}

/**
 * @brief Enqueue a SET_FILL_COLOR RDP command to draw a striped pattern.
 * 
 * This command is similar to #rdpq_set_fill_color, but allows to configure
 * two colors, and creates a fill pattern that alternates horizontally between
 * them every 2 pixels (creating vertical stripes).
 * 
 * This command relies on a low-level hack of how RDP works in filling primitives,
 * so there is no configuration knob: it only works with RGBA 16-bit target
 * buffers, it only allows two colors, and the vertical stripes are exactly
 * 2 pixel width.
 * 
 * @param[in]   color1      Color of the first vertical stripe
 * @param[in]   color2      Color of the second vertical stripe
 * 
 * @see #rdpq_set_fill_color
 *
 */
inline void rdpq_set_fill_color_stripes(color_t color1, color_t color2) {
    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    uint32_t c1 = (((int)color1.r >> 3) << 11) | (((int)color1.g >> 3) << 6) | (((int)color1.b >> 3) << 1) | (color1.a >> 7);
    uint32_t c2 = (((int)color2.r >> 3) << 11) | (((int)color2.g >> 3) << 6) | (((int)color2.b >> 3) << 1) | (color2.a >> 7);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_FILL_COLOR, 0, (c1 << 16) | c2,
        AUTOSYNC_PIPE);
}

/**
 * @brief Set the RDP FOG blender register
 * 
 * This function sets the internal RDP FOG register, part of the blender unit.
 * As the name implies, this register is normally used as part of fog calculation,
 * but it is actually a generic color register that can be used in custom
 * blender formulas. 
 * 
 * Another similar blender register is the BLEND register, configured via
 * #rdpq_set_blend_color.
 * 
 * See #RDPQ_BLENDER and #RDPQ_BLENDER2 on how to configure
 * the blender (typically, via #rdpq_mode_blender).
 * 
 * @param[in] color             Color to set the FOG register to
 * 
 * @see #RDPQ_BLENDER
 * @see #RDPQ_BLENDER2
 * @see #rdpq_set_blend_color
 * @see #rdpq_mode_blender
 */
inline void rdpq_set_fog_color(color_t color)
{
    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_FOG_COLOR, 0, color_to_packed32(color),
        AUTOSYNC_PIPE);
}

/**
 * @brief Set the RDP BLEND blender register
 * 
 * This function sets the internal RDP BLEND register, part of the blender unit.
 * As the name implies, this register is normally used as part of fog calculation,
 * but it is actually a generic color register that can be used in custom
 * blender formulas. 
 * 
 * Another similar blender register is the FOG register, configured via
 * #rdpq_set_fog_color.
 * 
 * See #RDPQ_BLENDER and #RDPQ_BLENDER2 on how to configure
 * the blender (typically, via #rdpq_mode_blender).
 * 
 * @param[in] color             Color to set the BLEND register to
 * 
 * @see #RDPQ_BLENDER
 * @see #RDPQ_BLENDER2
 * @see #rdpq_set_fog_color
 * @see #rdpq_mode_blender
 */
inline void rdpq_set_blend_color(color_t color)
{
    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_BLEND_COLOR, 0, color_to_packed32(color),
        AUTOSYNC_PIPE);
}

/**
 * @brief Set the RDP PRIM combiner register (color only) (RDP command: SET_PRIM_COLOR)
 * 
 * This function sets the internal RDP PRIM register, part of the
 * color combiner unit. Naming aside, it is a generic color register that
 * can be used in custom color combiner formulas. 
 * 
 * Another similar blender register is the ENV register, configured via
 * #rdpq_set_env_color.
 * 
 * See #RDPQ_COMBINER1 and #RDPQ_COMBINER2 on how to configure
 * the color combiner (typicall, via #rdpq_mode_combiner).
 * 
 * If you wish to set PRIM LOD or PRIM MIN LOD values of the PRIM register,
 * see #rdpq_set_prim_lod_frac, #rdpq_set_detail_factor or #rdpq_set_prim_register_raw.
 * 
 * @param[in] color             Color to set the PRIM register to
 * 
 * @see #RDPQ_COMBINER1
 * @see #RDPQ_COMBINER2
 * @see #rdpq_set_env_color
 * @see #rdpq_mode_combiner
 * @see #rdpq_set_prim_lod_frac
 * @see #rdpq_set_detail_factor
 * @see #rdpq_set_prim_register_raw
 * 
 */
inline void rdpq_set_prim_color(color_t color)
{
    // NOTE: this does not require a pipe sync
    extern void __rdpq_fixup_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_fixup_write8_syncchange(RDPQ_CMD_SET_PRIM_COLOR_COMPONENT, (0<<16), color_to_packed32(color), 0);
}

/**
 * @brief Set the detail/sharpen blending factor (RDP command: SET_PRIM_COLOR (partial))
 * 
 * This function sets the internal minimum clamp for LOD fraction, that is used for 
 * determining the interpolation blend factor of a detail or sharpen texture at high 
 * magnification.
 * 
 * Range is [0..1] where 0 means no influence, and 1 means full influence.
 * The range is internally inverted and converted to [0..31] for the RDP hardware
 * 
 * @param[in] value             Value to set the register to in range [0..1]
 * 
 * @see #RDPQ_COMBINER1
 * @see #RDPQ_COMBINER2
 * @see #rdpq_mode_combiner
 * 
 */
inline void rdpq_set_detail_factor(float value)
{
    // NOTE: this does not require a pipe sync
    int8_t conv = (1.0f - value) * 31;
    extern void __rdpq_fixup_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_fixup_write8_syncchange(RDPQ_CMD_SET_PRIM_COLOR_COMPONENT, ((conv & 0x1F) << 8) | (2<<16), 0, 0);
}

/**
 * @brief Set the RDP PRIM LOD FRAC combiner register (RDP command: SET_PRIM_COLOR (partial))
 * 
 * This function sets the internal Level of Detail fraction for primitive register, 
 * that is used for custom linear interpolation between any two colors in a Color Combiner.
 * 
 * See #RDPQ_COMBINER1 and #RDPQ_COMBINER2 on how to configure
 * the color combiner (typicall, via #rdpq_mode_combiner).
 * 
 * If you wish to set PRIM MIN LOD value, see #rdpq_set_detail_factor.
 * 
 * @param[in] value             Value to set the PRIM LOD register to in range [0..255]
 * 
 * @see #RDPQ_COMBINER1
 * @see #RDPQ_COMBINER2
 * @see #rdpq_mode_combiner
 * @see #rdpq_set_detail_factor
 * 
 */
inline void rdpq_set_prim_lod_frac(uint8_t value)
{
    // NOTE: this does not require a pipe sync
    extern void __rdpq_fixup_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_fixup_write8_syncchange(RDPQ_CMD_SET_PRIM_COLOR_COMPONENT, value | (1<<16), 0, 0);
}

/**
 * @brief Set the RDP PRIM combiner register (raw version) (RDP command: SET_PRIM_COLOR)
 * 
 * This function sets the internal RDP PRIM register, part of the
 * color combiner unit. Naming aside, it is a generic color register that
 * can be used in custom color combiner formulas. 
 * 
 * It also sets the PRIM LOD FRAC and PRIM MIN LOD FRAC values for the PRIM register
 * For more information, see #rdpq_set_prim_lod_frac, #rdpq_set_detail_factor.
 * 
 * Another similar blender register is the ENV register, configured via
 * #rdpq_set_env_color.
 * 
 * See #RDPQ_COMBINER1 and #RDPQ_COMBINER2 on how to configure
 * the color combiner (typicall, via #rdpq_mode_combiner).
 * 
 * If you wish to set PRIM COLOR or PRIM LOD or PRIM MIN LOD values individually,
 * see #rdpq_set_prim_lod_frac, #rdpq_set_detail_factor or #rdpq_set_prim_color.
 * 
 * @param[in] color             Color to set the PRIM register to
 * @param[in] minlod            Minimum LOD fraction to set the PRIM register to
 * @param[in] primlod           Primitive LOD fraction to set the PRIM register to
 * 
 * @see #RDPQ_COMBINER1
 * @see #RDPQ_COMBINER2
 * @see #rdpq_set_env_color
 * @see #rdpq_set_prim_color
 * @see #rdpq_set_prim_lod_frac
 * @see #rdpq_set_detail_factor
 * 
 */
inline void rdpq_set_prim_register_raw(color_t color, uint8_t minlod, uint8_t primlod)
{
    extern void __rdpq_write8(uint32_t cmd_id, uint32_t arg0, uint32_t arg1);
    __rdpq_write8(RDPQ_CMD_SET_PRIM_COLOR, ((minlod & 0x1F) << 8) | primlod, color_to_packed32(color));
}

/**
 * @brief Set the RDP ENV combiner register (RDP command: SET_ENV_COLOR)
 * 
 * This function sets the internal RDP ENV register, part of the
 * color combiner unit. Naming aside, it is a generic color register that
 * can be used in custom color combiner formulas. 
 * 
 * Another similar blender register is the PRIM register, configured via
 * #rdpq_set_prim_color.
 * 
 * See #RDPQ_COMBINER1 and #RDPQ_COMBINER2 on how to configure
 * the color combiner (typically, via #rdpq_mode_combiner).
 * 
 * @param[in] color             Color to set the ENV register to
 * 
 * @see #RDPQ_COMBINER1
 * @see #RDPQ_COMBINER2
 * @see #rdpq_set_prim_color
 * @see #rdpq_mode_combiner
 * 
 */
inline void rdpq_set_env_color(color_t color)
{
    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_ENV_COLOR, 0, color_to_packed32(color),
        AUTOSYNC_PIPE);
}

/**
 * @brief Configure the framebuffer to render to (RDP command: SET_COLOR_IMAGE)
 * 
 * This command is used to specify the render target that the RDP will draw to.
 * 
 * Calling this function also automatically configures scissoring (via
 * #rdpq_set_scissor), so that all draw commands are clipped within the buffer,
 * to avoid overwriting memory around it. Use `rdpq_config_disable(RDPQ_CFG_AUTOSCISSOR)`
 * if you need to disable this behavior.
 * 
 * If you have a raw pointer instead of a #surface_t, you can use #surface_make to create
 * a temporary surface structure to pass the information to #rdpq_set_color_image.
 * 
 * If the passed surface is NULL, rdpq will be detached from the render target. If
 * a drawing command is issued without a render target, it will be silently
 * ignored (but the validator will flag it as an error).
 * 
 * The only valid formats for a surface to be used as a render target are: #FMT_RGBA16,
 * #FMT_RGBA32, and #FMT_I8.
 *
 * @param[in]  surface   Surface to set as render target
 * 
 * @see #rdpq_set_color_image_raw
 */
void rdpq_set_color_image(const surface_t *surface);

/**
 * @brief Configure the Z-buffer to use (RDP command: SET_Z_IMAGE)
 * 
 * This commands is used to specify the Z-buffer that will be used by RDP for the next
 * rendering commands.
 * 
 * The surface must have the same width and height of the surface set as render target
 * (via #rdpq_set_color_image or #rdpq_set_color_image_raw). The color format should be
 * FMT_RGBA16, even though Z values will be written to it.
 * 
 * If the passed surface is NULL, rdpq will be detached from the Z buffer. If
 * a drawing command using Z is issued without a Z buffer, the behaviour will be
 * undefined (but the validator will flag it as an error).
 * 
 * @param surface      Surface to set as Z buffer
 * 
 * @see #rdpq_set_z_image_raw
 */
void rdpq_set_z_image(const surface_t* surface);

/**
 * @brief Configure the texture to use (RDP command: SET_TEX_IMAGE)
 * 
 * This commands is used to specify the texture image that will be used by RDP for
 * the next load commands (#rdpq_load_tile and #rdpq_load_block).
 * 
 * The surface must have the same width and height of the surface set as render target
 * (via #rdpq_set_color_image or #rdpq_set_color_image_raw). The color format should be
 * #FMT_RGBA16, even though Z values will be written to it.
 * 
 * @param surface      Surface to set as texture
 * 
 * @see #rdpq_set_texture_image_raw
 */
void rdpq_set_texture_image(const surface_t* surface);

/**
 * @brief Low-level version of #rdpq_set_color_image, with address lookup capability.
 * 
 * This is a low-level version of #rdpq_set_color_image, that exposes the address lookup
 * capability. It allows to either pass a direct buffer, or to use a buffer already stored
 * in the address lookup table, adding optionally an offset. See #rdpq_set_lookup_address
 * for more information.
 * 
 * RDP a physical constraint of 64-byte alignment for render targets, so make sure to respect
 * that while configuring a buffer. The validator will flag such a mistake.
 * 
 * @param index        Index in the rdpq lookup table of the buffer to set as render target.
 * @param offset       Byte offset to add to the buffer stored in the lookup table. Notice that
 *                     if index is 0, this can be a physical address to a buffer (use
 *                     #PhysicalAddr to convert a C pointer to a physical address).
 * @param format       Format of the buffer. Only #FMT_RGBA32, #FMT_RGBA16 or #FMT_I8 are
 *                     possible to use as a render target.
 * @param width        Width of the buffer in pixel
 * @param height       Height of the buffer in pixel
 * @param stride       Stride of the buffer in bytes (length of a row)
 * 
 * @see #rdpq_set_color_image
 * @see #rdpq_set_lookup_address
 */
inline void rdpq_set_color_image_raw(uint8_t index, uint32_t offset, tex_format_t format, uint32_t width, uint32_t height, uint32_t stride)
{
    assertf(format == FMT_RGBA32 || format == FMT_RGBA16 || 
            format == FMT_I8 || format == FMT_CI8, 
        "Image format is not supported as color image: %s\nIt must be FMT_RGBA32, FMT_RGBA16, FMT_I8 or FMT_CI8", tex_format_name(format));
    assertf(index <= 15, "Lookup address index out of range [0,15]: %d", index);

    extern void __rdpq_set_color_image(uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_set_color_image(
        _carg(format, 0x1F, 19) | _carg(TEX_FORMAT_BYTES2PIX(format, stride)-1, 0x3FF, 0) | _carg(height-1, 0x1FF, 10),
        _carg(index, 0xF, 26) | (offset & 0x1FFFFFF) | _carg((height-1)>>9, 0x1, 31),
        _carg(0, 0xFFF, 12) | _carg(0, 0xFFF, 0),                 // for set_scissor
        _carg(width*4, 0xFFF, 12) | _carg(height*4, 0xFFF, 0));   // for set_scissor
}

/**
 * @brief Low-level version of #rdpq_set_z_image, with address lookup capability.
 * 
 * This is a low-level version of #rdpq_set_z_image, that exposes the address lookup
 * capability. It allows to either pass a direct buffer, or to use a buffer already stored
 * in the address lookup table, adding optionally an offset. See #rdpq_set_lookup_address
 * for more information.
 * 
 * RDP a physical constraint of 64-byte alignment for render targets, so make sure to respect
 * that while configuring a buffer. The validator will flag such a mistake.
 * 
 * @param index        Index in the rdpq lookup table of the buffer to set as render target.
 * @param offset       Byte offset to add to the buffer stored in the lookup table. Notice that
 *                     if index is 0, this can be a physical address to a buffer (use
 *                     #PhysicalAddr to convert a C pointer to a physical address).
 * 
 * @see #rdpq_set_z_image
 * @see #rdpq_set_lookup_address
 */
inline void rdpq_set_z_image_raw(uint8_t index, uint32_t offset)
{
    assertf(index <= 15, "Lookup address index out of range [0,15]: %d", index);
    extern void __rdpq_fixup_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    __rdpq_fixup_write8_syncchange(RDPQ_CMD_SET_Z_IMAGE,
        0, 
        _carg(index, 0xF, 26) | (offset & 0x1FFFFFF),
        AUTOSYNC_PIPE);
}

/**
 * @brief Low-level version of #rdpq_set_texture_image, with address lookup capability.
 * 
 * This is a low-level version of #rdpq_set_texture_image, that exposes the address lookup
 * capability. It allows to either pass a direct buffer, or to use a buffer already stored
 * in the address lookup table, adding optionally an offset. See #rdpq_set_lookup_address
 * for more information.
 * 
 * RDP a physical constraint of 8-byte alignment for textures, so make sure to respect
 * that while configuring a buffer. The validator will flag such a mistake.
 * 
 * @param index        Index in the rdpq lookup table of the buffer to set as texture image.
 * @param offset       Byte offset to add to the buffer stored in the lookup table. Notice that
 *                     if index is 0, this can be a physical address to a buffer (use
 *                     #PhysicalAddr to convert a C pointer to a physical address).
 * @param format       Format of the texture (#tex_format_t)
 * @param width        Width of the texture in pixel (max 1024)
 * @param height       Height of the texture in pixel (max 1024)
 * 
 * @see #rdpq_set_texture_image
 * @see #rdpq_set_lookup_address
 */
inline void rdpq_set_texture_image_raw(uint8_t index, uint32_t offset, tex_format_t format, uint16_t width, uint16_t height)
{
    assertf(width <= 1024, "Texture width out of range [1,1024]: %d", width);
    assertf(height <= 1024, "Texture height out of range [1,1024]: %d", height);
    assertf(index <= 15, "Lookup address index out of range [0,15]: %d", index);
    extern void __rdpq_fixup_write8_syncchange(uint32_t, uint32_t, uint32_t, uint32_t);
    // NOTE: we also encode the texture height in the command (split in two halves...)
    // to help the validator to a better job. The RDP hardware ignores those bits.
    __rdpq_fixup_write8_syncchange(RDPQ_CMD_SET_TEXTURE_IMAGE,
        _carg(format, 0x1F, 19) | _carg(width-1, 0x3FF, 0) | _carg(height-1, 0x1FF, 10),
        _carg(index, 0xF, 26) | (offset & 0x1FFFFFF) | _carg((height-1)>>9, 0x1, 31),
        AUTOSYNC_PIPE);
}

/**
 * @brief Load a block of memory to TMEM with a single contiguous memory transfer.
 *
 * Loads data from RDRAM to TMEM but takes in byte offsets and sizes unlike #rdpq_load_block.
 *
 * @param[in] offset    Destination TMEM offset
 * @param[in] buffer    Pointer to data in RDRAM to load
 * @param[in] size      Number of bytes to load (max: 4096)
 *
 * @see #rdpq_load_block
 * @see #rdpq_load_block_fx
 */
inline void rdpq_load_block_linear(int32_t offset, void *buffer, uint16_t size)
{
    assertf((offset & 7) == 0, "invalid TMEM offset %ld: must be 8-byte aligned", offset);
    assertf((PhysicalAddr(buffer) & 7) == 0, "invalid buffer: %p, must be 8-byte aligned", buffer);
    assertf((size & 7) == 0, "invalid size %d: must be a multiple of 8", size);
    assertf(size <= 4096, "invalid size %d: must fit TMEM", size);
    rdpq_set_texture_image_raw(0, PhysicalAddr(buffer), FMT_RGBA16, 8, size / 8);
    rdpq_set_tile(RDPQ_TILE_INTERNAL, FMT_RGBA16, offset, 0, NULL);
    uint32_t num_texels = size / 2;
    rdpq_load_block_fx(RDPQ_TILE_INTERNAL, 0, 0, num_texels, 0);
}

/**
 * @brief Store an address into the rdpq lookup table
 * 
 * This function is for advanced usages, it is not normally required to call it.
 * 
 * This function modifies the internal RDPQ address lookup table, by storing
 * an address into one of the available slots.
 * 
 * The lookup table is used to allow for an indirect access to surface pointers.
 * For instance, some library code might want to record a block that manipulates
 * several surfaces, but without saving the actual surface pointers within the
 * block. Instead, all commands referring to a surface, will actually refer to
 * an index into the lookup table. The caller of the block will then store
 * the actual buffer pointers in the table, before playing back the block.
 * 
 * While recording, you can create a placeholder surface via #surface_make_placeholder or
 * #surface_make_placeholder_linear that is just an "index" into the lookup
 * table.
 * 
 * @code{.c}
 *      // Create placeholder surfaces with indices 3 and 4
 *      surface_t tex1 = surface_make_placeholder_linear(3, FMT_RGBA16, 32, 32);
 *      surface_t tex2 = surface_make_placeholder_linear(4, FMT_RGBA16, 32, 32);
 * 
 *      // Start recording a block.
 *      rspq_block_begin();
 *      rdpq_set_mode_standard();
 *      
 *      // Load texture from lookup table (slot 3) and draw it to the screen
 *      rdpq_set_texture_image(&tex1);
 *      rdpq_load_tile(0, 0, 32, 32);
 *      rdpq_texture_rectangle(0, 0, 32, 32);
 *      
 *      // Load texture from lookup table (slot 4) and draw it to the screen
 *      rdpq_set_texture_image(&tex2);
 *      rdpq_load_tile(0, 0, 32, 32);
 *      rdpq_texture_rectangle(32, 0, 64, 32);
 * 
 *      rspq_block_t *bl = rspq_block_end();
 * 
 *      [...]
 *
 *      // Set two real textures into the the lookup table and call the block
 *      rdpq_set_lookup_address(3, robot->buffer);
 *      rdpq_set_lookup_address(4, dragon->buffer);
 *      rspq_block_run(bl);
 * @endcode
 * 
 * @note RDP has some alignment constraints: color and Z buffers must be 64-byte aligned,
 *       and textures must be 8-byte aligned.
 *
 * @param index           Index of the slot in the table. Available slots are 1-15
 *                        (slot 0 is reserved).
 * @param rdram_addr      Pointer of the buffer to store into the address table.
 * 
 */
inline void rdpq_set_lookup_address(uint8_t index, void* rdram_addr)
{
    assertf(index > 0 && index <= 15, "Lookup address index out of range [1,15]: %d", index);
    extern void __rdpq_write8(uint32_t, uint32_t, uint32_t);
    __rdpq_write8(RDPQ_CMD_SET_LOOKUP_ADDRESS, index << 2, PhysicalAddr(rdram_addr));
}

/**
 * @brief Schedule a RDP SYNC_PIPE command.
 * 
 * This command must be sent before changing the RDP pipeline configuration (eg: color
 * combiner, blender, colors, etc.) if the RDP is currently drawing.
 * 
 * Normally, you do not need to call this function because rdpq automatically
 * emits sync commands whenever necessary. You must call this function only
 * if you have disabled autosync for SYNC_PIPE (see #RDPQ_CFG_AUTOSYNCPIPE).
 * 
 * @note No software emulator currently requires this command, so manually
 *       sending SYNC_PIPE should be developed on real hardware.
 */
void rdpq_sync_pipe(void);

/**
 * @brief Schedule a RDP SYNC_TILE command.
 * 
 * This command must be sent before changing a RDP tile configuration if the
 * RDP is currently drawing using that same tile.
 * 
 * Normally, you do not need to call this function because rdpq automatically
 * emits sync commands whenever necessary. You must call this function only
 * if you have disabled autosync for SYNC_TILE (see #RDPQ_CFG_AUTOSYNCTILE).
 * 
 * @note No software emulator currently requires this command, so manually
 *       sending SYNC_TILE should be developed on real hardware.
 */
void rdpq_sync_tile(void);

/**
 * @brief Schedule a RDP SYNC_LOAD command.
 * 
 * This command must be sent before loading an area of TMEM if the
 * RDP is currently drawing using that same area. 
 * 
 * Normally, you do not need to call this function because rdpq automatically
 * emits sync commands whenever necessary. You must call this function only
 * if you have disabled autosync for SYNC_LOAD (see #RDPQ_CFG_AUTOSYNCLOAD).
 * 
 * @note No software emulator currently requires this command, so manually
 *       sending SYNC_LOAD should be developed on real hardware.
 */
void rdpq_sync_load(void);

/**
 * @brief Schedule a RDP SYNC_FULL command and register a callback when it is done.
 * 
 * This function schedules a RDP SYNC_FULL command into the RSP queue. This
 * command basically forces the RDP to finish drawing everything that has been
 * sent to it before it, and then generate an interrupt when it is done.
 * 
 * This is normally useful at the end of the frame. For instance, it is used
 * internally by #rdpq_detach_wait to make sure RDP is finished drawing on
 * the target display before detaching it.
 * 
 * The function can be passed an optional callback that will be called
 * when the RDP interrupt triggers. This can be useful to perform some operations
 * asynchronously.
 * 
 * @param      callback  A callback to invoke under interrupt when the RDP
 *                       is finished drawing, or NULL if no callback is necessary.
 * @param      arg       Opaque argument that will be passed to the callback.
 * 
 * @see #rspq_wait
 * @see #rdpq_fence
 * 
 */
void rdpq_sync_full(void (*callback)(void*), void* arg);


/**
 * @brief Low-level function to set the rendering mode register.
 * 
 * This function enqueues a low-level SET_OTHER_MODES RDP command that changes
 * the RDP render mode, setting it to a new value
 * 
 * This function is very low level and requires very good knowledge of internal
 * RDP state management. Moreover, it completely overwrites any existing
 * configuration for all bits, so it must be used with caution within a block.
 * 
 * @note If possible, prefer using the RDPQ mode API (defined in rdpq_mode.h),
 * that expose a higher level API for changing the RDP modes
 *
 * @param      mode     The new render mode. See the RDP_RM
 * 
 */
inline void rdpq_set_other_modes_raw(uint64_t mode)
{
    extern void __rdpq_set_other_modes(uint32_t, uint32_t);
    __rdpq_set_other_modes(
        (mode >> 32) & 0x00FFFFFF,
        mode & 0xFFFFFFFF);
}

/**
 * @brief Low-level function to partly change the rendering mode register.
 * 
 * This function is very low level and requires very good knowledge of internal
 * RDP state management.
 * 
 * It allows to partially change the RDP render mode register, enqueuing a
 * command that will modify only the requested bits. This function
 * is to be preferred to #rdpq_set_other_modes_raw as it preservers existing
 * render mode for all the other bits, so it allows for easier composition.
 * 
 * @note If possible, prefer using the RDPQ mode API (defined in rdpq_mode.h),
 * that expose a higher level API for changing the RDP modes
 * 
 * @param[in] mask          Mask of bits of the SOM register that must be changed
 * @param[in] val           New value for the bits selected by the mask.
 * 
 */
inline void rdpq_change_other_modes_raw(uint64_t mask, uint64_t val)
{
    extern void __rdpq_change_other_modes(uint32_t, uint32_t, uint32_t);

    if (mask >> 32)
        __rdpq_change_other_modes(0, ~(mask >> 32), val >> 32);
    if ((uint32_t)mask)
        __rdpq_change_other_modes(4, ~(uint32_t)mask, (uint32_t)val);
}

/**
 * @brief Read the current render mode register.
 * 
 * This function executes a full sync (#rspq_wait) and then extracts the
 * current raw render mode from the RSP state. This should be used only
 * for debugging purposes.
 *
 * @return     THe current value of the render mode register.
 */
uint64_t rdpq_get_other_modes_raw(void);

/**
 * @brief Low-level function to change the RDP combiner.
 * 
 * This function enqueues a low-level SET_COMBINE RDP command that changes
 * the RDP combiner, setting it to a new value.
 * You can use #RDPQ_COMBINER1 and #RDPQ_COMBINER2 to create 
 * the combiner settings for respectively a 1-pass or 2-pass combiner.
 * 
 * @note Prefer using #rdpq_mode_combiner (part of the RDPQ mode API), as it better
 * handles integration with other render mode changes.
 * 
 * @param      comb     The new combiner setting
 * 
 * @see #rdpq_mode_combiner
 * @see #RDPQ_COMBINER1
 * @see #RDPQ_COMBINER2
 * 
 */
inline void rdpq_set_combiner_raw(uint64_t comb) {
    extern void __rdpq_write8_syncchange(uint32_t cmd_id, uint32_t arg0, uint32_t arg1, uint32_t autosync);
    __rdpq_write8_syncchange(RDPQ_CMD_SET_COMBINE_MODE_RAW,
        (comb >> 32) & 0x00FFFFFF,
        comb & 0xFFFFFFFF,
        AUTOSYNC_PIPE);   
}

/**
 * @brief Read the current combiner register.
 * 
 * This function executes a full sync (#rspq_wait) and then extracts the
 * current raw combiner from the RSP state. This should be used only
 * for debugging purposes.
 *
 * @return     THe current value of the combiner register.
 */
uint64_t rdpq_get_combiner_raw(void);

/**
 * @brief Add a fence to synchronize RSP with RDP commands.
 * 
 * This function schedules a fence in the RSP queue that makes RSP waits until
 * all previously enqueued RDP commands have finished executing. This is useful
 * in the rare cases in which you need to post-process the output of RDP with RSP
 * commands.
 * 
 * Notice that the RSP will spin-lock waiting for RDP to become idle, so, if
 * possible, call rdpq_fence as late as possible, to allow for parallel RDP/RSP
 * execution for the longest possible time.
 * 
 * Notice that this does not block the CPU in any way; the CPU will just
 * schedule the fence command in the RSP queue and continue execution. If you
 * need to block the CPU until the RDP is done, check #rspq_wait or #rdpq_sync_full
 * instead.
 * 
 * @see #rdpq_sync_full
 * @see #rspq_wait
 */
void rdpq_fence(void);

/**
 * @brief Send to the RDP a buffer of RDP commands from RDRAM
 * 
 * This command can be used to execute raw RDP commands from RDRAM. It is
 * normally not necessary to call this function as normal rdpq functions will
 * simply enqueue the commands in the RSP queue, but there can be cases
 * where commands have been prepared in RAM somehow (especially, for compatibility
 * with existing code that assembled RDP commands in RDRAM, or to playback
 * RDP command lists prepared with offline tools). 
 * 
 * This function fully interoperates with the rest of RDPQ, so you can freely
 * intermix it with standard rdpq calls.
 * 
 * @param buffer        Pointer to the buffer containing RDP commands
 * @param size          Size of the buffer, in bytes (must be a multiple of 8)
 * 
 * @note This function cannot be called within a block.
 */
void rdpq_exec(void *buffer, int size);

/**
 * @brief Enqueue a callback that will be called after the RSP and the RDP have
 *        finished processing all commands enqueued until now.
 * 
 * This function is similar to #rspq_call_deferred, but it also guarantees
 * that the callback is called after the RDP has finished processing all
 * commands enqueued until now.
 * 
 * For example:
 * 
 * @code{.c}
 *      // Draw a green rectangle
 *      rdpq_mode_set_fill(RGBA(0,255,0,0));
 *      rdpq_fill_rectangle(10, 10, 100, 100);
 * 
 *      // Enqueue a callback. The callback is guaranteed to be called
 *      // after the RSP has finished prepared the RDP command list for the
 *      // filled rectangle. It is possible that the RDP would still
 *      // be processing the rectangle when the callback is called.
 *      rspq_call_deferred(my_callback1, NULL);
 * 
 *      // Enqueue a callback. The callback is guaranteed to be called
 *      // after the rectangle has been fully drawn to the target buffer, so
 *      // that for instance the callback could readback the green pixels.
 *      rdpq_call_deferred(my_callback2, NULL);
 * @endcode
 * 
 * @param func          Callback function to call 
 * @param arg           Argument to pass to the callback function
 */
void rdpq_call_deferred(void (*func)(void *), void *arg);

/**
 * @brief Enqueue a RSP command that also generates RDP commands.
 * 
 * This function is similar to #rspq_write: it enqueues a RSP command in the
 * RSP command queue for later execution by RSP. The main difference is that
 * this macro also declares that the RSP command is going to generate RDP
 * commands as part of its execution.
 * 
 * RSP commands in overlays can generate RDP commands by including rsp_rdqp.inc
 * and calling RDPQ_Send (or RDPQ_Write8 / RDPQ_Write16 / RDPQ_Finalize). If
 * they do, they must enqueued using #rdpq_write instead of #rspq_write.
 * 
 * It is important to know that the RSP command is going to generate RDP commands
 * because the space for them needs to be allocated in the static buffer in
 * blocks. When wrongly using #rspq_write instead of #rdpq_write, the command
 * will work correctly outside of blocks but might fail in surprising ways
 * when called within blocks.
 * 
 * In some cases, it is not possible to know beforehand how many RDP commands 
 * will be generated. In these case, @p num_rdp_commands should be the maximum
 * possible value in words. If the number is quite high and potentially
 * unbounded, pass the special value "-1".
 * 
 * @param num_rdp_commands    Maximum number of RDP 8-byte commands that will be
 *                            generated by the RSP command. Use -1 if the number
 *                            is unbounded and potentially high. 
 * @param ovl_id              ID of the overlay for the command (see #rspq_write)
 * @param cmd_id              ID of the command (see #rspq_write)
 * 
 * @see #rspq_write
 * 
 * @note Some RDP commands are made of multiple 64 bit words. For the purpose
 *       of #rdpq_write, please treat @p num_rdp_commands as it was the
 *       "number of 64-bit words". So for instance if the RSP command generates
 *       a single RDP TEXTURE_RECTANGLE command, pass 2 as @p num_rdp_commands.
 * 
 * @hideinitializer
 */
#define rdpq_write(num_rdp_commands, ovl_id, cmd_id, ...) ({ \
    int __num_rdp_commands = (num_rdp_commands); \
    if (!__builtin_constant_p(__num_rdp_commands) || __num_rdp_commands != 0) { \
        if (__builtin_expect(rspq_block != NULL, 0)) { \
            __rdpq_block_reserve(__num_rdp_commands); \
        } \
    } \
    rspq_write(ovl_id, cmd_id, ##__VA_ARGS__); \
})

/// @cond
// Declarations used by rdpq_write, not part of the public API.
typedef struct rspq_block_s rspq_block_t;
extern rspq_block_t *rspq_block;
extern void __rdpq_block_reserve(int); \
/// @endcond


#ifdef __cplusplus
}
#endif

#endif
