mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-02 20:58:18 +00:00
WIP: working towards minimizing C-ABI & PsyQ CRT usage
This commit is contained in:
+15
-23
@@ -50,17 +50,13 @@
|
||||
#define asm_words(...) m_expand(glue(GCC_ASM_INL_, GCC_ASM_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__))
|
||||
// Very nasty macro expansion. See the Cruft pragma region after all the DSL defines
|
||||
|
||||
/* reg_str(n) — Stringify an integer register id into the GCC asm
|
||||
* string form (e.g. 12 → "$12"). Use this anywhere GCC's parser
|
||||
* expects a literal string identifying a register: clobber lists,
|
||||
* asm templates, etc. The two-level macro is the standard preprocessor
|
||||
* idiom for forcing one level of expansion before stringify — without
|
||||
* it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead
|
||||
* of expanding `R_T4` to its value first.
|
||||
/* reg_str(n) — Stringify an integer register id into the GCC asm string form (e.g. 12 → "$12").
|
||||
* Use this anywhere GCC's parser expects a literal string identifying a register: clobber lists,
|
||||
* asm templates, etc. The two-level macro is the standard preprocessor idiom for forcing one level of expansion before stringify —
|
||||
* without it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead of expanding `R_T4` to its value first.
|
||||
*
|
||||
* For declaring a register variable bound to a specific GPR, use the
|
||||
* `rgcc(n)` bundle from gcc_asm.h instead — it adds the `__asm__()`
|
||||
* qualifier around the string.
|
||||
* For declaring a register variable bound to a specific GPR, use the `rgcc(n)` bundle from gcc_asm.h instead —
|
||||
* it adds the `__asm__()` qualifier around the string.
|
||||
*
|
||||
* register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose
|
||||
* register V3_S2* p0 rgcc(R_T4) = ...; // bundled
|
||||
@@ -85,21 +81,19 @@
|
||||
* - The string "$12" is derived from it via reg_str, so they cannot drift apart.
|
||||
* - Spelling `__asm__(reg_str(R_T4_Code))` at every call site is noise.
|
||||
*
|
||||
* tmpl defined in dsl.h (the token-paste glue).
|
||||
* tmpl defined in dsl.h (token-paste glue).
|
||||
* rgcc define here (gcc_asm.h) because the `__asm__` keyword is GCC-specific.
|
||||
* Anyone porting to a different compiler's asm dialect overrides rgcc,
|
||||
* Anyone porting to a different compiler's asm dialect overrides rgcc,
|
||||
* and the integer→string derivation in rlit can be retargeted in one place.
|
||||
*
|
||||
* For clobber lists and asm-template strings, use the bare `rlit(R_T4_Code)`.
|
||||
* ------------------------------------------------------------------------ */
|
||||
#define rgcc(n) __asm__(rlit(n))
|
||||
|
||||
/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used
|
||||
* by the placeholder-pun macros (the .word bodies are fully baked
|
||||
* at compile time and have no runtime operand references), but kept
|
||||
* here for completeness in case a future asm template needs to refer
|
||||
* to a runtime input by position. Mirror of rgcc but produces "%N"
|
||||
* instead of "$N". */
|
||||
/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used by the placeholder-pun macros
|
||||
* (the .word bodies are fully baked at compile time and have no runtime operand references),
|
||||
* but kept here for completeness in case a future asm template needs to refer to a runtime input by position.
|
||||
* Mirror of rgcc but produces "%N" instead of "$N". */
|
||||
#define rgcc_ref_(n) "%" #n
|
||||
#define rgcc_ref(n) rgcc_ref_(n)
|
||||
|
||||
@@ -147,11 +141,9 @@
|
||||
9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
|
||||
|
||||
/* --- 2. String Concatenation Helpers --- *
|
||||
* NOTE: we use `%0`, `%1`, ... not `%c0`, `%c1`, ... because GCC's
|
||||
* asm-parser rejects `%cN` in this position with "invalid use of '%c'".
|
||||
* The `%cN` form is for printing *character* constants; for arbitrary
|
||||
* integer immediates (the only kind `"i"(...)` produces), the plain
|
||||
* `%N` form is the right one. Both expand to the bare immediate.
|
||||
* NOTE: we use `%0`, `%1`, ... not `%c0`, `%c1`, ... because GCC's asm-parser rejects `%cN` in this position with "invalid use of '%c'".
|
||||
* The `%cN` form is for printing *character* constants; for arbitrary integer immediates (the only kind `"i"(...)` produces),
|
||||
* the plain `%N` form is the right one. Both expand to the bare immediate.
|
||||
*/
|
||||
#define GCC_ASM_W1 "%0"
|
||||
#define GCC_ASM_W2 GCC_ASM_W1 ", %1"
|
||||
|
||||
+288
-243
@@ -39,8 +39,8 @@
|
||||
/* ============================================================================
|
||||
* Hardware MMIO Addresses
|
||||
* ============================================================================
|
||||
* PSX GPU has two 32-bit ports in the I/O register region at KSEG2
|
||||
* 0x1F800000+. GP0 (offset 0x10) is the data port (commands + params).
|
||||
* PSX GPU has two 32-bit ports in the I/O register region at KSEG2 0x1F800000+.
|
||||
* GP0 (offset 0x10) is the data port (commands + params).
|
||||
* GP1 (offset 0x14) is the control port (status, ctrl writes).
|
||||
* ============================================================================ */
|
||||
/* IO base address (KSEG2 0x1F800000+ for the I/O register region).
|
||||
@@ -49,18 +49,18 @@
|
||||
* `lui $reg, 0x1F80` (1 word) then `sw $data, GPIO_PORT*_OFFSET($reg)` (1 word).
|
||||
* Mirrors the `IO_BASE_ADDR equ 0x1F80` + `gpio_port0 equ 0x1810` pattern from graphics_hello/gp.s. */
|
||||
enum {
|
||||
IO_BASE_ADDR = 0x1F800000, /* full 32-bit I/O region base */
|
||||
IO_BASE_ADDR_HI16 = 0x1F80, /* fits in a single `lui $reg, 0x1F80` */
|
||||
IO_BASE_ADDR = 0x1F800000, /* full 32-bit I/O region base */
|
||||
IO_BASE_ADDR_HI16 = 0x1F80, /* fits in a single `lui $reg, 0x1F80` */
|
||||
|
||||
/* Offsets from IO_BASE_ADDR to each port. Used by tape-side macros
|
||||
* that pin a register to IO_BASE_ADDR and access ports via offsets:
|
||||
* sw $data, GPIO_PORT0_OFFSET($io_base) ; write GP0
|
||||
* sw $data, GPIO_PORT1_OFFSET($io_base) ; write GP1 */
|
||||
GPIO_PORT0_OFFSET = 0x1810,
|
||||
GPIO_PORT1_OFFSET = 0x1814,
|
||||
/* Offsets from IO_BASE_ADDR to each port. Used by tape-side macros
|
||||
* that pin a register to IO_BASE_ADDR and access ports via offsets:
|
||||
* sw $data, GPIO_PORT0_OFFSET($io_base) ; write GP0
|
||||
* sw $data, GPIO_PORT1_OFFSET($io_base) ; write GP1 */
|
||||
GPIO_PORT0_OFFSET = 0x1810,
|
||||
GPIO_PORT1_OFFSET = 0x1814,
|
||||
|
||||
HW_GP0_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT0_OFFSET,
|
||||
HW_GP1_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT1_OFFSET,
|
||||
HW_GP0_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT0_OFFSET,
|
||||
HW_GP1_ADDR = (IO_BASE_ADDR_HI16 << 16) | GPIO_PORT1_OFFSET,
|
||||
};
|
||||
|
||||
#define HW_GP0 C_(U4 V_*, HW_GP0_ADDR)
|
||||
@@ -73,66 +73,64 @@ enum {
|
||||
* GP0 command byte constants + Layer 1 (GPU bitfield shifts)
|
||||
* ============================================================================
|
||||
* 8-bit GP0 opcodes (the upper byte of a primitive's first word). These are the BYTE only.
|
||||
* The layer-1 bitfield-layout constants live in the same enum block so the encoder can reference them by name.
|
||||
* NO macro body past this point uses a raw shift or raw mask.
|
||||
* Every shift/width/mask is named here, named once.
|
||||
* Mirrors the OPCODE_SHIFT / RS_SHIFT / REG_MASK convention from mips.h.
|
||||
* ============================================================================ */
|
||||
enum {
|
||||
gp0_cmd_Nop = 0x00,
|
||||
gp0_cmd_Nop = 0x00,
|
||||
|
||||
/* Cache management */
|
||||
gp0_cmd_ClearCache = 0x01,
|
||||
gp0_cmd_FillVram = 0x02,
|
||||
gp0_cmd_CopyVram = 0x80,
|
||||
gp0_cmd_CopyVramChained = 0x81,
|
||||
gp0_cmd_ReadVram = 0xC0,
|
||||
/* Cache management */
|
||||
gp0_cmd_ClearCache = 0x01,
|
||||
gp0_cmd_FillVram = 0x02,
|
||||
gp0_cmd_CopyVram = 0x80,
|
||||
gp0_cmd_CopyVramChained = 0x81,
|
||||
gp0_cmd_ReadVram = 0xC0,
|
||||
|
||||
/* Polygons */
|
||||
gp0_cmd_poly_f3 = 0x20, /* Flat Triangle */
|
||||
gp0_cmd_poly_ft3 = 0x24, /* Flat Textured Triangle */
|
||||
gp0_cmd_poly_g3 = 0x30, /* Gouraud Triangle */
|
||||
gp0_cmd_poly_gt3 = 0x34, /* Gouraud Textured Tri */
|
||||
gp0_cmd_poly_f4 = 0x28, /* Flat Quad */
|
||||
gp0_cmd_poly_ft4 = 0x2C, /* Flat Textured Quad */
|
||||
gp0_cmd_poly_g4 = 0x38, /* Gouraud Quad */
|
||||
gp0_cmd_poly_gt4 = 0x3C, /* Gouraud Textured Quad */
|
||||
/* Polygons */
|
||||
gp0_cmd_poly_f3 = 0x20, /* Flat Triangle */
|
||||
gp0_cmd_poly_ft3 = 0x24, /* Flat Textured Triangle */
|
||||
gp0_cmd_poly_g3 = 0x30, /* Gouraud Triangle */
|
||||
gp0_cmd_poly_gt3 = 0x34, /* Gouraud Textured Tri */
|
||||
gp0_cmd_poly_f4 = 0x28, /* Flat Quad */
|
||||
gp0_cmd_poly_ft4 = 0x2C, /* Flat Textured Quad */
|
||||
gp0_cmd_poly_g4 = 0x38, /* Gouraud Quad */
|
||||
gp0_cmd_poly_gt4 = 0x3C, /* Gouraud Textured Quad */
|
||||
|
||||
/* Lines */
|
||||
gp0_cmd_line_f2 = 0x40,
|
||||
gp0_cmd_line_g2 = 0x50,
|
||||
/* Lines */
|
||||
gp0_cmd_line_f2 = 0x40,
|
||||
gp0_cmd_line_g2 = 0x50,
|
||||
|
||||
/* Sprites + Tiles + Rects */
|
||||
gp0_cmd_sprt_1 = 0x64,
|
||||
gp0_cmd_sprt_8 = 0x74,
|
||||
gp0_cmd_sprt_16 = 0x7C,
|
||||
gp0_cmd_tile_1 = 0x60,
|
||||
gp0_cmd_tile_8 = 0x68,
|
||||
gp0_cmd_tile_16 = 0x70,
|
||||
/* Sprites + Tiles + Rects */
|
||||
gp0_cmd_sprt_1 = 0x64,
|
||||
gp0_cmd_sprt_8 = 0x74,
|
||||
gp0_cmd_sprt_16 = 0x7C,
|
||||
gp0_cmd_tile_1 = 0x60,
|
||||
gp0_cmd_tile_8 = 0x68,
|
||||
gp0_cmd_tile_16 = 0x70,
|
||||
|
||||
/* State setters (not drawing primitives; set render context). */
|
||||
gp0_cmd_DrawModeSetting = 0xE1, /* TPage / draw-mode (semi-trans, dither, etc.) */
|
||||
gp0_cmd_SetTextureWindow = 0xE2,
|
||||
gp0_cmd_SetDrawArea_TopLeft = 0xE3,
|
||||
gp0_cmd_SetDrawArea_BotRight = 0xE4,
|
||||
gp0_cmd_SetDrawOffset = 0xE5,
|
||||
gp0_cmd_SetMaskBit = 0xE6,
|
||||
/* State setters (not drawing primitives; set render context). */
|
||||
gp0_cmd_DrawModeSetting = 0xE1, /* TPage / draw-mode (semi-trans, dither, etc.) */
|
||||
gp0_cmd_SetTextureWindow = 0xE2,
|
||||
gp0_cmd_SetDrawArea_TopLeft = 0xE3,
|
||||
gp0_cmd_SetDrawArea_BotRight = 0xE4,
|
||||
gp0_cmd_SetDrawOffset = 0xE5,
|
||||
gp0_cmd_SetMaskBit = 0xE6,
|
||||
|
||||
/* bitfield shifts / widths / masks ----
|
||||
* Generic GP0/GP1 command byte (upper 8 bits of every word sent to either port). */
|
||||
gp0_cmd_shift = 24,
|
||||
gp0_cmd_width = 8,
|
||||
gp0_cmd_mask = 0xFF,
|
||||
/* bitfield shifts / widths / masks ----
|
||||
* Generic GP0/GP1 command byte (upper 8 bits of every word sent to either port). */
|
||||
gp0_cmd_shift = 24,
|
||||
gp0_cmd_width = 8,
|
||||
gp0_cmd_mask = 0xFF,
|
||||
|
||||
/* Color word layout (lives in Poly_F3.color, Poly_G4.c0..c3, etc.):
|
||||
* bits 31..24 = command byte
|
||||
* bits 23..16 = BLUE
|
||||
* bits 15..08 = GREEN
|
||||
* bits 07..00 = RED (PSX GPU is BGR, NOT RGB) */
|
||||
gp0_color_cmd_shift = 24, gp0_color_cmd_width = 8, gp0_color_cmd_mask = 0xFF,
|
||||
gp0_color_blue_shift = 16, gp0_color_blue_width = 8, gp0_color_blue_mask = 0xFF,
|
||||
gp0_color_green_shift = 8, gp0_color_green_width = 8, gp0_color_green_mask = 0xFF,
|
||||
gp0_color_red_shift = 0, gp0_color_red_width = 8, gp0_color_red_mask = 0xFF,
|
||||
/* Color word layout (lives in Poly_F3.color, Poly_G4.c0..c3, etc.):
|
||||
* bits 31..24 = command byte
|
||||
* bits 23..16 = BLUE
|
||||
* bits 15..08 = GREEN
|
||||
* bits 07..00 = RED (PSX GPU is BGR, NOT RGB) */
|
||||
gp0_color_cmd_shift = 24, gp0_color_cmd_width = 8, gp0_color_cmd_mask = 0xFF,
|
||||
gp0_color_blue_shift = 16, gp0_color_blue_width = 8, gp0_color_blue_mask = 0xFF,
|
||||
gp0_color_green_shift = 8, gp0_color_green_width = 8, gp0_color_green_mask = 0xFF,
|
||||
gp0_color_red_shift = 0, gp0_color_red_width = 8, gp0_color_red_mask = 0xFF,
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
@@ -171,10 +169,13 @@ enum {
|
||||
#define gp0_word_poly_gt4(r,g,b) enc_color_word(gp0_cmd_poly_gt4, (r),(g),(b))
|
||||
|
||||
/* Cache management — bare-cmd words (no color/range payload). */
|
||||
#define gp0_word_clear_cache() enc_gp0_cmd_word(gp0_cmd_ClearCache)
|
||||
#define gp0_word_fill_vram() enc_gp0_cmd_word(gp0_cmd_FillVram)
|
||||
#define gp0_word_copy_vram() enc_gp0_cmd_word(gp0_cmd_CopyVram)
|
||||
#define gp0_word_read_vram() enc_gp0_cmd_word(gp0_cmd_ReadVram)
|
||||
#define gp0_word_clear_cache() enc_gp0_cmd_word(gp0_cmd_ClearCache)
|
||||
#define gp0_word_fill_vram() enc_gp0_cmd_word(gp0_cmd_FillVram)
|
||||
#define gp0_word_copy_vram() enc_gp0_cmd_word(gp0_cmd_CopyVram)
|
||||
#define gp0_word_read_vram() enc_gp0_cmd_word(gp0_cmd_ReadVram)
|
||||
|
||||
/* NOP — bare-cmd word (no effect; used as DR_ENV padding). */
|
||||
#define gp0_word_nop() enc_gp0_cmd_word(gp0_cmd_Nop)
|
||||
|
||||
/* ============================================================================
|
||||
* GP1 command byte constants + Layer 1 (display-mode + range + draw-area bitfield shifts)
|
||||
@@ -184,58 +185,57 @@ enum {
|
||||
* (cmd byte in the upper 8 bits via `enc_gp0_cmd(cmd)`).
|
||||
* ============================================================================ */
|
||||
enum {
|
||||
gp1_cmd_Reset = 0x00,
|
||||
gp1_cmd_ResetCmdBuffer = 0x01,
|
||||
gp1_cmd_AcknowledgeIRQ = 0x02,
|
||||
gp1_cmd_DisplayEnable = 0x03,
|
||||
gp1_cmd_DMADirection = 0x04,
|
||||
gp1_cmd_StartDisplayArea = 0x05,
|
||||
gp1_cmd_HorizontalDisplayRange = 0x06,
|
||||
gp1_cmd_VerticalDisplayRange = 0x07,
|
||||
gp1_cmd_DisplayMode = 0x08,
|
||||
/* Note: GP1 only has commands 0x00..0x08.
|
||||
* The state-setter commands (SetTextureWindow, * SetDrawArea*, SetDrawOffset, SetMaskBit)
|
||||
* live in the GP0 enum as * 0xE1..0xE6.
|
||||
* DrawArea word builders are below as GP0s * macros (since they emit GP0 commands). */
|
||||
gp1_cmd_Reset = 0x00,
|
||||
gp1_cmd_ResetCmdBuffer = 0x01,
|
||||
gp1_cmd_AcknowledgeIRQ = 0x02,
|
||||
gp1_cmd_DisplayEnable = 0x03,
|
||||
gp1_cmd_DMADirection = 0x04,
|
||||
gp1_cmd_StartDisplayArea = 0x05,
|
||||
gp1_cmd_HorizontalDisplayRange = 0x06,
|
||||
gp1_cmd_VerticalDisplayRange = 0x07,
|
||||
gp1_cmd_DisplayMode = 0x08,
|
||||
/* Note: GP1 only has commands 0x00..0x08.
|
||||
* The state-setter commands (SetTextureWindow, * SetDrawArea*, SetDrawOffset, SetMaskBit)
|
||||
* live in the GP0 enum as * 0xE1..0xE6.
|
||||
* DrawArea word builders are below as GP0s * macros (since they emit GP0 commands). */
|
||||
|
||||
/* ---- Display-mode payload flags (per PSX-SPX §"GP1 Display Mode").
|
||||
* Bit positions match the encoder shifts below; values are the
|
||||
* *payload* bits only (the cmd byte is OR'd in by enc_gp1_disp_mode_word). */
|
||||
gp1_disp_HRes_256 = 0x0,
|
||||
gp1_disp_HRes_320 = 0x1,
|
||||
gp1_disp_HRes_512 = 0x2,
|
||||
gp1_disp_HRes_640 = 0x3,
|
||||
gp1_disp_VRes_240 = 0x0,
|
||||
gp1_disp_VRes_480 = 0x1,
|
||||
gp1_disp_Color15 = 0x0,
|
||||
gp1_disp_Color24 = 0x1,
|
||||
gp1_disp_VInterlace = 0x1,
|
||||
/* ---- Display-mode payload flags (per PSX-SPX §"GP1 Display Mode").
|
||||
* Bit positions match the encoder shifts below; values are the *payload* bits only (cmd byte is OR'd in by enc_gp1_disp_mode_word). */
|
||||
gp1_disp_HRes_256 = 0x0,
|
||||
gp1_disp_HRes_320 = 0x1,
|
||||
gp1_disp_HRes_512 = 0x2,
|
||||
gp1_disp_HRes_640 = 0x3,
|
||||
gp1_disp_VRes_240 = 0x0,
|
||||
gp1_disp_VRes_480 = 0x1,
|
||||
gp1_disp_Color15 = 0x0,
|
||||
gp1_disp_Color24 = 0x1,
|
||||
gp1_disp_VInterlace = 0x1,
|
||||
|
||||
/* ---- Layer 1: GP1 display-mode + range + draw-area shifts/masks ---- */
|
||||
gp1_disp_hres_shift = 0, gp1_disp_hres_width = 2, gp1_disp_hres_mask = 0x3,
|
||||
gp1_disp_vres_shift = 2, gp1_disp_vres_width = 1, gp1_disp_vres_mask = 0x1,
|
||||
gp1_disp_color_shift = 4, gp1_disp_color_width = 1, gp1_disp_color_mask = 0x1,
|
||||
gp1_disp_interlace_shift = 5, gp1_disp_interlace_width = 1, gp1_disp_interlace_mask = 0x1,
|
||||
/* ---- Layer 1: GP1 display-mode + range + draw-area shifts/masks ---- */
|
||||
gp1_disp_hres_shift = 0, gp1_disp_hres_width = 2, gp1_disp_hres_mask = 0x3,
|
||||
gp1_disp_vres_shift = 2, gp1_disp_vres_width = 1, gp1_disp_vres_mask = 0x1,
|
||||
gp1_disp_color_shift = 4, gp1_disp_color_width = 1, gp1_disp_color_mask = 0x1,
|
||||
gp1_disp_interlace_shift = 5, gp1_disp_interlace_width = 1, gp1_disp_interlace_mask = 0x1,
|
||||
|
||||
/* GP1 horizontal display range: bits 0..11 = X2, bits 12..23 = X1 */
|
||||
gp1_hrange_x1_shift = 12, gp1_hrange_x1_width = 12, gp1_hrange_x1_mask = 0xFFF,
|
||||
gp1_hrange_x2_shift = 0, gp1_hrange_x2_width = 12, gp1_hrange_x2_mask = 0xFFF,
|
||||
/* GP1 horizontal display range: bits 0..11 = X2, bits 12..23 = X1 */
|
||||
gp1_hrange_x1_shift = 12, gp1_hrange_x1_width = 12, gp1_hrange_x1_mask = 0xFFF,
|
||||
gp1_hrange_x2_shift = 0, gp1_hrange_x2_width = 12, gp1_hrange_x2_mask = 0xFFF,
|
||||
|
||||
/* GP1 vertical display range: bits 0..9 = Y2, bits 10..19 = Y1 */
|
||||
gp1_vrange_y1_shift = 10, gp1_vrange_y1_width = 10, gp1_vrange_y1_mask = 0x3FF,
|
||||
gp1_vrange_y2_shift = 0, gp1_vrange_y2_width = 10, gp1_vrange_y2_mask = 0x3FF,
|
||||
/* GP1 vertical display range: bits 0..9 = Y2, bits 10..19 = Y1 */
|
||||
gp1_vrange_y1_shift = 10, gp1_vrange_y1_width = 10, gp1_vrange_y1_mask = 0x3FF,
|
||||
gp1_vrange_y2_shift = 0, gp1_vrange_y2_width = 10, gp1_vrange_y2_mask = 0x3FF,
|
||||
|
||||
/* GP1 draw area (top-left or bottom-right): bits 0..9 = X, bits 10..19 = Y
|
||||
* (10-bit signed — caller pre-signs and masks with the named mask) */
|
||||
gp1_draw_x_shift = 0, gp1_draw_x_width = 10, gp1_draw_x_mask = 0x3FF,
|
||||
gp1_draw_y_shift = 10, gp1_draw_y_width = 10, gp1_draw_y_mask = 0x3FF,
|
||||
/* GP1 draw area (top-left or bottom-right): bits 0..9 = X, bits 10..19 = Y
|
||||
* (10-bit signed — caller pre-signs and masks with the named mask) */
|
||||
gp1_draw_x_shift = 0, gp1_draw_x_width = 10, gp1_draw_x_mask = 0x3FF,
|
||||
gp1_draw_y_shift = 10, gp1_draw_y_width = 10, gp1_draw_y_mask = 0x3FF,
|
||||
};
|
||||
|
||||
/* ---- Layer 1.5: GP1 per-field encoders ---- */
|
||||
#define enc_gp1_disp_hres(h) (((h) & gp1_disp_hres_mask) << gp1_disp_hres_shift)
|
||||
#define enc_gp1_disp_vres(v) (((v) & gp1_disp_vres_mask) << gp1_disp_vres_shift)
|
||||
#define enc_gp1_disp_color(c) (((c) & gp1_disp_color_mask) << gp1_disp_color_shift)
|
||||
#define enc_gp1_disp_interlace(i) (((i) & gp1_disp_interlace_mask << gp1_disp_interlace_shift)
|
||||
#define enc_gp1_disp_hres(h) (((h) & gp1_disp_hres_mask) << gp1_disp_hres_shift)
|
||||
#define enc_gp1_disp_vres(v) (((v) & gp1_disp_vres_mask) << gp1_disp_vres_shift)
|
||||
#define enc_gp1_disp_color(c) (((c) & gp1_disp_color_mask) << gp1_disp_color_shift)
|
||||
#define enc_gp1_disp_interlace(i) (((i) & gp1_disp_interlace_mask) << gp1_disp_interlace_shift)
|
||||
|
||||
#define enc_gp1_hrange_x1(x1) (((x1) & gp1_hrange_x1_mask) << gp1_hrange_x1_shift)
|
||||
#define enc_gp1_hrange_x2(x2) (((x2) & gp1_hrange_x2_mask) << gp1_hrange_x2_shift)
|
||||
@@ -255,6 +255,11 @@ enum {
|
||||
#define enc_gp0_draw_area_br_word(x, y) (enc_gp0_cmd(gp0_cmd_SetDrawArea_BotRight) | enc_gp1_draw_x(x) | enc_gp1_draw_y(y))
|
||||
|
||||
/* ---- Layer 3: GP1 semantic word builders ---- */
|
||||
#define gp1_word_Reset() enc_gp0_cmd_word(gp1_cmd_Reset)
|
||||
#define gp1_word_ResetCmdBuffer() enc_gp0_cmd_word(gp1_cmd_ResetCmdBuffer)
|
||||
#define gp1_word_AcknowledgeIRQ() enc_gp0_cmd_word(gp1_cmd_AcknowledgeIRQ)
|
||||
#define gp1_word_StartDisplayArea() enc_gp0_cmd_word(gp1_cmd_StartDisplayArea)
|
||||
|
||||
#define gp1_word_display_enable(on) (enc_gp0_cmd(gp1_cmd_DisplayEnable) | ((on) & 1))
|
||||
#define gp1_word_display_disable() gp1_word_display_enable(0)
|
||||
#define gp1_word_display_mode_320x240_15bit_ntsc enc_gp1_disp_mode_word(gp1_disp_HRes_320, gp1_disp_VRes_240, gp1_disp_Color15, 0)
|
||||
@@ -279,31 +284,36 @@ enum {
|
||||
#define gp1_word_display_enabled enc_gp0_cmd_word(gp1_cmd_DisplayEnable)
|
||||
#define gp1_word_display_disabled (enc_gp0_cmd_word(gp1_cmd_DisplayEnable) | 1)
|
||||
|
||||
#define gp1_word_DisplayOn() gp1_word_display_enable(0)
|
||||
#define gp1_word_DisplayOff() gp1_word_display_enable(1)
|
||||
|
||||
/* ---- DMA direction (2-bit payload on DMADirection cmd 0x04) ---- */
|
||||
enum {
|
||||
gp1_dma_dir_Off = 0,
|
||||
gp1_dma_dir_FIFO = 1,
|
||||
gp1_dma_dir_CPU_to_GPU = 2,
|
||||
gp1_dma_dir_GPUREAD_to_CPU = 3,
|
||||
gp1_dma_dir_Off = 0,
|
||||
gp1_dma_dir_FIFO = 1,
|
||||
gp1_dma_dir_CPU_to_GPU = 2,
|
||||
gp1_dma_dir_GPUREAD_to_CPU = 3,
|
||||
};
|
||||
#define gp1_word_dma_direction(dir) (enc_gp0_cmd(gp1_cmd_DMADirection) | ((dir) & 0x3))
|
||||
#define gp1_word_dma_to_gpu() gp1_word_dma_direction(gp1_dma_dir_CPU_to_GPU)
|
||||
#define gp1_word_dma_read_cpu() gp1_word_dma_direction(gp1_dma_dir_GPUREAD_to_CPU)
|
||||
|
||||
/* ---- Standard display ranges (NTSC + PAL pre-baked) ---- */
|
||||
/* Horizontal range values are in video clock units (8 units/pixel); vertical range values are scanline numbers. */
|
||||
enum {
|
||||
/* NTSC horizontal range: X1=608, X2=3168 */
|
||||
gp1_hrange_NTSC_x1 = 0x260,
|
||||
gp1_hrange_NTSC_x2 = 0xC60,
|
||||
/* PAL horizontal range (same as NTSC for most CRTs) */
|
||||
gp1_hrange_PAL_x1 = 0x260,
|
||||
gp1_hrange_PAL_x2 = 0xC60,
|
||||
/* NTSC horizontal range: X1=608, X2=3168 */
|
||||
gp1_hrange_NTSC_x1 = 0x260,
|
||||
gp1_hrange_NTSC_x2 = 0xC60,
|
||||
/* PAL horizontal range (same as NTSC for most CRTs) */
|
||||
gp1_hrange_PAL_x1 = 0x260,
|
||||
gp1_hrange_PAL_x2 = 0xC60,
|
||||
|
||||
/* NTSC vertical range: Y1=24, Y2=264 */
|
||||
gp1_vrange_NTSC_y1 = 24,
|
||||
gp1_vrange_NTSC_y2 = 264,
|
||||
/* PAL vertical range: Y1=24, Y2=504 */
|
||||
gp1_vrange_PAL_y1 = 24,
|
||||
gp1_vrange_PAL_y2 = 504,
|
||||
/* NTSC vertical range: Y1=24, Y2=264 */
|
||||
gp1_vrange_NTSC_y1 = 24,
|
||||
gp1_vrange_NTSC_y2 = 264,
|
||||
/* PAL vertical range: Y1=24, Y2=504 */
|
||||
gp1_vrange_PAL_y1 = 24,
|
||||
gp1_vrange_PAL_y2 = 504,
|
||||
};
|
||||
|
||||
#define gp1_word_horizontal_range_ntsc enc_gp1_hrange_word(gp1_hrange_NTSC_x1, gp1_hrange_NTSC_x2)
|
||||
@@ -314,14 +324,49 @@ enum {
|
||||
/* ---- Draw-mode setting (TPage / draw-area allowance) ---- */
|
||||
/* The "drawing enabled" word is the standard post-init state. */
|
||||
enum {
|
||||
gp0_DrawMode_DrawToDispBit = 10,
|
||||
/* Per psx-spx, the standard 0xE1 layout has dfe at bit 10. But libpsyx's PutDrawEnv
|
||||
* uses bit 19 (in the "unused" 14-23 range) for dfe in the DR_ENV code[0] — and the
|
||||
* PSX hardware honors bit 19 in the DR_ENV context (not bit 10). So we need a
|
||||
* separate bit definition for the DR_ENV-specific DrawMode. */
|
||||
gp0_DrawMode_DrawToDispBit = 10, // standard psx-spx bit 10 (dfe)
|
||||
gp0_DrawMode_DR_ENV_DrawToDispBit = 19, // libpsyx DR_ENV code[0] (dfe in DR_ENV context)
|
||||
gp0_DrawMode_DR_ENV_isbgBit = 19, // libpsyx uses bit 19 for isbg too
|
||||
};
|
||||
#define gp0_word_draw_mode_drawing_allowed (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit))
|
||||
|
||||
/* ---- DrawArea pre-baked at origin (0,0) and full screen (320x240) ---- */
|
||||
/* DR_ENV-specific DrawMode variants (libpsyx SetDrawEnv layout).
|
||||
* The DR_ENV is a 16-word packet emitted at boot by gp_screen_init's ac_put_draw_env_demo
|
||||
* atom component. Within the DR_ENV, the 0xE1 command is reused in three different bit
|
||||
* configurations:
|
||||
* code[0] = `gp0_word_draw_mode_drawing_allowed` (dfe=1; standard post-init state)
|
||||
* code[6] = `gp0_word_dr_env_bg_color_cmd(isbg, r, g, b)` (initial-bg-color path)
|
||||
* code[7] = `gp0_word_dr_env_draw_mode(isbg)` (isbg-flag path)
|
||||
* Bits 0-23 of the 0xE1 word are the payload; bits 24-31 are the cmd byte (0xE1). */
|
||||
#define gp0_word_dr_env_bg_color_cmd(isbg, r, g, b) (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit) | ((isbg) ? gp0_dr_env_isbg_bit : 0) | enc_gp0_color_r(r) | enc_gp0_color_g(g) | enc_gp0_color_b(b))
|
||||
#define gp0_word_dr_env_draw_mode(isbg) (enc_gp0_cmd(gp0_cmd_DrawModeSetting) | (1 << gp0_DrawMode_DrawToDispBit) | ((isbg) ? gp0_dr_env_isbg_bit : 0))
|
||||
|
||||
/* State-setter bare-cmd words (no immediate payload; the GPU uses the current state machine already programmed). */
|
||||
#define gp0_word_set_texture_window() enc_gp0_cmd_word(gp0_cmd_SetTextureWindow)
|
||||
#define gp0_word_set_draw_offset() enc_gp0_cmd_word(gp0_cmd_SetDrawOffset)
|
||||
#define gp0_word_set_mask_bit() enc_gp0_cmd_word(gp0_cmd_SetMaskBit)
|
||||
|
||||
/* DR_ENV code[5] Mask (0xE6 cmd + isbg bit). The isbg bit is set so the GPU knows the auto-clear path is active (paired with code[6] + code[7]). */
|
||||
#define gp0_word_dr_env_mask() (gp0_word_set_mask_bit() | gp0_dr_env_isbg_bit)
|
||||
|
||||
/* DR_ENV pre-baked constants (libpsyx PutDrawEnv layout).
|
||||
* DR_ENV is a 16-word packet: tag = (length << 24) | addr, where length = 15 (15 code words follow) and addr = 0 (chain to nothing). */
|
||||
enum {
|
||||
PolyTag_len_bits = 8,
|
||||
PolyTag_addr_bits = 24,
|
||||
|
||||
gp0_dr_env_tag = (15 << 24) | 0x00FFFFFF,
|
||||
gp0_dr_env_isbg_bit = (1 << gp0_DrawMode_DR_ENV_isbgBit),
|
||||
};
|
||||
|
||||
/* ---- DrawArea at origin (0,0) and full screen (320x240) ---- */
|
||||
#define gp0_word_draw_area_top_left_origin enc_gp0_draw_area_tl_word(0, 0)
|
||||
#define gp0_word_draw_area_bottom_right_320x240 enc_gp0_draw_area_br_word(320, 240)
|
||||
#define gp0_word_draw_area_bottom_right_640x480 enc_gp0_draw_area_br_word(640, 480)
|
||||
#define gp0_word_draw_area_bottom_right_320x240 enc_gp0_draw_area_br_word(319, 239)
|
||||
#define gp0_word_draw_area_bottom_right_640x480 enc_gp0_draw_area_br_word(639, 479)
|
||||
|
||||
#pragma endregion GPU Ports & Commands
|
||||
|
||||
@@ -332,9 +377,9 @@ enum {
|
||||
* Read from HW_GP1; the lower bits are DMA-block-size (variable-width).
|
||||
* ============================================================================ */
|
||||
enum {
|
||||
gp1_Status_BitReady = 31,
|
||||
gp1_Status_BitSendingDMA = 25,
|
||||
gp1_Status_DMABlockSizeShift = 0,
|
||||
gp1_Status_BitReady = 31,
|
||||
gp1_Status_BitSendingDMA = 25,
|
||||
gp1_Status_DMABlockSizeShift = 0,
|
||||
};
|
||||
|
||||
#define gp1_status_is_ready() ((HW_GP1[0] >> gp1_Status_BitReady) & 1)
|
||||
@@ -360,10 +405,10 @@ typedef Struct_(RGB8) { B1 r; B1 g; B1 b; };
|
||||
#define rgb8(r,g,b) ((RGB8){r,g,b})
|
||||
|
||||
/* ---------- PolyTag (the OT-link header; 1 word) ---------- */
|
||||
enum {
|
||||
PolyTag_len_bits = 8,
|
||||
PolyTag_addr_bits = 24,
|
||||
};
|
||||
// enum {
|
||||
// PolyTag_len_bits = 8,
|
||||
// PolyTag_addr_bits = 24,
|
||||
// };
|
||||
typedef Struct_(PolyTag) {
|
||||
union {
|
||||
U4 code;
|
||||
@@ -387,95 +432,95 @@ typedef Struct_(PolyTag) {
|
||||
|
||||
/* ---------- Poly_F3 (Flat Triangle; 5 words) ---------- */
|
||||
typedef Struct_(Poly_F3) {
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
union {
|
||||
struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; };
|
||||
A3_V2_S2 points;
|
||||
};
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
union {
|
||||
struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; };
|
||||
A3_V2_S2 points;
|
||||
};
|
||||
};
|
||||
|
||||
/* ---------- Poly_F4 (Flat Quad; 6 words) ---------- */
|
||||
typedef Struct_(Poly_F4) {
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
union {
|
||||
struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; V2_S2 p3; };
|
||||
A4_V2_S2 points;
|
||||
};
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
union {
|
||||
struct { V2_S2 p0; V2_S2 p1; V2_S2 p2; V2_S2 p3; };
|
||||
A4_V2_S2 points;
|
||||
};
|
||||
};
|
||||
|
||||
/* ---------- Poly_G3 (Gouraud Triangle; 7 words) ---------- */
|
||||
typedef Struct_(Poly_G3) {
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2;
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2;
|
||||
};
|
||||
|
||||
/* ---------- Poly_G4 (Gouraud Quad; 9 words) ---------- */
|
||||
typedef Struct_(Poly_G4) {
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2; RGB8 c3; B1 pad3;
|
||||
V2_S2 p3;
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2; RGB8 c3; B1 pad3;
|
||||
V2_S2 p3;
|
||||
};
|
||||
|
||||
/* ---------- Poly_FT3 (Flat Textured Triangle; placeholder layout) ---------- */
|
||||
/* TODO(Ed): verify the textured-variant layout against PSX-SPX when needed. */
|
||||
typedef Struct_(Poly_FT3) {
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 p0; U1 u0; U1 v0;
|
||||
V2_S2 p1; U1 u1; U1 v1;
|
||||
V2_S2 p2; U1 u2; U1 v2;
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 p0; U1 u0; U1 v0;
|
||||
V2_S2 p1; U1 u1; U1 v1;
|
||||
V2_S2 p2; U1 u2; U1 v2;
|
||||
};
|
||||
|
||||
/* ---------- Poly_FT4 (Flat Textured Quad) ---------- */
|
||||
typedef Struct_(Poly_FT4) {
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 p0; U1 u0; U1 v0;
|
||||
V2_S2 p1; U1 u1; U1 v1;
|
||||
V2_S2 p2; U1 u2; U1 v2;
|
||||
V2_S2 p3; U1 u3; U1 v3;
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 p0; U1 u0; U1 v0;
|
||||
V2_S2 p1; U1 u1; U1 v1;
|
||||
V2_S2 p2; U1 u2; U1 v2;
|
||||
V2_S2 p3; U1 u3; U1 v3;
|
||||
};
|
||||
|
||||
/* ---------- Poly_GT3 (Gouraud Textured Triangle) ---------- */
|
||||
typedef Struct_(Poly_GT3) {
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 tp0; U1 u0; U1 v0;
|
||||
V2_S2 tp1; U1 u1; U1 v1;
|
||||
V2_S2 tp2; U1 u2; U1 v2;
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 tp0; U1 u0; U1 v0;
|
||||
V2_S2 tp1; U1 u1; U1 v1;
|
||||
V2_S2 tp2; U1 u2; U1 v2;
|
||||
};
|
||||
|
||||
/* ---------- Poly_GT4 (Gouraud Textured Quad) ---------- */
|
||||
typedef Struct_(Poly_GT4) {
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2; RGB8 c3; B1 pad3;
|
||||
V2_S2 p3;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 tp0; U1 u0; U1 v0;
|
||||
V2_S2 tp1; U1 u1; U1 v1;
|
||||
V2_S2 tp2; U1 u2; U1 v2;
|
||||
V2_S2 tp3; U1 u3; U1 v3;
|
||||
U4 tag; RGB8 c0; B1 code;
|
||||
V2_S2 p0; RGB8 c1; B1 pad1;
|
||||
V2_S2 p1; RGB8 c2; B1 pad2;
|
||||
V2_S2 p2; RGB8 c3; B1 pad3;
|
||||
V2_S2 p3;
|
||||
U4 tpage;
|
||||
U4 clut;
|
||||
V2_S2 tp0; U1 u0; U1 v0;
|
||||
V2_S2 tp1; U1 u1; U1 v1;
|
||||
V2_S2 tp2; U1 u2; U1 v2;
|
||||
V2_S2 tp3; U1 u3; U1 v3;
|
||||
};
|
||||
|
||||
/* ---------- Primitive setters (C-level) ----------
|
||||
@@ -510,26 +555,26 @@ typedef Struct_(Poly_GT4) {
|
||||
* bits 12..31 = reserved (zero)
|
||||
* ============================================================================ */
|
||||
enum {
|
||||
/* ---- Layer 1: TPage bitfield shifts / widths / masks ---- */
|
||||
gp0_tpage_x_shift = 0, gp0_tpage_x_width = 4, gp0_tpage_x_mask = 0xF,
|
||||
gp0_tpage_y_shift = 4, gp0_tpage_y_width = 1, gp0_tpage_y_mask = 0x1,
|
||||
gp0_tpage_semi_trans_shift = 5, gp0_tpage_semi_trans_width = 2, gp0_tpage_semi_trans_mask = 0x3,
|
||||
gp0_tpage_color_depth_shift = 7, gp0_tpage_color_depth_width = 2, gp0_tpage_color_depth_mask = 0x3,
|
||||
gp0_tpage_dither_shift = 9, gp0_tpage_dither_width = 1, gp0_tpage_dither_mask = 0x1,
|
||||
gp0_tpage_draw_to_disp_shift = 10, gp0_tpage_draw_to_disp_width = 1, gp0_tpage_draw_to_disp_mask = 0x1,
|
||||
gp0_tpage_tex_disable_shift = 11, gp0_tpage_tex_disable_width = 1, gp0_tpage_tex_disable_mask = 0x1,
|
||||
/* ---- Layer 1: TPage bitfield shifts / widths / masks ---- */
|
||||
gp0_tpage_x_shift = 0, gp0_tpage_x_width = 4, gp0_tpage_x_mask = 0xF,
|
||||
gp0_tpage_y_shift = 4, gp0_tpage_y_width = 1, gp0_tpage_y_mask = 0x1,
|
||||
gp0_tpage_semi_trans_shift = 5, gp0_tpage_semi_trans_width = 2, gp0_tpage_semi_trans_mask = 0x3,
|
||||
gp0_tpage_color_depth_shift = 7, gp0_tpage_color_depth_width = 2, gp0_tpage_color_depth_mask = 0x3,
|
||||
gp0_tpage_dither_shift = 9, gp0_tpage_dither_width = 1, gp0_tpage_dither_mask = 0x1,
|
||||
gp0_tpage_draw_to_disp_shift = 10, gp0_tpage_draw_to_disp_width = 1, gp0_tpage_draw_to_disp_mask = 0x1,
|
||||
gp0_tpage_tex_disable_shift = 11, gp0_tpage_tex_disable_width = 1, gp0_tpage_tex_disable_mask = 0x1,
|
||||
|
||||
/* TPage color-depth payload values (NOT bit positions — these go in
|
||||
* the 2-bit field at gp0_tpage_color_depth_shift). */
|
||||
gp0_tpage_color_4bpp = 0x0,
|
||||
gp0_tpage_color_8bpp = 0x1,
|
||||
gp0_tpage_color_16bpp = 0x2,
|
||||
/* TPage color-depth payload values (NOT bit positions — these go in
|
||||
* the 2-bit field at gp0_tpage_color_depth_shift). */
|
||||
gp0_tpage_color_4bpp = 0x0,
|
||||
gp0_tpage_color_8bpp = 0x1,
|
||||
gp0_tpage_color_16bpp = 0x2,
|
||||
|
||||
/* TPage semi-transparency mode payload values (NOT bit positions). */
|
||||
gp0_tpage_semi_trans_none = 0x0,
|
||||
gp0_tpage_semi_trans_alpha = 0x1,
|
||||
gp0_tpage_semi_trans_add = 0x2,
|
||||
gp0_tpage_semi_trans_sub = 0x3,
|
||||
/* TPage semi-transparency mode payload values (NOT bit positions). */
|
||||
gp0_tpage_semi_trans_none = 0x0,
|
||||
gp0_tpage_semi_trans_alpha = 0x1,
|
||||
gp0_tpage_semi_trans_add = 0x2,
|
||||
gp0_tpage_semi_trans_sub = 0x3,
|
||||
};
|
||||
|
||||
/* ---- Layer 1.5: TPage per-field encoders. Mirrors enc_gte_sf/mx/v in gte.h. ---- */
|
||||
@@ -543,19 +588,19 @@ enum {
|
||||
|
||||
/* ---- Layer 2: TPage composite encoder. Mirrors enc_gte_cmdw in gte.h ---- */
|
||||
#define enc_gp0_tpage_word(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \
|
||||
(enc_gp0_tpage_x(x) \
|
||||
| enc_gp0_tpage_y(y) \
|
||||
| enc_gp0_tpage_semi_trans(semi_trans) \
|
||||
| enc_gp0_tpage_color_depth(color_depth) \
|
||||
| enc_gp0_tpage_dither(dither) \
|
||||
| enc_gp0_tpage_draw_to_disp(draw_to_disp) \
|
||||
| enc_gp0_tpage_tex_disable(tex_disable))
|
||||
(enc_gp0_tpage_x(x) \
|
||||
| enc_gp0_tpage_y(y) \
|
||||
| enc_gp0_tpage_semi_trans(semi_trans) \
|
||||
| enc_gp0_tpage_color_depth(color_depth) \
|
||||
| enc_gp0_tpage_dither(dither) \
|
||||
| enc_gp0_tpage_draw_to_disp(draw_to_disp) \
|
||||
| enc_gp0_tpage_tex_disable(tex_disable))
|
||||
|
||||
typedef Struct_(TexturePage) { U4 raw; };
|
||||
|
||||
/* ---- Layer 3: TPage semantic word builder ---- */
|
||||
#define gp0_word_tpage(x, y, semi_trans, color_depth, dither, draw_to_disp, tex_disable) \
|
||||
enc_gp0_tpage_word((x), (y), (semi_trans), (color_depth), (dither), (draw_to_disp), (tex_disable))
|
||||
enc_gp0_tpage_word((x), (y), (semi_trans), (color_depth), (dither), (draw_to_disp), (tex_disable))
|
||||
#pragma endregion TPage
|
||||
|
||||
#pragma region CLUT
|
||||
@@ -569,12 +614,12 @@ typedef Struct_(TexturePage) { U4 raw; };
|
||||
* bits 24..31 = command byte — 0x20 (4bpp load) or 0x25 (8bpp load)
|
||||
* ============================================================================ */
|
||||
enum {
|
||||
/* ---- Layer 1: CLUT bitfield shifts / widths / masks ---- */
|
||||
gp0_clut_y_shift = 0, gp0_clut_y_width = 6, gp0_clut_y_mask = 0x3F,
|
||||
gp0_clut_x_shift = 6, gp0_clut_x_width = 9, gp0_clut_x_mask = 0x1FF,
|
||||
/* CLUT-load cmd-byte variants — the upper byte of the GP0 word. */
|
||||
gp0_clut_cmd_Load4bpp = 0x20,
|
||||
gp0_clut_cmd_Load8bpp = 0x25,
|
||||
/* ---- Layer 1: CLUT bitfield shifts / widths / masks ---- */
|
||||
gp0_clut_y_shift = 0, gp0_clut_y_width = 6, gp0_clut_y_mask = 0x3F,
|
||||
gp0_clut_x_shift = 6, gp0_clut_x_width = 9, gp0_clut_x_mask = 0x1FF,
|
||||
/* CLUT-load cmd-byte variants — the upper byte of the GP0 word. */
|
||||
gp0_clut_cmd_Load4bpp = 0x20,
|
||||
gp0_clut_cmd_Load8bpp = 0x25,
|
||||
};
|
||||
|
||||
/* ---- Layer 1.5: CLUT per-field encoders ---- */
|
||||
@@ -615,26 +660,26 @@ enum {
|
||||
* Stoppped for now at the struct + enum level.
|
||||
* ============================================================================ */
|
||||
enum {
|
||||
tim_file_id_magic = 0x10,
|
||||
tim_type_4bpp = 0x00,
|
||||
tim_type_8bpp = 0x01,
|
||||
tim_type_16bpp = 0x02,
|
||||
tim_type_32bpp = 0x03,
|
||||
tim_type_mixed = 0x04,
|
||||
tim_flag_has_clut = 0x08,
|
||||
tim_file_id_magic = 0x10,
|
||||
tim_type_4bpp = 0x00,
|
||||
tim_type_8bpp = 0x01,
|
||||
tim_type_16bpp = 0x02,
|
||||
tim_type_32bpp = 0x03,
|
||||
tim_type_mixed = 0x04,
|
||||
tim_flag_has_clut = 0x08,
|
||||
};
|
||||
|
||||
typedef Struct_(TIM_Header) {
|
||||
U4 file_id; /* always 0x10 = "TIM" magic */
|
||||
U4 version; /* ignored; always 0 */
|
||||
U4 flags; /* bits 0..2 = type, bit 3 = has_clut */
|
||||
U4 file_id; /* always 0x10 = "TIM" magic */
|
||||
U4 version; /* ignored; always 0 */
|
||||
U4 flags; /* bits 0..2 = type, bit 3 = has_clut */
|
||||
};
|
||||
typedef Struct_(TIM_SectionHeader) {
|
||||
U4 section_length; /* bytes in this section including this header */
|
||||
U2 org_x; /* origin in VRAM */
|
||||
U2 org_y;
|
||||
U2 width; /* width in pixels */
|
||||
U2 height; /* height in pixels */
|
||||
U4 section_length; /* bytes in this section including this header */
|
||||
U2 org_x; /* origin in VRAM */
|
||||
U2 org_y;
|
||||
U2 width; /* width in pixels */
|
||||
U2 height; /* height in pixels */
|
||||
};
|
||||
#pragma endregion TIM File Format
|
||||
|
||||
|
||||
+82
-126
@@ -17,9 +17,8 @@
|
||||
* gte_lw_v0_xy(base) (gte + lw + v0 + xy)
|
||||
* load_upper_i (load-upper + immediate, unique verb)
|
||||
*
|
||||
* Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are
|
||||
* NOT in this header. They live in the opt-in `gte_vendor_sym.h` for
|
||||
* users who prefer the textbook MIPS assembly mnemonics.
|
||||
* Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are NOT in this header.
|
||||
* They are in the opt-in `gte_vendor_sym.h` for users who prefer the textbook MIPS assembly mnemonics.
|
||||
* ============================================================================ */
|
||||
|
||||
#ifdef INTELLISENSE_DIRECTIVES
|
||||
@@ -34,20 +33,16 @@
|
||||
* gte.h — Geometry Transformation Engine (COP2) for the PS1
|
||||
* ============================================================================
|
||||
*
|
||||
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word`
|
||||
* constants from C. No GCC inline-assembly string syntax in the code body.
|
||||
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word` constants from C.
|
||||
* No GCC inline-assembly string syntax in the code body.
|
||||
*
|
||||
* STYLE NOTES
|
||||
* -----------
|
||||
* - Per-field encoders are named `enc_gte_<field>(value)` and each one
|
||||
* self-masks its argument before shifting. Mirrors the `enc_op / enc_rs
|
||||
* / enc_rt / ...` family in mips.h.
|
||||
* - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of
|
||||
* the per-field encoders, plus the COP2/CO base.
|
||||
* - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined
|
||||
* for the common cases so call sites read like assembly source.
|
||||
* - All register/field values are enums (not `#define`s) so they show up
|
||||
* in debugger symbol tables and IDE autocomplete.
|
||||
* - Per-field encoders are named `enc_gte_<field>(value)` and each one self-masks its argument before shifting.
|
||||
* Mirrors the `enc_op / enc_rs / enc_rt / ...` family in mips.h.
|
||||
* - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of the per-field encoders, plus the COP2/CO base.
|
||||
* - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined for the common cases so call sites read like assembly source.
|
||||
* - All register/field values are enums (not `#define`s) so they show up in debugger symbol tables and IDE autocomplete.
|
||||
*
|
||||
* SEE ALSO
|
||||
* --------
|
||||
@@ -58,8 +53,7 @@
|
||||
|
||||
/* --- GTE Data Registers (Coprocessor 2) ---
|
||||
* Preprocessor-visible integer ids for the COP2 data register file.
|
||||
* Each enum value is bound to a parallel `_Code` `#define` so the
|
||||
* preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
||||
* Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
||||
* Same pattern as the GPR `_Code` set in mips.h. */
|
||||
#define C2_VXY0_Code 0
|
||||
#define C2_VZ0_Code 1
|
||||
@@ -192,10 +186,8 @@ enum {
|
||||
|
||||
/* --- GTE Control Register Indices (for ctc2/cfc2) ---
|
||||
* Preprocessor-visible integer ids for the COP2 control register file.
|
||||
* Each enum value is bound to a parallel `_Code` `#define` so the
|
||||
* preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
||||
* Same pattern as the GPR `_Code` set in mips.h. Note: indices 21-23
|
||||
* are reserved/unused on real hardware, so there's a gap. */
|
||||
* Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
||||
* Same pattern as the GPR `_Code` set in mips.h. Note: indices 21-23 are reserved/unused on real hardware, so there's a gap. */
|
||||
#define gte_cr_RT11_Code 0
|
||||
#define gte_cr_RT12_Code 1 /* packed with RT13 in bits 16..31 */
|
||||
#define gte_cr_RT13_Code 2 /* packed with RT22 in bits 16..31 */
|
||||
@@ -223,8 +215,9 @@ enum {
|
||||
#define gte_cr_RFC_Code 27
|
||||
#define gte_cr_GFC_Code 28
|
||||
#define gte_cr_BFC_Code 29
|
||||
#define gte_cr_OFX_Code 30
|
||||
#define gte_cr_OFY_Code 31
|
||||
#define gte_cr_OFX_Code 24
|
||||
#define gte_cr_OFY_Code 25
|
||||
#define gte_cr_H_Code 26
|
||||
|
||||
enum {
|
||||
gte_cr_RT11 = gte_cr_RT11_Code, gte_cr_RT12 = gte_cr_RT12_Code, gte_cr_RT13 = gte_cr_RT13_Code,
|
||||
@@ -246,21 +239,16 @@ enum { _C2_OPS_ = 0
|
||||
|
||||
/* COP2 transfer sub-opcodes (5-bit field in the `rs` slot of enc_gte_tx).
|
||||
*
|
||||
* Spans the 2x2 {From, To} × {Data, Control} register classes that the
|
||||
* GTE exposes:
|
||||
*
|
||||
* Spans the 2x2 {From, To} × {Data, Control} register classes that the GTE exposes:
|
||||
* bit 1 (0x02): register class — 0 = data, 1 = control
|
||||
* bit 2 (0x04): direction — 0 = read, 1 = write
|
||||
*
|
||||
* The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit
|
||||
* numbers as the general MIPS `cop_mf` / `cop_mt` defined in mips.h
|
||||
* (which target the data register file on any coprocessor). They are
|
||||
* re-aliased here so the four-way table reads like the spec mnemonics
|
||||
* (MFC2 / CFC2 / MTC2 / CTC2) and so the encoding lives next to its
|
||||
* only consumer (this header).
|
||||
* The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit numbers as the general MIPS `cop_mf` / `cop_mt` defined in mips.h
|
||||
* (which target the data register file on any coprocessor).
|
||||
* They are re-aliased here so the four-way table reads like the spec mnemonics (MFC2 / CFC2 / MTC2 / CTC2)
|
||||
* and so the encoding lives next to its only consumer (this header).
|
||||
*
|
||||
* Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2)
|
||||
* live in gte_vendor_sym.h. */
|
||||
* Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2) live in gte_vendor_sym.h. */
|
||||
enum { _C2_TX_SUBS_ = 0
|
||||
, sub_mfc2 = 0x00 /* MFC2: Move From Coprocessor 2 data reg */
|
||||
, sub_cfc2 = 0x02 /* CFC2: Copy From Coprocessor 2 ctrl reg */
|
||||
@@ -270,11 +258,11 @@ enum { _C2_TX_SUBS_ = 0
|
||||
|
||||
/* COP2 (GTE) Transfer Format: mfc2 / cfc2 / mtc2 / ctc2 rt, rd
|
||||
* Layout: [op_cop2:6][sub:5][rt:5][rd:5][0:11]
|
||||
* - sub: one of sub_mfc2 / sub_cfc2 / sub_mtc2 / sub_ctc2
|
||||
* - rt: GPR source/dest
|
||||
* - rd: COP2 register index (0..31):
|
||||
* data class → C2_VXY0_Code..C2_LZCR_Code (gte_in_v0_xy..gte_math_accum2 aliases)
|
||||
* ctrl class → gte_cr_RT11_Code..gte_cr_OFY_Code */
|
||||
* - sub: one of sub_mfc2 / sub_cfc2 / sub_mtc2 / sub_ctc2
|
||||
* - rt: GPR source/dest
|
||||
* - rd: COP2 register index (0..31):
|
||||
* data class → C2_VXY0_Code..C2_LZCR_Code (gte_in_v0_xy..gte_math_accum2 aliases)
|
||||
* ctrl class → gte_cr_RT11_Code..gte_cr_OFY_Code */
|
||||
#define enc_gte_tx(sub, rt, rd) (enc_op(op_cop2) | enc_rs(sub) | enc_rt(rt) | enc_rd(rd))
|
||||
|
||||
|
||||
@@ -314,8 +302,8 @@ enum { _C2_TX_SUBS_ = 0
|
||||
* `swc2` is redundant when we're already inside the `gte_` namespace.
|
||||
* gte_lw rt, base, off → lwc2 rt, off(base)
|
||||
* gte_sw rt, base, off → swc2 rt, off(base)
|
||||
* For the typical user-facing vector-level load (xy + z as two
|
||||
* instructions), use the higher-level `gte_load_vN` macros below. */
|
||||
* For the typical user-facing vector-level load (xy + z as two instructions),
|
||||
* use the higher-level `gte_load_vN` macros below. */
|
||||
#define gte_lw(rt, base, off) enc_gte_lw(rt, base, off)
|
||||
#define gte_sw(rt, base, off) enc_gte_sw(rt, base, off)
|
||||
|
||||
@@ -323,13 +311,12 @@ enum { _C2_TX_SUBS_ = 0
|
||||
* Opcode is always MIPS_OP_COP2, RS is always 1 (CO).
|
||||
* The lower 25 bits are the GTE-specific command payload.
|
||||
*
|
||||
* The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs`
|
||||
* pattern in mips.h: each one self-masks and shifts its own field, so a
|
||||
* caller can build up a GTE command piece by piece (handy for state-driven
|
||||
* MVMVA emitters that vary one field at a time).
|
||||
* The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs` pattern in mips.h:
|
||||
* Each one self-masks and shifts its own field, so a caller can build up a GTE command piece by piece
|
||||
* (handy for state-driven MVMVA emitters that vary one field at a time).
|
||||
*
|
||||
* `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command
|
||||
* word in one go. It just ORs the per-field encoders together. */
|
||||
* `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command word in one go.
|
||||
* It just ORs the per-field encoders together. */
|
||||
#define gte_cmd_base (enc_op(op_cop2) | (1 << 25))
|
||||
|
||||
/* Per-field encoders. Each one does (value & mask) << shift on its own. */
|
||||
@@ -359,12 +346,13 @@ enum { _C2_TX_SUBS_ = 0
|
||||
* Decomposition (per the `enc_gte_<field>` definitions above):
|
||||
* gte_cmdw_<name> = gte_cmd_base | enc_gte_cmd(<cmd>)
|
||||
|
||||
* The SF/MX/V/CV/LM fields are all zero in the common cases
|
||||
* The SF / MX / V / CV / LM fields are all zero in the common cases
|
||||
* (standard rotation-matrix, no scaling factor, V0 vector, translation vector, no clamp),
|
||||
* so the only varying bits are the `cmd` field.
|
||||
*
|
||||
* Naming follows the file's convention: `gte_cmd_*` is the raw 6-bit `cmd` field id, `gte_cmdw_*`
|
||||
* is the fully-encoded 32-bit instruction word ready to drop into a `.word` directive.
|
||||
* Naming convention:
|
||||
* - `gte_cmd_*` : Raw 6-bit `cmd` field id
|
||||
* - `gte_cmdw_* : 32-bit instruction word ready to drop into a `.word` directive.
|
||||
*
|
||||
* --------------------------------------------------------------------------
|
||||
* PsyQ-compatibility note (RTPS/RTPT):
|
||||
@@ -380,7 +368,7 @@ enum { _C2_TX_SUBS_ = 0
|
||||
* `nclip` ends up wrong, and the triangle is culled.
|
||||
*
|
||||
* So for RTPS and RTPT we OR-in the `0x28` "PsyQ compat" pattern to match the working bit pattern everyone has shipped for 25 years.
|
||||
* NCLIP/OP/MVMVA stay spec-clean — their reserved bits really are zero in the original PsyQ source.
|
||||
* NCLIP / OP / MVMVA stay spec-clean — their reserved bits really are zero in the original PsyQ source.
|
||||
* --------------------------------------------------------------------------
|
||||
*/
|
||||
#define gte_cmdw_psyq_compat (1u << 21 | enc_gte_sf(gte_sf_integer))
|
||||
@@ -413,20 +401,16 @@ enum { _C2_TX_SUBS_ = 0
|
||||
|
||||
/**
|
||||
* @brief Loads a single SVECTOR to GTE vector register V0
|
||||
*
|
||||
* @details Loads values from an SVECTOR struct to GTE data registers C2_VXY0
|
||||
* (XY at offset 0) and C2_VZ0 (Z at offset 4) using `lwc2`.
|
||||
*
|
||||
* Uses string-style GCC inline asm with `%0` substitution because the
|
||||
* base register `r0` is a runtime GPR chosen by the compiler.
|
||||
* Uses string-style GCC inline asm with `%0` substitution because the base register `r0` is a runtime GPR chosen by the compiler.
|
||||
* It cannot be encoded into a static `.word` constant.
|
||||
*
|
||||
* Usage:
|
||||
* asm_gte_load_v0(svector_ptr);
|
||||
* Usage: asm_gte_load_v0(svector_ptr);
|
||||
*/
|
||||
|
||||
/* lwc2 encoding helpers parameterized on the base GPR.
|
||||
*
|
||||
* gte_lw_v0_xy(base) → lwc2 $0, 0(base) ; C2_VXY0
|
||||
* gte_lw_v0_z(base) → lwc2 $1, 4(base) ; C2_VZ0
|
||||
* gte_lw_v1_xy(base) → lwc2 $2, 0(base) ; C2_VXY1
|
||||
@@ -435,8 +419,7 @@ enum { _C2_TX_SUBS_ = 0
|
||||
* gte_lw_v2_z(base) → lwc2 $5, 4(base) ; C2_VZ2
|
||||
*
|
||||
* `base` is the GPR number to bake into the .word constant's `rs` field.
|
||||
* These are pure compile-time integers; the C compiler constant-folds
|
||||
* them into .word directives. */
|
||||
* These are pure compile-time integers; the C compiler constant-folds them into .word directives. */
|
||||
|
||||
enum {
|
||||
GTE_Z_Offset = 4
|
||||
@@ -459,8 +442,8 @@ enum {
|
||||
* gte_load_v0(p_in_12, R_T4); // R_T4 = 12, base is $12
|
||||
*
|
||||
* Then `"r"(r_ptr)` inside the asm binds to $12 (the only register `p_in_12` can live in),
|
||||
* which is exactly the register the .word constants expect. A `"$12"` clobber would conflict with the register-variable binding
|
||||
* ("asm specifier for variable conflicts with asm clobber list"), so we omit it.
|
||||
* which is exactly the register the .word constants expect.
|
||||
* A `"$12"` clobber would conflict with the register-variable binding ("asm specifier for variable conflicts with asm clobber list"), so we omit it.
|
||||
* The other ABI-clobbers ($2/$8/$9/$31) stay because the GTE instructions don't touch caller-saved GPRs but the kernel does treat them as volatile.
|
||||
*
|
||||
* WHICH REGISTER TO PICK
|
||||
@@ -499,10 +482,8 @@ enum {
|
||||
|
||||
/* gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) — prelude to gte_cmd_rtpt.
|
||||
*
|
||||
* Loads all three GTE input vectors (6 words) from three separate pointers,
|
||||
* one per GTE vector register, each loaded from its own base GPR.
|
||||
* Caller must bind each `pN` to `bN` via a register variable.
|
||||
*
|
||||
* Loads all three GTE input vectors (6 words) from three separate pointers, one per GTE vector register,
|
||||
* each loaded from its own base GPR. Caller must bind each `pN` to `bN` via a register variable.
|
||||
* register V3_S2* p0 rgcc(R_T4) = verts[0].ptr; // → __asm__("$12")
|
||||
* register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13")
|
||||
* register V3_S2* p2 rgcc(R_T6) = verts[2].ptr; // → __asm__("$14")
|
||||
@@ -521,29 +502,20 @@ enum {
|
||||
|
||||
/**
|
||||
* @brief Rotate, Translate and Perspective Triple (23 cycles)
|
||||
*
|
||||
* @details Performs rotation, translation and perspective calculation of three
|
||||
* vertices at once. The equation performed is the same as gte_rtps() only
|
||||
* repeated three times for each vertex. The result of the first vertex is
|
||||
* stored in GTE data register C2_SXY0, the second vector in C2_SXY1 then
|
||||
* C2_SXY2.
|
||||
* @details Performs rotation, translation and perspective calculation of three vertices at once.
|
||||
* The equation performed is the same as gte_rtps() only repeated three times for each vertex.
|
||||
* The result of the first vertex is stored in GTE data register C2_SXY0, the second vector in C2_SXY1 then C2_SXY2.
|
||||
*
|
||||
* Encoder-style emission (no inline-asm strings in the code body):
|
||||
* 1. Two `nop` words fill the COP2 pipeline latency — the GTE
|
||||
* takes ~8 cycles per perspective divide, and the nops let any
|
||||
* preceding lwc2/swc2 retire before RTPT starts reading its
|
||||
* inputs from V0/V1/V2.
|
||||
* 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the
|
||||
* pre-baked encoders above) — `0x0280030` decoded as
|
||||
* `op_cop2` | CO(1) | cmd=RTPT, with all SF/MX/V/CV/LM fields
|
||||
* zero (standard rotation, no scaling, V0 vector, translation
|
||||
* vector, no clamp).
|
||||
* 1. Two `nop` words fill the COP2 pipeline latency — the GTE takes ~8 cycles per perspective divide,
|
||||
* and the nops let any preceding lwc2/swc2 retire before RTPT starts reading its inputs from V0/V1/V2.
|
||||
* 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the pre-baked encoders above) —
|
||||
* `0x0280030` decoded as `op_cop2` | CO(1) | cmd=RTPT, with all SF/MX/V/CV/LM fields zero
|
||||
* (standard rotation, no scaling, V0 vector, translation vector, no clamp).
|
||||
*
|
||||
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel
|
||||
* ABI) plus the standard "memory" barrier. Does not clobber any COP2
|
||||
* data/control register — those have to be saved by the caller if
|
||||
* they need to survive across the call (RTPT writes SXY0..2, SZ0..3,
|
||||
* OTZ, MAC0..3, IR0..3, etc.).
|
||||
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel ABI)
|
||||
* plus the standard "memory" barrier. Does not clobber any COP2 data/control register —
|
||||
* those have to be saved by the caller if they need to survive across the call (RTPT writes SXY0..2, SZ0..3, OTZ, MAC0..3, IR0..3, etc.).
|
||||
*/
|
||||
#define gte_rtpt() \
|
||||
asm volatile( \
|
||||
@@ -559,32 +531,24 @@ enum {
|
||||
|
||||
/**
|
||||
* @brief Normal clipping (8 cycles)
|
||||
*
|
||||
* @details Computes the sign of three screen coordinates (C2_SXY0-2) used for
|
||||
* backface culling. If the value of C2_MAC0 is negative, the coordinates are
|
||||
* inverted and thus the triangle is back facing.
|
||||
* @details Computes the sign of three screen coordinates (C2_SXY0-2) used for backface culling.
|
||||
* If the value of C2_MAC0 is negative, the coordinates are inverted and thus the triangle is back facing.
|
||||
*
|
||||
* The following equation is performed when executing this GTE command:
|
||||
*
|
||||
* MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1
|
||||
*
|
||||
* Encoder-style emission (no inline-asm strings in the code body):
|
||||
* 1. Two `nop` words fill the COP2 pipeline latency - the GTE
|
||||
* pipeline takes a few cycles per op, and the nops let any
|
||||
* preceding lwc2/swc2/RTPT retire before NCLIP starts reading
|
||||
* its inputs from SXY0/SXY1/SXY2.
|
||||
* 2. The NCLIP command word itself is `gte_cmdw_nclip` (see the
|
||||
* pre-baked encoders above) - `0x01400006` decoded as
|
||||
* `op_cop2` | CO(1) | cmd=NCLIP, with all SF/MX/V/CV/LM fields
|
||||
* zero. NCLIP is spec-clean in the original PsyQ source
|
||||
* (unlike RTPS/RTPT which carry the `gte_cmdw_psyq_compat`
|
||||
* quirk), so `gte_cmdw_nclip` does NOT OR in any reserved bits.
|
||||
* 1. Two `nop` words fill the COP2 pipeline latency
|
||||
* - the GTE pipeline takes a few cycles per op, and the nops let any preceding
|
||||
* lwc2/swc2/RTPT retire before NCLIP starts reading its inputs from SXY0/SXY1/SXY2.
|
||||
* 2. The NCLIP command word itself is `gte_cmdw_nclip` (see the pre-baked encoders above)
|
||||
* - `0x01400006` decoded as `op_cop2` | CO(1) | cmd=NCLIP, with all SF/MX/V/CV/LM fields zero.
|
||||
* NCLIP is spec-clean in the original PsyQ source (unlike RTPS/RTPT which carry the `gte_cmdw_psyq_compat` quirk),
|
||||
* so `gte_cmdw_nclip` does NOT OR in any reserved bits.
|
||||
*
|
||||
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel
|
||||
* ABI) plus the standard "memory" barrier. Does not clobber any COP2
|
||||
* data/control register - those have to be saved by the caller if
|
||||
* they need to survive across the call (NCLIP writes MAC0 only; it
|
||||
* is purely a sign-of-double-product computation on SXY0..2).
|
||||
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel ABI) plus the standard "memory" barrier.
|
||||
* Does not clobber any COP2 data/control register.
|
||||
* Those have to be saved by the caller if they need to survive across the call (NCLIP writes MAC0 only;
|
||||
* it is purely a sign-of-double-product computation on SXY0..2).
|
||||
*/
|
||||
#define gte_nclip() \
|
||||
asm volatile( \
|
||||
@@ -610,13 +574,10 @@ enum {
|
||||
"cop2 0x0158002D;")
|
||||
|
||||
/* asm_gte_matrix_set_rotation(r0)
|
||||
* Loads the 3x3 rotation matrix at `r0` into the GTE's rotation-matrix control registers (RT11..RT22, indices 0..4) via ctc2.
|
||||
*
|
||||
* Loads the 3x3 rotation matrix at `r0` into the GTE's rotation-matrix
|
||||
* control registers (RT11..RT22, indices 0..4) via ctc2.
|
||||
*
|
||||
* Memory layout at r0: five contiguous 32-bit words (offsets 0..16),
|
||||
* each holding two packed 16-bit matrix elements. The first 1.5 rows
|
||||
* of a standard PSX SDK MATRIX struct (where each row is laid out as
|
||||
* Memory layout at r0: five contiguous 32-bit words (offsets 0..16), each holding two packed 16-bit matrix elements.
|
||||
* The first 1.5 rows of a standard PSX SDK MATRIX struct (where each row is laid out as
|
||||
* [RT_xx, RT_xy] | [RT_xz, pad] | ...).
|
||||
*
|
||||
* Generated MIPS (mirrors the source macro):
|
||||
@@ -631,27 +592,22 @@ enum {
|
||||
* ctc2 $13, $3 ; → C2_RT21
|
||||
* ctc2 $14, $4 ; → C2_RT22
|
||||
*
|
||||
* Same contract as gte_load_v0: caller MUST bind `r0` to $12 via a
|
||||
* register variable (`rgcc(R_T4)`) for the `lw $12, off(...)`
|
||||
* instructions to read from the right base. The `"r"(r0)` constraint
|
||||
* alone doesn't force a specific GPR — it just lets GCC pick one.
|
||||
* The .word constants here bake R_T4/R_T5/R_T6 into the `rs` field
|
||||
* of each lw, so the lw instructions will only do the right thing
|
||||
* if $12/$13/$14 hold the matrix base at runtime.
|
||||
* Same contract as gte_load_v0: caller MUST bind `r0` to $12 via a register variable (`rgcc(R_T4)`) for the `lw $12, off(...)`
|
||||
* instructions to read from the right base. The `"r"(r0)` constraint alone doesn't force a specific GPR — it just lets GCC pick one.
|
||||
* The .word constants here bake R_T4/R_T5/R_T6 into the `rs` field of each lw, so the lw instructions will
|
||||
* only do the right thing if $12 / $13 / $14 hold the matrix base at runtime.
|
||||
*
|
||||
* M3_S2* m = ...;
|
||||
* register M3_S2* m_in_12 rgcc(R_T4) = m;
|
||||
* asm_gte_matrix_set_rotation(m_in_12);
|
||||
*
|
||||
* We clobber $12/$13/$14 (the ones we use as scratch inside the
|
||||
* inline asm) plus the system clobbers; we don't clobber `r0` because
|
||||
* the `rgcc` binding already says "this variable lives in $12".
|
||||
* We clobber $12/$13/$14 (the ones we use as scratch inside the inline asm)
|
||||
* plus the system clobbers; we don't clobber `r0` because the `rgcc` binding already says "this variable lives in $12".
|
||||
*
|
||||
* WARNING: Incomplete by design. The source macro only writes RT11..RT22
|
||||
* (5 of 9 rotation elements); RT23 and the entire RT3x row are left
|
||||
* untouched. Real libpsn00b SetRotMatrix writes all 9. Use only when the
|
||||
* GTE's remaining rotation entries are already correct, or you will
|
||||
* get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output.
|
||||
* WARNING: Incomplete by design. The source macro only writes RT11..RT22 (5 of 9 rotation elements);
|
||||
* RT23 and the entire RT3x row are left untouched.
|
||||
* Real libpsn00b SetRotMatrix writes all 9. Use only when the GTE's remaining rotation entries are already correct,
|
||||
* or you will get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output.
|
||||
*/
|
||||
#define asm_gte_matrix_set_rotation(r0) \
|
||||
asm volatile( \
|
||||
|
||||
+15
-16
@@ -32,23 +32,12 @@ typedef Slice_MipsCode MipsAtom;
|
||||
|
||||
/* Register aliases */
|
||||
enum {
|
||||
R_AtomJmp = R_T9 atom_reg, /* debug-visible; tape yield handshake scratch */
|
||||
R_TapePtr = R_T8 atom_reg, /* The Instruction Stream Pointer */
|
||||
R_InCursor = R_T4,
|
||||
|
||||
R_PrimCursor = R_T7 atom_reg atom_type(U4 *), /* VRAM output cursor (primitive buffer) */
|
||||
R_FaceCursor = R_T4 atom_reg atom_type(V4_S2 *), /* Cube face-index cursor (V4_S2*); floor context switches to V3_S2* via atom_phase */
|
||||
R_VertBase = R_T5 atom_reg atom_type(V3_S2 *), /* Base address of the vertex array */
|
||||
R_OtBase = R_T6 atom_reg atom_type(U4 *), /* Base address of the Ordering Table */
|
||||
|
||||
R_AtomJmp = R_T9 atom_reg, /* debug-visible; tape yield handshake scratch */
|
||||
R_TapePtr = R_T8 atom_reg, /* The Instruction Stream Pointer */
|
||||
R_InCursor = R_T4,
|
||||
/* Stringification codes for the GCC inline assembler clobber lists. */
|
||||
#define R_TapePtr_Code R_T8_Code
|
||||
#define R_InCursor_Code R_T4_Code
|
||||
|
||||
#define R_PrimCursor_Code R_T7_Code
|
||||
#define R_FaceCursor_Code R_T4_Code
|
||||
#define R_VertBase_Code R_T5_Code
|
||||
#define R_OtBase_Code R_T6_Code
|
||||
};
|
||||
|
||||
#pragma region Tape Drive
|
||||
@@ -76,8 +65,7 @@ FI_ void tape_run(Slice_MipsCode tape) { register U4* tp rgcc(R_TapePtr) = u4_r(
|
||||
rlit(R_AT)
|
||||
, rlit(R_V0), rlit(R_V1)
|
||||
, rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3)
|
||||
/* Tell GCC the tape engine owns and destroys the workspace registers */
|
||||
, rlit(R_PrimCursor), rlit(R_FaceCursor), rlit(R_VertBase), rlit(R_OtBase)
|
||||
, rlit(R_T4), rlit(R_T5), rlit(R_T6), rlit(R_T7)
|
||||
, rlit(R_T9)
|
||||
, clb_mem_drain
|
||||
); }
|
||||
@@ -113,6 +101,17 @@ atom_dbg_skip MipsAtomComp_(ac_yield) {
|
||||
jump_reg( R_AtomJmp), nop,
|
||||
};
|
||||
|
||||
enum {
|
||||
R_PrimCursor = R_T7 atom_reg atom_type(U4*), /* VRAM output cursor (primitive buffer) */
|
||||
R_FaceCursor = R_T4 atom_reg atom_type(V4_S2*), /* Cube face-index cursor (V4_S2*); floor context switches to V3_S2* via atom_phase */
|
||||
R_VertBase = R_T5 atom_reg atom_type(V3_S2*), /* Base address of the vertex array */
|
||||
R_OtBase = R_T6 atom_reg atom_type(U4*), /* Base address of the Ordering Table */
|
||||
#define R_PrimCursor_Code R_T7_Code
|
||||
#define R_FaceCursor_Code R_T4_Code
|
||||
#define R_VertBase_Code R_T5_Code
|
||||
#define R_OtBase_Code R_T6_Code
|
||||
};
|
||||
|
||||
/* Words: 3; Loads 3 S2 indices from the face array */
|
||||
atom_dbg_skip MipsAtomComp_(ac_load_tri_indices) {
|
||||
load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)),
|
||||
|
||||
+2
-5
@@ -444,18 +444,15 @@ enum { _BitOffsets = 0
|
||||
#define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm))
|
||||
|
||||
/* load_imm_2w — unconditional 2-word `li` form: `lui` + (ori | addi).
|
||||
*
|
||||
* Granular companion to `load_imm`: skips the compile-time range checks and always emits 2 .words. Use this when:
|
||||
* - you know `imm` is > 0xFFFF (otherwise you're wasting a word), OR
|
||||
* - `imm` is not a compile-time constant and you want predictable
|
||||
* 2-word emission without the `__builtin_constant_p` branches.
|
||||
* - `imm` is not a compile-time constant and you want predictable 2-word emission without the `__builtin_constant_p` branches.
|
||||
*
|
||||
* The lo16 strategy is still chosen at expansion time on the lo half:
|
||||
* lo16 in 0x0000..0x7FFF → addi (sign-ext is harmless, the lui already cleared bits 15..0)
|
||||
* lo16 in 0x8000..0xFFFF → ori (zero-extends to preserve the intended bit pattern)
|
||||
*
|
||||
* For situations where you need to bypass even this choice
|
||||
* (e.g. to force a specific encoding for a known discontiguous high/low pair),
|
||||
* For situations where you need to bypass even this choice (e.g. to force a specific encoding for a known discontiguous high/low pair),
|
||||
* see `load_imm_2w_ori_forced` and `load_imm_2w_addi_forced` below.
|
||||
* Statement-level (not expression-level): emits its own `asm volatile(...)`.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifdef INTELLISENSE_DIRECTIVES
|
||||
#pragma once
|
||||
#endif
|
||||
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
||||
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
||||
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
||||
|
||||
#ifndef WORD_COUNT
|
||||
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
||||
#endif
|
||||
|
||||
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
|
||||
load_upper_i(reg_transfer, cmd >> 16) \
|
||||
, or_i_self( reg_transfer, cmd & 0xFFFF) \
|
||||
, store_word( reg_transfer, reg_base, port)
|
||||
WORD_COUNT(mac_gcmd_push, 3)
|
||||
|
||||
#define mac_put_disp_env(reg_transfer, reg_base, port) \
|
||||
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_set_mask_bit(), reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port)
|
||||
WORD_COUNT(mac_put_disp_env, 15)
|
||||
|
||||
#define mac_put_draw_env(reg_transfer, reg_base, port) \
|
||||
mac_gcmd_push(gp0_dr_env_tag, reg_transfer, reg_base, port) /* tag (length=15 << 24, addr=0) — packet header for the DR_ENV sequence. The GPU needs this to recognize the next 15 words as a DR_ENV packet and trigger the isbg auto-clear. */ \
|
||||
, mac_gcmd_push(gp0_word_draw_mode_drawing_allowed, reg_transfer, reg_base, port) /* code[0] DrawMode (dfe=1, dtd=0, tpage=0) */ \
|
||||
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) /* code[1] TextureWindow (tw=(0,0)) */ \
|
||||
, mac_gcmd_push(enc_gp0_draw_area_tl_word(0, ScreenRes_Y), reg_transfer, reg_base, port) /* code[2] DrawArea top-left (clip.x=0, clip.y=ScreenRes_Y=240) */ \
|
||||
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port) /* code[3] DrawArea bottom-right (clip.x+w=320, clip.y+h=480) */ \
|
||||
, mac_gcmd_push(gp0_word_set_draw_offset(), reg_transfer, reg_base, port) /* code[4] DrawOffset (ofs=(0,0)) — bare-cmd word; the GPU uses the current state machine. */ \
|
||||
, mac_gcmd_push(gp0_word_dr_env_mask(), reg_transfer, reg_base, port) /* code[5] Mask (dtd=0, dfe=1, isbg=1) — 0xE6 cmd + isbg bit. */ \
|
||||
, mac_gcmd_push(gp0_word_dr_env_bg_color_cmd(1, 7, 7, 7), reg_transfer, reg_base, port) /* code[6] Initial-bg-color + auto-clear (isbg=1, r=7, g=7, b=7). */ \
|
||||
, mac_gcmd_push(gp0_word_dr_env_draw_mode(1), reg_transfer, reg_base, port) /* code[7] Re-assert DrawMode with isbg=1 (isbg-flag set; the 0xE1 cmd byte plus isbg only). */ /* code[8..10] Padding (NOP — GPU discards; the DR_ENV requires 16 words total). */ \
|
||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) /* code[11..12] TextureWindow bottom-right (tw.x+tw.w=0, tw.y+tw.h=0) — libpsyx emits twice. */ \
|
||||
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) /* code[13..14] Padding (NOP) — completes the 16-word packet. */ \
|
||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
|
||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port)
|
||||
WORD_COUNT(mac_put_draw_env, 48)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "duffle/lottes_tape.h"
|
||||
#include "duffle/word_count.metadata.h"
|
||||
|
||||
# include "gen/hello_joypad.macs.h"
|
||||
# include "gen/hello_joypad.offsets.h"
|
||||
#include "hello_joypad.h"
|
||||
|
||||
@@ -171,10 +172,6 @@ void gp_screen_init_c11(DoubleBuffer* screen_buf, S4* active_buf_id)
|
||||
|
||||
// Initialize and setup the GTE geometry offsets
|
||||
geom_init();
|
||||
// NOTE: geom_set_offset/geom_set_screen are kept as-is (the libgte versions
|
||||
// are known to be broken in this PSYQ 4.7 build — see report 2026-07-09).
|
||||
// The user's research wants the C-side non-tape reference to work as a
|
||||
// known-good baseline for comparison against the tape.
|
||||
geom_set_offset(ScreenRes_CenterX, ScreenRes_CenterY);
|
||||
geom_set_screen(ScreenZ);
|
||||
|
||||
@@ -452,8 +449,24 @@ int main(void)
|
||||
}
|
||||
}
|
||||
{
|
||||
// gknown gp_screen_init();
|
||||
gp_screen_init_c11(& smem.screen_buf, & smem.active_buf_id);
|
||||
reset_graph(0);
|
||||
// TODO(Ed): Move to an atom
|
||||
{
|
||||
smem.active_buf_id = 0;
|
||||
displayenv_init(& r_(smem.screen_buf.display)[0], 0, 0, ScreenRes_X, ScreenRes_Y);
|
||||
drawenv_init (& r_(smem.screen_buf.draw )[0], 0, ScreenRes_Y, ScreenRes_X, ScreenRes_Y);
|
||||
displayenv_init(& r_(smem.screen_buf.display)[1], 0, ScreenRes_Y, ScreenRes_X, ScreenRes_Y);
|
||||
drawenv_init (& r_(smem.screen_buf.draw )[1], 0, 0, ScreenRes_X, ScreenRes_Y);
|
||||
smem.screen_buf.draw[0].enable_auto_clear = true;
|
||||
smem.screen_buf.draw[1].enable_auto_clear = true;
|
||||
smem.screen_buf.draw[0].initial_bg_color = rgb8( .r = 7, .g = 7, .b = 7 );
|
||||
smem.screen_buf.draw[1].initial_bg_color = rgb8( .r = 7, .g = 7, .b = 7 );
|
||||
}
|
||||
TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape));
|
||||
register U4* io_base_addr rgcc(R_IO_BaseAddr) = u4_r(IO_BASE_ADDR);
|
||||
tb.used = 0; tb_scope_run(& tb) {
|
||||
tb_emit(& tb, gp_screen_init);
|
||||
}
|
||||
pad_init(0);
|
||||
}
|
||||
while (1) {
|
||||
|
||||
@@ -2,20 +2,125 @@
|
||||
# include "duffle/gen/duffle.macs.h"
|
||||
# include "duffle/gen/duffle.offsets.h"
|
||||
# include "duffle/atom_dsl.h"
|
||||
# include "duffle/pad.h"
|
||||
# include "duffle/lottes_tape.h"
|
||||
# include "duffle/mips.h"
|
||||
# include "duffle/gte.h"
|
||||
# include "duffle/gp.h"
|
||||
# include "duffle/word_count.metadata.h"
|
||||
# include "gen/hello_joypad.offsets.h"
|
||||
# include "gen/hello_joypad.macs.h"
|
||||
# include "hello_joypad.h"
|
||||
#endif
|
||||
|
||||
#pragma region MACs (Mips Atom components)
|
||||
|
||||
FI_ MipsAtom ac_gcmd_push(U4 cmd, U4 reg_transfer, U4 reg_base, U2 port)
|
||||
MipsAtomComp_Proc_(ac_gcmd_push, {
|
||||
load_upper_i(reg_transfer, cmd >> 16),
|
||||
or_i_self( reg_transfer, cmd & 0xFFFF),
|
||||
store_word( reg_transfer, reg_base, port),
|
||||
})
|
||||
|
||||
FI_ MipsAtom ac_put_disp_env(U4 reg_transfer, U4 reg_base, U2 port)
|
||||
MipsAtomComp_Proc_(ac_put_disp_env, {
|
||||
// Emits 5 GP0 commands for buffer 0 (display_area = (0,0,320,240)).
|
||||
// Sequence per libpsyx PutDispEnv: DrawArea TL → DrawArea BR → Mask → DrawArea TL → DrawArea BR
|
||||
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_set_mask_bit(), reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port),
|
||||
})
|
||||
|
||||
FI_ MipsAtom ac_put_draw_env(U4 reg_transfer, U4 reg_base, U2 port)
|
||||
MipsAtomComp_Proc_(ac_put_draw_env, {
|
||||
/* DR_ENV (16-word packet) — emitted at boot by the gp_screen_init atom.
|
||||
* The values below are pre-baked for the demo's buffer 0: auto_clear=true, bg=(7,7,7), clip=(0,240,320,240), ofs=(0,0), tw=(0,0,0,0).
|
||||
*
|
||||
* ORIGIN: each code word corresponds to the EXACT value libpsyx's PutDrawEnv function would compute for the same DrawEnv settings.
|
||||
* References:
|
||||
* - libpsyx source: `toolchain/psyq-4_7/lib/libgpu.a` (binary, function `PutDrawEnv`)
|
||||
* - PSX-SPX doc: https://problemkaputt.de/psx-spx.htm#gputdrawingcommands
|
||||
* - PSYQ SDK: `setdrawenv` / `makelongdr_env` source
|
||||
* - NOCASH PSX spec: §"GP0(E1h) Draw Mode setting" through §"DR_ENV"
|
||||
*
|
||||
* The 16-word format is documented in the PSYQ SDK manual and on NOCASH's PSX-spec.txt. The libpsyx reference is at:
|
||||
* ./toolchain/psyq-4_7/lib/libgpu.a
|
||||
* (binary; the PutDrawEnv implementation builds the 16-word DR_ENV from the user's DRAWENV struct and emits it via GP0 GPU commands.)
|
||||
*
|
||||
* Word indices (libpsyx PutDrawEnv / SetDrawEnv order):
|
||||
* tag = (length << 24) | addr — 16-word packet (1 tag + 15 code)
|
||||
* code[0] = DrawMode (dfe=1, dtd=0, tpage=0) — must come first per libpsyx
|
||||
* code[1] = TextureWindow (tw=(0,0)) — bare-cmd word; GPU uses current state
|
||||
* code[2] = DrawArea top-left (clip.x=0, clip.y=240)
|
||||
* code[3] = DrawArea bottom-right (clip.x+w=320, clip.y+h=480)
|
||||
* code[4] = DrawOffset (ofs=(0,0)) — bare-cmd word
|
||||
* code[5] = Mask (dtd=0, dfe=1, isbg=1) — 0xE6 cmd + isbg bit
|
||||
* code[6] = Initial-bg-color (isbg=1, r=7, g=7, b=7)
|
||||
* code[7] = DrawMode (isbg=1, tpage=0) — re-asserts DrawMode with isbg
|
||||
* code[8..10] = padding (NOP) — 3 words to fill the packet
|
||||
* code[11..12] = TextureWindow bottom-right — defaults to (0,0,0,0)
|
||||
* code[13..14] = padding (NOP) — completes the 16-word packet
|
||||
*/
|
||||
mac_gcmd_push(gp0_dr_env_tag, reg_transfer, reg_base, port), /* tag (length=15 << 24, addr=0) — packet header for the DR_ENV sequence. The GPU needs this to recognize the next 15 words as a DR_ENV packet and trigger the isbg auto-clear. */
|
||||
mac_gcmd_push(gp0_word_draw_mode_drawing_allowed, reg_transfer, reg_base, port), /* code[0] DrawMode (dfe=1, dtd=0, tpage=0) */
|
||||
mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port), /* code[1] TextureWindow (tw=(0,0)) */
|
||||
mac_gcmd_push(enc_gp0_draw_area_tl_word(0, ScreenRes_Y), reg_transfer, reg_base, port), /* code[2] DrawArea top-left (clip.x=0, clip.y=ScreenRes_Y=240) */
|
||||
mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port), /* code[3] DrawArea bottom-right (clip.x+w=320, clip.y+h=480) */
|
||||
|
||||
mac_gcmd_push(gp0_word_set_draw_offset(), reg_transfer, reg_base, port), /* code[4] DrawOffset (ofs=(0,0)) — bare-cmd word; the GPU uses the current state machine. */
|
||||
mac_gcmd_push(gp0_word_dr_env_mask(), reg_transfer, reg_base, port), /* code[5] Mask (dtd=0, dfe=1, isbg=1) — 0xE6 cmd + isbg bit. */
|
||||
mac_gcmd_push(gp0_word_dr_env_bg_color_cmd(1, 7, 7, 7), reg_transfer, reg_base, port), /* code[6] Initial-bg-color + auto-clear (isbg=1, r=7, g=7, b=7). */
|
||||
mac_gcmd_push(gp0_word_dr_env_draw_mode(1), reg_transfer, reg_base, port), /* code[7] Re-assert DrawMode with isbg=1 (isbg-flag set; the 0xE1 cmd byte plus isbg only). */
|
||||
|
||||
/* code[8..10] Padding (NOP — GPU discards; the DR_ENV requires 16 words total). */
|
||||
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
|
||||
|
||||
/* code[11..12] TextureWindow bottom-right (tw.x+tw.w=0, tw.y+tw.h=0) — libpsyx emits twice. */
|
||||
mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port),
|
||||
|
||||
/* code[13..14] Padding (NOP) — completes the 16-word packet. */
|
||||
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
|
||||
mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port),
|
||||
})
|
||||
|
||||
#pragma endregion MACs
|
||||
|
||||
#pragma region Baked Atoms
|
||||
|
||||
enum {
|
||||
R_IO_BaseAddr = R_T4 atom_reg, /* Caller-pinned: IO_BASE_ADDR = 0x1F800000 */
|
||||
#define R_IO_BaseAddr_Code R_T4_Code
|
||||
};
|
||||
internal MipsAtom_(gp_screen_init) atom_info(atom_phase(screen_init)) {
|
||||
store_word(R_0, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(00h) Reset */
|
||||
mac_gcmd_push(gp1_word_ResetCmdBuffer(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(01h) ClearFIFO */
|
||||
mac_gcmd_push(gp1_word_AcknowledgeIRQ(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(02h) AckIRQ */
|
||||
mac_gcmd_push(gp1_word_DisplayOn(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(03h) Display ON */
|
||||
mac_gcmd_push(gp1_word_dma_to_gpu(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(04h) DMADirection=2 (CPU→GPU). libpsyx's per-frame PutDrawEnv/DrawOTag use DMA2; without this the DMA queue never drains. */
|
||||
mac_gcmd_push(gp1_word_StartDisplayArea(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET), /* GP1(05h) StartDisplayArea (X=0, Y=0) */
|
||||
|
||||
/* GP1: DisplayMode + Display Ranges */
|
||||
mac_gcmd_push(gp1_word_display_mode_320x240_15bit_ntsc, R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
|
||||
mac_gcmd_push(gp1_word_horizontal_range_ntsc, R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
|
||||
mac_gcmd_push(gp1_word_vertical_range_ntsc, R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
|
||||
|
||||
/* GTE: SetGeomOffset (OFX, OFY) — ScreenRes_CenterX, ScreenRes_CenterY. */
|
||||
load_upper_i(R_T5, ScreenRes_CenterX), gte_mv_to_ctrl_r(R_T5, gte_cr_OFX_Code),
|
||||
load_upper_i(R_T5, ScreenRes_CenterY), gte_mv_to_ctrl_r(R_T5, gte_cr_OFY_Code),
|
||||
|
||||
/* GTE: SetGeomScreen (H) — CR26 (per PSX-SPX / libpsyx), value is the raw projection-plane distance, NOT shifted. */
|
||||
add_ui(R_T5, R_0, ScreenZ), gte_mv_to_ctrl_r(R_T5, gte_cr_H_Code),
|
||||
|
||||
/* GP1: DisplayEnable — bit 0 = 0 (Display ON). */
|
||||
mac_gcmd_push(gp1_word_DisplayOn(), R_T5, R_IO_BaseAddr, GPIO_PORT1_OFFSET),
|
||||
mac_yield(),
|
||||
};
|
||||
|
||||
enum {
|
||||
R_PadState = R_T4 atom_reg atom_type(U4),
|
||||
R_PadSignal = R_T0 atom_reg atom_type(U4),
|
||||
@@ -29,15 +134,14 @@ typedef Struct_(Binds_PadInputDemo) {
|
||||
};
|
||||
internal MipsAtom_(pad_input_demo) atom_info(atom_bind(Binds_PadInputDemo)
|
||||
, atom_reads(R_PadState, R_CubeRot, R_FloorRot)
|
||||
, atom_writes( R_CubeRot, R_FloorRot)
|
||||
, atom_writes(R_CubeRot, R_FloorRot)
|
||||
) {
|
||||
load_word(R_PadState, R_TapePtr, O_(Binds_PadInputDemo,pad_state)),
|
||||
load_word(R_CubeRot, R_TapePtr, O_(Binds_PadInputDemo,cube_rot)),
|
||||
load_word(R_FloorRot, R_TapePtr, O_(Binds_PadInputDemo,floor_rot)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_PadInputDemo)),
|
||||
|
||||
and_i(R_PadSignal, R_PadState, pad0_(Pad_Left)),
|
||||
branch_le_zero(R_PadSignal, atom_offset(pad_left, exit_pad_left)),
|
||||
and_i(R_PadSignal, R_PadState, pad0_(Pad_Left)), branch_le_zero(R_PadSignal, atom_offset(pad_left, exit_pad_left)),
|
||||
load_half( R_T5, R_CubeRot, O_(V3_S2,y)), // BD-Slot occupied
|
||||
load_half( R_T6, R_FloorRot, O_(V3_S2,y)),
|
||||
add_si( R_T5, R_T5, 30),
|
||||
@@ -46,8 +150,7 @@ internal MipsAtom_(pad_input_demo) atom_info(atom_bind(Binds_PadInputDemo)
|
||||
store_half(R_T6, R_FloorRot, O_(V3_S2,y)),
|
||||
atom_label(exit_pad_left)
|
||||
|
||||
and_i(R_PadSignal, R_PadState, pad0_(Pad_Right)),
|
||||
branch_le_zero(R_PadSignal, atom_offset(pad_right, exit_pad_right)),
|
||||
and_i(R_PadSignal, R_PadState, pad0_(Pad_Right)), branch_le_zero(R_PadSignal, atom_offset(pad_right, exit_pad_right)),
|
||||
load_half( R_T5, R_CubeRot, O_(V3_S2,y)), // BD-Slot occupied
|
||||
load_half( R_T6, R_FloorRot, O_(V3_S2,y)),
|
||||
add_si( R_T5, R_T5, -30),
|
||||
|
||||
@@ -7,12 +7,12 @@ A rest from the usual.
|
||||
## Dependencies
|
||||
|
||||
I will be programming from a Windows 11 machine (may eventually try this on the Steam Deck...):
|
||||

|
||||
|
||||
[armips](https://github.com/Kingcom/armips)
|
||||
|
||||
* Supports doing bare-metal assembly for the ps1
|
||||
* `scoop install armips` or just clone and build..
|
||||
* Was used early in the course. Now I just use an macro asm dsl in C11.
|
||||
|
||||
[luajit-2.1](https://github.com/LuaJIT/LuaJIT.git)
|
||||
|
||||
@@ -73,3 +73,9 @@ scoop install luajit
|
||||

|
||||

|
||||

|
||||
|
||||
Win 11 machine:
|
||||
|
||||

|
||||
|
||||
Still haven't gotten around to trying this on linux...
|
||||
|
||||
Reference in New Issue
Block a user