mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-07 08:08:49 +00:00
WIP: working towards minimizing C-ABI & PsyQ CRT usage
This commit is contained in:
+14
-22
@@ -50,17 +50,13 @@
|
|||||||
#define asm_words(...) m_expand(glue(GCC_ASM_INL_, GCC_ASM_COUNT_ARGS(__VA_ARGS__))(__VA_ARGS__))
|
#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
|
// 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
|
/* reg_str(n) — Stringify an integer register id into the GCC asm string form (e.g. 12 → "$12").
|
||||||
* string form (e.g. 12 → "$12"). Use this anywhere GCC's parser
|
* Use this anywhere GCC's parser expects a literal string identifying a register: clobber lists,
|
||||||
* 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 —
|
||||||
* asm templates, etc. The two-level macro is the standard preprocessor
|
* without it, `#n` would stringify the macro name `R_T4` to `"R_T4"` instead of expanding `R_T4` to its value first.
|
||||||
* 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
|
* For declaring a register variable bound to a specific GPR, use the `rgcc(n)` bundle from gcc_asm.h instead —
|
||||||
* `rgcc(n)` bundle from gcc_asm.h instead — it adds the `__asm__()`
|
* it adds the `__asm__()` qualifier around the string.
|
||||||
* qualifier around the string.
|
|
||||||
*
|
*
|
||||||
* register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose
|
* register V3_S2* p0 __asm__(reg_str(R_T4)) = ...; // verbose
|
||||||
* register V3_S2* p0 rgcc(R_T4) = ...; // bundled
|
* register V3_S2* p0 rgcc(R_T4) = ...; // bundled
|
||||||
@@ -85,7 +81,7 @@
|
|||||||
* - The string "$12" is derived from it via reg_str, so they cannot drift apart.
|
* - 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.
|
* - 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.
|
* 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.
|
* and the integer→string derivation in rlit can be retargeted in one place.
|
||||||
@@ -94,12 +90,10 @@
|
|||||||
* ------------------------------------------------------------------------ */
|
* ------------------------------------------------------------------------ */
|
||||||
#define rgcc(n) __asm__(rlit(n))
|
#define rgcc(n) __asm__(rlit(n))
|
||||||
|
|
||||||
/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used
|
/* rgcc_ref(n) — GCC operand-reference form "%N". Not currently used by the placeholder-pun macros
|
||||||
* by the placeholder-pun macros (the .word bodies are fully baked
|
* (the .word bodies are fully baked at compile time and have no runtime operand references),
|
||||||
* at compile time and have no runtime operand references), but kept
|
* but kept here for completeness in case a future asm template needs to refer to a runtime input by position.
|
||||||
* here for completeness in case a future asm template needs to refer
|
* Mirror of rgcc but produces "%N" instead of "$N". */
|
||||||
* to a runtime input by position. Mirror of rgcc but produces "%N"
|
|
||||||
* instead of "$N". */
|
|
||||||
#define rgcc_ref_(n) "%" #n
|
#define rgcc_ref_(n) "%" #n
|
||||||
#define rgcc_ref(n) rgcc_ref_(n)
|
#define rgcc_ref(n) rgcc_ref_(n)
|
||||||
|
|
||||||
@@ -147,11 +141,9 @@
|
|||||||
9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
|
9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
|
||||||
|
|
||||||
/* --- 2. String Concatenation Helpers --- *
|
/* --- 2. String Concatenation Helpers --- *
|
||||||
* NOTE: we use `%0`, `%1`, ... not `%c0`, `%c1`, ... because GCC's
|
* NOTE: we use `%0`, `%1`, ... not `%c0`, `%c1`, ... because GCC's asm-parser rejects `%cN` in this position with "invalid use of '%c'".
|
||||||
* 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 `%cN` form is for printing *character* constants; for arbitrary
|
* the plain `%N` form is the right one. Both expand to the bare immediate.
|
||||||
* 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_W1 "%0"
|
||||||
#define GCC_ASM_W2 GCC_ASM_W1 ", %1"
|
#define GCC_ASM_W2 GCC_ASM_W1 ", %1"
|
||||||
|
|||||||
+60
-15
@@ -39,8 +39,8 @@
|
|||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
* Hardware MMIO Addresses
|
* Hardware MMIO Addresses
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
* PSX GPU has two 32-bit ports in the I/O register region at KSEG2
|
* PSX GPU has two 32-bit ports in the I/O register region at KSEG2 0x1F800000+.
|
||||||
* 0x1F800000+. GP0 (offset 0x10) is the data port (commands + params).
|
* GP0 (offset 0x10) is the data port (commands + params).
|
||||||
* GP1 (offset 0x14) is the control port (status, ctrl writes).
|
* GP1 (offset 0x14) is the control port (status, ctrl writes).
|
||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
/* IO base address (KSEG2 0x1F800000+ for the I/O register region).
|
/* IO base address (KSEG2 0x1F800000+ for the I/O register region).
|
||||||
@@ -73,9 +73,7 @@ enum {
|
|||||||
* GP0 command byte constants + Layer 1 (GPU bitfield shifts)
|
* 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.
|
* 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.
|
* 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.
|
* Mirrors the OPCODE_SHIFT / RS_SHIFT / REG_MASK convention from mips.h.
|
||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
enum {
|
enum {
|
||||||
@@ -176,6 +174,9 @@ enum {
|
|||||||
#define gp0_word_copy_vram() enc_gp0_cmd_word(gp0_cmd_CopyVram)
|
#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_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)
|
* GP1 command byte constants + Layer 1 (display-mode + range + draw-area bitfield shifts)
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
@@ -199,8 +200,7 @@ enum {
|
|||||||
* DrawArea word builders are below as GP0s * macros (since they emit GP0 commands). */
|
* DrawArea word builders are below as GP0s * macros (since they emit GP0 commands). */
|
||||||
|
|
||||||
/* ---- Display-mode payload flags (per PSX-SPX §"GP1 Display Mode").
|
/* ---- Display-mode payload flags (per PSX-SPX §"GP1 Display Mode").
|
||||||
* Bit positions match the encoder shifts below; values are the
|
* 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). */
|
||||||
* *payload* bits only (the cmd byte is OR'd in by enc_gp1_disp_mode_word). */
|
|
||||||
gp1_disp_HRes_256 = 0x0,
|
gp1_disp_HRes_256 = 0x0,
|
||||||
gp1_disp_HRes_320 = 0x1,
|
gp1_disp_HRes_320 = 0x1,
|
||||||
gp1_disp_HRes_512 = 0x2,
|
gp1_disp_HRes_512 = 0x2,
|
||||||
@@ -235,7 +235,7 @@ enum {
|
|||||||
#define enc_gp1_disp_hres(h) (((h) & gp1_disp_hres_mask) << gp1_disp_hres_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_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_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_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_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)
|
#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))
|
#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 ---- */
|
/* ---- 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_enable(on) (enc_gp0_cmd(gp1_cmd_DisplayEnable) | ((on) & 1))
|
||||||
#define gp1_word_display_disable() gp1_word_display_enable(0)
|
#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)
|
#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,6 +284,9 @@ enum {
|
|||||||
#define gp1_word_display_enabled enc_gp0_cmd_word(gp1_cmd_DisplayEnable)
|
#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_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) ---- */
|
/* ---- DMA direction (2-bit payload on DMADirection cmd 0x04) ---- */
|
||||||
enum {
|
enum {
|
||||||
gp1_dma_dir_Off = 0,
|
gp1_dma_dir_Off = 0,
|
||||||
@@ -287,6 +295,8 @@ enum {
|
|||||||
gp1_dma_dir_GPUREAD_to_CPU = 3,
|
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_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) ---- */
|
/* ---- Standard display ranges (NTSC + PAL pre-baked) ---- */
|
||||||
/* Horizontal range values are in video clock units (8 units/pixel); vertical range values are scanline numbers. */
|
/* Horizontal range values are in video clock units (8 units/pixel); vertical range values are scanline numbers. */
|
||||||
@@ -314,14 +324,49 @@ enum {
|
|||||||
/* ---- Draw-mode setting (TPage / draw-area allowance) ---- */
|
/* ---- Draw-mode setting (TPage / draw-area allowance) ---- */
|
||||||
/* The "drawing enabled" word is the standard post-init state. */
|
/* The "drawing enabled" word is the standard post-init state. */
|
||||||
enum {
|
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))
|
#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_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_320x240 enc_gp0_draw_area_br_word(319, 239)
|
||||||
#define gp0_word_draw_area_bottom_right_640x480 enc_gp0_draw_area_br_word(640, 480)
|
#define gp0_word_draw_area_bottom_right_640x480 enc_gp0_draw_area_br_word(639, 479)
|
||||||
|
|
||||||
#pragma endregion GPU Ports & Commands
|
#pragma endregion GPU Ports & Commands
|
||||||
|
|
||||||
@@ -360,10 +405,10 @@ typedef Struct_(RGB8) { B1 r; B1 g; B1 b; };
|
|||||||
#define rgb8(r,g,b) ((RGB8){r,g,b})
|
#define rgb8(r,g,b) ((RGB8){r,g,b})
|
||||||
|
|
||||||
/* ---------- PolyTag (the OT-link header; 1 word) ---------- */
|
/* ---------- PolyTag (the OT-link header; 1 word) ---------- */
|
||||||
enum {
|
// enum {
|
||||||
PolyTag_len_bits = 8,
|
// PolyTag_len_bits = 8,
|
||||||
PolyTag_addr_bits = 24,
|
// PolyTag_addr_bits = 24,
|
||||||
};
|
// };
|
||||||
typedef Struct_(PolyTag) {
|
typedef Struct_(PolyTag) {
|
||||||
union {
|
union {
|
||||||
U4 code;
|
U4 code;
|
||||||
|
|||||||
+75
-119
@@ -17,9 +17,8 @@
|
|||||||
* gte_lw_v0_xy(base) (gte + lw + v0 + xy)
|
* gte_lw_v0_xy(base) (gte + lw + v0 + xy)
|
||||||
* load_upper_i (load-upper + immediate, unique verb)
|
* load_upper_i (load-upper + immediate, unique verb)
|
||||||
*
|
*
|
||||||
* Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are
|
* Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are NOT in this header.
|
||||||
* NOT in this header. They live in the opt-in `gte_vendor_sym.h` for
|
* They are in the opt-in `gte_vendor_sym.h` for users who prefer the textbook MIPS assembly mnemonics.
|
||||||
* users who prefer the textbook MIPS assembly mnemonics.
|
|
||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
|
|
||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
@@ -34,20 +33,16 @@
|
|||||||
* gte.h — Geometry Transformation Engine (COP2) for the PS1
|
* gte.h — Geometry Transformation Engine (COP2) for the PS1
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*
|
*
|
||||||
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word`
|
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word` constants from C.
|
||||||
* constants from C. No GCC inline-assembly string syntax in the code body.
|
* No GCC inline-assembly string syntax in the code body.
|
||||||
*
|
*
|
||||||
* STYLE NOTES
|
* STYLE NOTES
|
||||||
* -----------
|
* -----------
|
||||||
* - Per-field encoders are named `enc_gte_<field>(value)` and each one
|
* - Per-field encoders are named `enc_gte_<field>(value)` and each one self-masks its argument before shifting.
|
||||||
* self-masks its argument before shifting. Mirrors the `enc_op / enc_rs
|
* Mirrors the `enc_op / enc_rs / enc_rt / ...` family in mips.h.
|
||||||
* / 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.
|
||||||
* - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of
|
* - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined for the common cases so call sites read like assembly source.
|
||||||
* the per-field encoders, plus the COP2/CO base.
|
* - All register/field values are enums (not `#define`s) so they show up in debugger symbol tables and IDE autocomplete.
|
||||||
* - 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
|
* SEE ALSO
|
||||||
* --------
|
* --------
|
||||||
@@ -58,8 +53,7 @@
|
|||||||
|
|
||||||
/* --- GTE Data Registers (Coprocessor 2) ---
|
/* --- GTE Data Registers (Coprocessor 2) ---
|
||||||
* Preprocessor-visible integer ids for the COP2 data register file.
|
* Preprocessor-visible integer ids for the COP2 data register file.
|
||||||
* Each enum value is bound to a parallel `_Code` `#define` so the
|
* Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
||||||
* preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
|
||||||
* Same pattern as the GPR `_Code` set in mips.h. */
|
* Same pattern as the GPR `_Code` set in mips.h. */
|
||||||
#define C2_VXY0_Code 0
|
#define C2_VXY0_Code 0
|
||||||
#define C2_VZ0_Code 1
|
#define C2_VZ0_Code 1
|
||||||
@@ -192,10 +186,8 @@ enum {
|
|||||||
|
|
||||||
/* --- GTE Control Register Indices (for ctc2/cfc2) ---
|
/* --- GTE Control Register Indices (for ctc2/cfc2) ---
|
||||||
* Preprocessor-visible integer ids for the COP2 control register file.
|
* Preprocessor-visible integer ids for the COP2 control register file.
|
||||||
* Each enum value is bound to a parallel `_Code` `#define` so the
|
* Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths).
|
||||||
* 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. */
|
||||||
* 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_RT11_Code 0
|
||||||
#define gte_cr_RT12_Code 1 /* packed with RT13 in bits 16..31 */
|
#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 */
|
#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_RFC_Code 27
|
||||||
#define gte_cr_GFC_Code 28
|
#define gte_cr_GFC_Code 28
|
||||||
#define gte_cr_BFC_Code 29
|
#define gte_cr_BFC_Code 29
|
||||||
#define gte_cr_OFX_Code 30
|
#define gte_cr_OFX_Code 24
|
||||||
#define gte_cr_OFY_Code 31
|
#define gte_cr_OFY_Code 25
|
||||||
|
#define gte_cr_H_Code 26
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
gte_cr_RT11 = gte_cr_RT11_Code, gte_cr_RT12 = gte_cr_RT12_Code, gte_cr_RT13 = gte_cr_RT13_Code,
|
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).
|
/* 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
|
* Spans the 2x2 {From, To} × {Data, Control} register classes that the GTE exposes:
|
||||||
* GTE exposes:
|
|
||||||
*
|
|
||||||
* bit 1 (0x02): register class — 0 = data, 1 = control
|
* bit 1 (0x02): register class — 0 = data, 1 = control
|
||||||
* bit 2 (0x04): direction — 0 = read, 1 = write
|
* bit 2 (0x04): direction — 0 = read, 1 = write
|
||||||
*
|
*
|
||||||
* The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit
|
* 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
|
||||||
* numbers as the general MIPS `cop_mf` / `cop_mt` defined in mips.h
|
* (which target the data register file on any coprocessor).
|
||||||
* (which target the data register file on any coprocessor). They are
|
* They are re-aliased here so the four-way table reads like the spec mnemonics (MFC2 / CFC2 / MTC2 / CTC2)
|
||||||
* re-aliased here so the four-way table reads like the spec mnemonics
|
* and so the encoding lives next to its only consumer (this header).
|
||||||
* (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)
|
* Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2) live in gte_vendor_sym.h. */
|
||||||
* live in gte_vendor_sym.h. */
|
|
||||||
enum { _C2_TX_SUBS_ = 0
|
enum { _C2_TX_SUBS_ = 0
|
||||||
, sub_mfc2 = 0x00 /* MFC2: Move From Coprocessor 2 data reg */
|
, sub_mfc2 = 0x00 /* MFC2: Move From Coprocessor 2 data reg */
|
||||||
, sub_cfc2 = 0x02 /* CFC2: Copy From Coprocessor 2 ctrl reg */
|
, sub_cfc2 = 0x02 /* CFC2: Copy From Coprocessor 2 ctrl reg */
|
||||||
@@ -314,8 +302,8 @@ enum { _C2_TX_SUBS_ = 0
|
|||||||
* `swc2` is redundant when we're already inside the `gte_` namespace.
|
* `swc2` is redundant when we're already inside the `gte_` namespace.
|
||||||
* gte_lw rt, base, off → lwc2 rt, off(base)
|
* gte_lw rt, base, off → lwc2 rt, off(base)
|
||||||
* gte_sw rt, base, off → swc2 rt, off(base)
|
* gte_sw rt, base, off → swc2 rt, off(base)
|
||||||
* For the typical user-facing vector-level load (xy + z as two
|
* For the typical user-facing vector-level load (xy + z as two instructions),
|
||||||
* instructions), use the higher-level `gte_load_vN` macros below. */
|
* use the higher-level `gte_load_vN` macros below. */
|
||||||
#define gte_lw(rt, base, off) enc_gte_lw(rt, base, off)
|
#define gte_lw(rt, base, off) enc_gte_lw(rt, base, off)
|
||||||
#define gte_sw(rt, base, off) enc_gte_sw(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).
|
* Opcode is always MIPS_OP_COP2, RS is always 1 (CO).
|
||||||
* The lower 25 bits are the GTE-specific command payload.
|
* The lower 25 bits are the GTE-specific command payload.
|
||||||
*
|
*
|
||||||
* The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs`
|
* The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs` pattern in mips.h:
|
||||||
* pattern in mips.h: each one self-masks and shifts its own field, so a
|
* Each one self-masks and shifts its own field, so a caller can build up a GTE command piece by piece
|
||||||
* caller can build up a GTE command piece by piece (handy for state-driven
|
* (handy for state-driven MVMVA emitters that vary one field at a time).
|
||||||
* MVMVA emitters that vary one field at a time).
|
|
||||||
*
|
*
|
||||||
* `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command
|
* `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command word in one go.
|
||||||
* word in one go. It just ORs the per-field encoders together. */
|
* It just ORs the per-field encoders together. */
|
||||||
#define gte_cmd_base (enc_op(op_cop2) | (1 << 25))
|
#define gte_cmd_base (enc_op(op_cop2) | (1 << 25))
|
||||||
|
|
||||||
/* Per-field encoders. Each one does (value & mask) << shift on its own. */
|
/* Per-field encoders. Each one does (value & mask) << shift on its own. */
|
||||||
@@ -363,8 +350,9 @@ enum { _C2_TX_SUBS_ = 0
|
|||||||
* (standard rotation-matrix, no scaling factor, V0 vector, translation vector, no clamp),
|
* (standard rotation-matrix, no scaling factor, V0 vector, translation vector, no clamp),
|
||||||
* so the only varying bits are the `cmd` field.
|
* 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_*`
|
* Naming convention:
|
||||||
* is the fully-encoded 32-bit instruction word ready to drop into a `.word` directive.
|
* - `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):
|
* PsyQ-compatibility note (RTPS/RTPT):
|
||||||
@@ -413,20 +401,16 @@ enum { _C2_TX_SUBS_ = 0
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Loads a single SVECTOR to GTE vector register V0
|
* @brief Loads a single SVECTOR to GTE vector register V0
|
||||||
*
|
|
||||||
* @details Loads values from an SVECTOR struct to GTE data registers C2_VXY0
|
* @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`.
|
* (XY at offset 0) and C2_VZ0 (Z at offset 4) using `lwc2`.
|
||||||
*
|
*
|
||||||
* Uses string-style GCC inline asm with `%0` substitution because the
|
* Uses string-style GCC inline asm with `%0` substitution because the base register `r0` is a runtime GPR chosen by the compiler.
|
||||||
* base register `r0` is a runtime GPR chosen by the compiler.
|
|
||||||
* It cannot be encoded into a static `.word` constant.
|
* It cannot be encoded into a static `.word` constant.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage: asm_gte_load_v0(svector_ptr);
|
||||||
* asm_gte_load_v0(svector_ptr);
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* lwc2 encoding helpers parameterized on the base GPR.
|
/* lwc2 encoding helpers parameterized on the base GPR.
|
||||||
*
|
|
||||||
* gte_lw_v0_xy(base) → lwc2 $0, 0(base) ; C2_VXY0
|
* gte_lw_v0_xy(base) → lwc2 $0, 0(base) ; C2_VXY0
|
||||||
* gte_lw_v0_z(base) → lwc2 $1, 4(base) ; C2_VZ0
|
* gte_lw_v0_z(base) → lwc2 $1, 4(base) ; C2_VZ0
|
||||||
* gte_lw_v1_xy(base) → lwc2 $2, 0(base) ; C2_VXY1
|
* 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
|
* 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.
|
* `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
|
* These are pure compile-time integers; the C compiler constant-folds them into .word directives. */
|
||||||
* them into .word directives. */
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
GTE_Z_Offset = 4
|
GTE_Z_Offset = 4
|
||||||
@@ -459,8 +442,8 @@ enum {
|
|||||||
* gte_load_v0(p_in_12, R_T4); // R_T4 = 12, base is $12
|
* 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),
|
* 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
|
* which is exactly the register the .word constants expect.
|
||||||
* ("asm specifier for variable conflicts with asm clobber list"), so we omit it.
|
* 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.
|
* 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
|
* WHICH REGISTER TO PICK
|
||||||
@@ -499,10 +482,8 @@ enum {
|
|||||||
|
|
||||||
/* gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) — prelude to gte_cmd_rtpt.
|
/* 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,
|
* Loads all three GTE input vectors (6 words) from three separate pointers, one per GTE vector register,
|
||||||
* one per GTE vector register, each loaded from its own base GPR.
|
* each loaded from its own base GPR. Caller must bind each `pN` to `bN` via a register variable.
|
||||||
* 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* p0 rgcc(R_T4) = verts[0].ptr; // → __asm__("$12")
|
||||||
* register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13")
|
* register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13")
|
||||||
* register V3_S2* p2 rgcc(R_T6) = verts[2].ptr; // → __asm__("$14")
|
* 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)
|
* @brief Rotate, Translate and Perspective Triple (23 cycles)
|
||||||
*
|
* @details Performs rotation, translation and perspective calculation of three vertices at once.
|
||||||
* @details Performs rotation, translation and perspective calculation of three
|
* The equation performed is the same as gte_rtps() only repeated three times for each vertex.
|
||||||
* vertices at once. The equation performed is the same as gte_rtps() only
|
* The result of the first vertex is stored in GTE data register C2_SXY0, the second vector in C2_SXY1 then C2_SXY2.
|
||||||
* 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):
|
* Encoder-style emission (no inline-asm strings in the code body):
|
||||||
* 1. Two `nop` words fill the COP2 pipeline latency — the GTE
|
* 1. Two `nop` words fill the COP2 pipeline latency — the GTE takes ~8 cycles per perspective divide,
|
||||||
* takes ~8 cycles per perspective divide, and the nops let any
|
* and the nops let any preceding lwc2/swc2 retire before RTPT starts reading its inputs from V0/V1/V2.
|
||||||
* preceding lwc2/swc2 retire before RTPT starts reading its
|
* 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the pre-baked encoders above) —
|
||||||
* inputs from V0/V1/V2.
|
* `0x0280030` decoded as `op_cop2` | CO(1) | cmd=RTPT, with all SF/MX/V/CV/LM fields zero
|
||||||
* 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the
|
* (standard rotation, no scaling, V0 vector, translation vector, no clamp).
|
||||||
* 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
|
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel ABI)
|
||||||
* ABI) plus the standard "memory" barrier. Does not clobber any COP2
|
* plus the standard "memory" barrier. Does not clobber any COP2 data/control register —
|
||||||
* data/control register — those have to be saved by the caller if
|
* 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.).
|
||||||
* they need to survive across the call (RTPT writes SXY0..2, SZ0..3,
|
|
||||||
* OTZ, MAC0..3, IR0..3, etc.).
|
|
||||||
*/
|
*/
|
||||||
#define gte_rtpt() \
|
#define gte_rtpt() \
|
||||||
asm volatile( \
|
asm volatile( \
|
||||||
@@ -559,32 +531,24 @@ enum {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Normal clipping (8 cycles)
|
* @brief Normal clipping (8 cycles)
|
||||||
*
|
* @details Computes the sign of three screen coordinates (C2_SXY0-2) used for backface culling.
|
||||||
* @details Computes the sign of three screen coordinates (C2_SXY0-2) used for
|
* If the value of C2_MAC0 is negative, the coordinates are inverted and thus the triangle is back facing.
|
||||||
* 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:
|
* The following equation is performed when executing this GTE command:
|
||||||
*
|
|
||||||
* MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1
|
* MAC0 = SX0*SY1 + SX1*SY2 + SX2*SY0 - SX0*SY2 - SX1*SY0 - SX2*SY1
|
||||||
*
|
|
||||||
* Encoder-style emission (no inline-asm strings in the code body):
|
* Encoder-style emission (no inline-asm strings in the code body):
|
||||||
* 1. Two `nop` words fill the COP2 pipeline latency - the GTE
|
* 1. Two `nop` words fill the COP2 pipeline latency
|
||||||
* pipeline takes a few cycles per op, and the nops let any
|
* - the GTE pipeline takes a few cycles per op, and the nops let any preceding
|
||||||
* preceding lwc2/swc2/RTPT retire before NCLIP starts reading
|
* lwc2/swc2/RTPT retire before NCLIP starts reading its inputs from SXY0/SXY1/SXY2.
|
||||||
* its inputs from SXY0/SXY1/SXY2.
|
* 2. The NCLIP command word itself is `gte_cmdw_nclip` (see the pre-baked encoders above)
|
||||||
* 2. The NCLIP command word itself is `gte_cmdw_nclip` (see the
|
* - `0x01400006` decoded as `op_cop2` | CO(1) | cmd=NCLIP, with all SF/MX/V/CV/LM fields zero.
|
||||||
* pre-baked encoders above) - `0x01400006` decoded as
|
* NCLIP is spec-clean in the original PsyQ source (unlike RTPS/RTPT which carry the `gte_cmdw_psyq_compat` quirk),
|
||||||
* `op_cop2` | CO(1) | cmd=NCLIP, with all SF/MX/V/CV/LM fields
|
* so `gte_cmdw_nclip` does NOT OR in any reserved bits.
|
||||||
* 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
|
* Clobbers the caller-saved GPRs via `clbr_volatile_gprs` (per the kernel ABI) plus the standard "memory" barrier.
|
||||||
* ABI) plus the standard "memory" barrier. Does not clobber any COP2
|
* Does not clobber any COP2 data/control register.
|
||||||
* data/control register - those have to be saved by the caller if
|
* Those have to be saved by the caller if they need to survive across the call (NCLIP writes MAC0 only;
|
||||||
* they need to survive across the call (NCLIP writes MAC0 only; it
|
* it is purely a sign-of-double-product computation on SXY0..2).
|
||||||
* is purely a sign-of-double-product computation on SXY0..2).
|
|
||||||
*/
|
*/
|
||||||
#define gte_nclip() \
|
#define gte_nclip() \
|
||||||
asm volatile( \
|
asm volatile( \
|
||||||
@@ -610,13 +574,10 @@ enum {
|
|||||||
"cop2 0x0158002D;")
|
"cop2 0x0158002D;")
|
||||||
|
|
||||||
/* asm_gte_matrix_set_rotation(r0)
|
/* 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
|
* Memory layout at r0: five contiguous 32-bit words (offsets 0..16), each holding two packed 16-bit matrix elements.
|
||||||
* control registers (RT11..RT22, indices 0..4) via ctc2.
|
* 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] | ...).
|
* [RT_xx, RT_xy] | [RT_xz, pad] | ...).
|
||||||
*
|
*
|
||||||
* Generated MIPS (mirrors the source macro):
|
* Generated MIPS (mirrors the source macro):
|
||||||
@@ -631,27 +592,22 @@ enum {
|
|||||||
* ctc2 $13, $3 ; → C2_RT21
|
* ctc2 $13, $3 ; → C2_RT21
|
||||||
* ctc2 $14, $4 ; → C2_RT22
|
* ctc2 $14, $4 ; → C2_RT22
|
||||||
*
|
*
|
||||||
* Same contract as gte_load_v0: caller MUST bind `r0` to $12 via a
|
* Same contract as gte_load_v0: caller MUST bind `r0` to $12 via a register variable (`rgcc(R_T4)`) for the `lw $12, off(...)`
|
||||||
* 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.
|
||||||
* instructions to read from the right base. The `"r"(r0)` constraint
|
* The .word constants here bake R_T4/R_T5/R_T6 into the `rs` field of each lw, so the lw instructions will
|
||||||
* alone doesn't force a specific GPR — it just lets GCC pick one.
|
* only do the right thing if $12 / $13 / $14 hold the matrix base at runtime.
|
||||||
* 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 = ...;
|
* M3_S2* m = ...;
|
||||||
* register M3_S2* m_in_12 rgcc(R_T4) = m;
|
* register M3_S2* m_in_12 rgcc(R_T4) = m;
|
||||||
* asm_gte_matrix_set_rotation(m_in_12);
|
* asm_gte_matrix_set_rotation(m_in_12);
|
||||||
*
|
*
|
||||||
* We clobber $12/$13/$14 (the ones we use as scratch inside the
|
* We clobber $12/$13/$14 (the ones we use as scratch inside the inline asm)
|
||||||
* inline asm) plus the system clobbers; we don't clobber `r0` because
|
* plus the system clobbers; we don't clobber `r0` because the `rgcc` binding already says "this variable lives in $12".
|
||||||
* the `rgcc` binding already says "this variable lives in $12".
|
|
||||||
*
|
*
|
||||||
* WARNING: Incomplete by design. The source macro only writes RT11..RT22
|
* WARNING: Incomplete by design. The source macro only writes RT11..RT22 (5 of 9 rotation elements);
|
||||||
* (5 of 9 rotation elements); RT23 and the entire RT3x row are left
|
* RT23 and the entire RT3x row are left untouched.
|
||||||
* untouched. Real libpsn00b SetRotMatrix writes all 9. Use only when the
|
* Real libpsn00b SetRotMatrix writes all 9. Use only when the GTE's remaining rotation entries are already correct,
|
||||||
* GTE's remaining rotation entries are already correct, or you will
|
* or you will get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output.
|
||||||
* get stale-RT2x/RT3x artifacts in RTPS/RTPT/MVMVA output.
|
|
||||||
*/
|
*/
|
||||||
#define asm_gte_matrix_set_rotation(r0) \
|
#define asm_gte_matrix_set_rotation(r0) \
|
||||||
asm volatile( \
|
asm volatile( \
|
||||||
|
|||||||
+12
-13
@@ -35,20 +35,9 @@ enum {
|
|||||||
R_AtomJmp = R_T9 atom_reg, /* debug-visible; tape yield handshake scratch */
|
R_AtomJmp = R_T9 atom_reg, /* debug-visible; tape yield handshake scratch */
|
||||||
R_TapePtr = R_T8 atom_reg, /* The Instruction Stream Pointer */
|
R_TapePtr = R_T8 atom_reg, /* The Instruction Stream Pointer */
|
||||||
R_InCursor = R_T4,
|
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 */
|
|
||||||
|
|
||||||
/* Stringification codes for the GCC inline assembler clobber lists. */
|
/* Stringification codes for the GCC inline assembler clobber lists. */
|
||||||
#define R_TapePtr_Code R_T8_Code
|
#define R_TapePtr_Code R_T8_Code
|
||||||
#define R_InCursor_Code R_T4_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
|
#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_AT)
|
||||||
, rlit(R_V0), rlit(R_V1)
|
, rlit(R_V0), rlit(R_V1)
|
||||||
, rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3)
|
, 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_T4), rlit(R_T5), rlit(R_T6), rlit(R_T7)
|
||||||
, rlit(R_PrimCursor), rlit(R_FaceCursor), rlit(R_VertBase), rlit(R_OtBase)
|
|
||||||
, rlit(R_T9)
|
, rlit(R_T9)
|
||||||
, clb_mem_drain
|
, clb_mem_drain
|
||||||
); }
|
); }
|
||||||
@@ -113,6 +101,17 @@ atom_dbg_skip MipsAtomComp_(ac_yield) {
|
|||||||
jump_reg( R_AtomJmp), nop,
|
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 */
|
/* Words: 3; Loads 3 S2 indices from the face array */
|
||||||
atom_dbg_skip MipsAtomComp_(ac_load_tri_indices) {
|
atom_dbg_skip MipsAtomComp_(ac_load_tri_indices) {
|
||||||
load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)),
|
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))
|
#define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm))
|
||||||
|
|
||||||
/* load_imm_2w — unconditional 2-word `li` form: `lui` + (ori | addi).
|
/* 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:
|
* 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
|
* - you know `imm` is > 0xFFFF (otherwise you're wasting a word), OR
|
||||||
* - `imm` is not a compile-time constant and you want predictable
|
* - `imm` is not a compile-time constant and you want predictable 2-word emission without the `__builtin_constant_p` branches.
|
||||||
* 2-word emission without the `__builtin_constant_p` branches.
|
|
||||||
*
|
*
|
||||||
* The lo16 strategy is still chosen at expansion time on the lo half:
|
* 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 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)
|
* lo16 in 0x8000..0xFFFF → ori (zero-extends to preserve the intended bit pattern)
|
||||||
*
|
*
|
||||||
* For situations where you need to bypass even this choice
|
* For situations where you need to bypass even this choice (e.g. to force a specific encoding for a known discontiguous high/low pair),
|
||||||
* (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.
|
* see `load_imm_2w_ori_forced` and `load_imm_2w_addi_forced` below.
|
||||||
* Statement-level (not expression-level): emits its own `asm volatile(...)`.
|
* 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/lottes_tape.h"
|
||||||
#include "duffle/word_count.metadata.h"
|
#include "duffle/word_count.metadata.h"
|
||||||
|
|
||||||
|
# include "gen/hello_joypad.macs.h"
|
||||||
# include "gen/hello_joypad.offsets.h"
|
# include "gen/hello_joypad.offsets.h"
|
||||||
#include "hello_joypad.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
|
// Initialize and setup the GTE geometry offsets
|
||||||
geom_init();
|
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_offset(ScreenRes_CenterX, ScreenRes_CenterY);
|
||||||
geom_set_screen(ScreenZ);
|
geom_set_screen(ScreenZ);
|
||||||
|
|
||||||
@@ -452,8 +449,24 @@ int main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// gknown gp_screen_init();
|
reset_graph(0);
|
||||||
gp_screen_init_c11(& smem.screen_buf, & smem.active_buf_id);
|
// 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);
|
pad_init(0);
|
||||||
}
|
}
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|||||||
@@ -2,20 +2,125 @@
|
|||||||
# include "duffle/gen/duffle.macs.h"
|
# include "duffle/gen/duffle.macs.h"
|
||||||
# include "duffle/gen/duffle.offsets.h"
|
# include "duffle/gen/duffle.offsets.h"
|
||||||
# include "duffle/atom_dsl.h"
|
# include "duffle/atom_dsl.h"
|
||||||
|
# include "duffle/pad.h"
|
||||||
# include "duffle/lottes_tape.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 "duffle/word_count.metadata.h"
|
||||||
# include "gen/hello_joypad.offsets.h"
|
# include "gen/hello_joypad.offsets.h"
|
||||||
|
# include "gen/hello_joypad.macs.h"
|
||||||
# include "hello_joypad.h"
|
# include "hello_joypad.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#pragma region MACs (Mips Atom components)
|
#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 endregion MACs
|
||||||
|
|
||||||
#pragma region Baked Atoms
|
#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 {
|
enum {
|
||||||
R_PadState = R_T4 atom_reg atom_type(U4),
|
R_PadState = R_T4 atom_reg atom_type(U4),
|
||||||
R_PadSignal = R_T0 atom_reg atom_type(U4),
|
R_PadSignal = R_T0 atom_reg atom_type(U4),
|
||||||
@@ -36,8 +141,7 @@ internal MipsAtom_(pad_input_demo) atom_info(atom_bind(Binds_PadInputDemo)
|
|||||||
load_word(R_FloorRot, R_TapePtr, O_(Binds_PadInputDemo,floor_rot)),
|
load_word(R_FloorRot, R_TapePtr, O_(Binds_PadInputDemo,floor_rot)),
|
||||||
add_ui_self( R_TapePtr, S_(Binds_PadInputDemo)),
|
add_ui_self( R_TapePtr, S_(Binds_PadInputDemo)),
|
||||||
|
|
||||||
and_i(R_PadSignal, R_PadState, pad0_(Pad_Left)),
|
and_i(R_PadSignal, R_PadState, pad0_(Pad_Left)), branch_le_zero(R_PadSignal, atom_offset(pad_left, exit_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_T5, R_CubeRot, O_(V3_S2,y)), // BD-Slot occupied
|
||||||
load_half( R_T6, R_FloorRot, O_(V3_S2,y)),
|
load_half( R_T6, R_FloorRot, O_(V3_S2,y)),
|
||||||
add_si( R_T5, R_T5, 30),
|
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)),
|
store_half(R_T6, R_FloorRot, O_(V3_S2,y)),
|
||||||
atom_label(exit_pad_left)
|
atom_label(exit_pad_left)
|
||||||
|
|
||||||
and_i(R_PadSignal, R_PadState, pad0_(Pad_Right)),
|
and_i(R_PadSignal, R_PadState, pad0_(Pad_Right)), branch_le_zero(R_PadSignal, atom_offset(pad_right, exit_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_T5, R_CubeRot, O_(V3_S2,y)), // BD-Slot occupied
|
||||||
load_half( R_T6, R_FloorRot, O_(V3_S2,y)),
|
load_half( R_T6, R_FloorRot, O_(V3_S2,y)),
|
||||||
add_si( R_T5, R_T5, -30),
|
add_si( R_T5, R_T5, -30),
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ A rest from the usual.
|
|||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
I will be programming from a Windows 11 machine (may eventually try this on the Steam Deck...):
|
I will be programming from a Windows 11 machine (may eventually try this on the Steam Deck...):
|
||||||

|
|
||||||
|
|
||||||
[armips](https://github.com/Kingcom/armips)
|
[armips](https://github.com/Kingcom/armips)
|
||||||
|
|
||||||
* Supports doing bare-metal assembly for the ps1
|
* Supports doing bare-metal assembly for the ps1
|
||||||
* `scoop install armips` or just clone and build..
|
* `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)
|
[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