From 2dda3032817bd1254c41a233b6c9fe03613795fc Mon Sep 17 00:00:00 2001 From: Connor Olding Date: Tue, 23 Oct 2018 16:29:25 +0200 Subject: [PATCH] add on-screen frame-budget usage, rework graphics * enable anti-aliasing * increase teapot resolution * rework teapot display list offset to 0 for convenience * add font for frame-budget and debug text * add on-screen ascii text to cache test to get a feel for fonts * reduce cache test resolution to VI-padded 576x432 * rework other rendering settings (maybe no real difference) * tweak task pushing (maybe no real difference) --- cachetest.asm | 106 +++++++++---- dlist.asm | 27 +++- font.8x16.asm | 144 ++++++++++++++++++ font.asm | 103 ------------- inc/F3DEX2.inc | 23 ++- inc/main.inc | 12 +- inc/n64.inc | 5 + kernel.asm | 1 - main.asm | 88 ++++++++++- .../NotoSansMono-SemiCondensedMedium.16.i4 | Bin 0 -> 6080 bytes res/teapot.F3D | Bin 17224 -> 40496 bytes task.asm | 11 +- text.txt | 12 ++ 13 files changed, 374 insertions(+), 158 deletions(-) create mode 100644 font.8x16.asm delete mode 100644 font.asm create mode 100644 res/fonts/NotoSansMono-SemiCondensedMedium.16.i4 create mode 100644 text.txt diff --git a/cachetest.asm b/cachetest.asm index 1b0b45c..cbd14f2 100644 --- a/cachetest.asm +++ b/cachetest.asm @@ -44,8 +44,8 @@ constant MAIN_FROM(0x0000) constant MAIN_TO(0x0080) constant MAIN_FONT(0x4000) -constant WIDTH(640) -constant HEIGHT(480) +constant WIDTH(576) // 640 * 0.9 +constant HEIGHT(432) // 480 * 0.9 constant DEPTH(2) constant VIDEO_BUFFER(0x80400000 - WIDTH * HEIGHT * DEPTH) constant VIDEO_MODE(BPP16 | INTERLACE | AA_MODE_2 | DIVOT_EN | PIXEL_ADV_3 | DITHER_FILTER_EN) @@ -89,7 +89,7 @@ Start: li t1, 0xDEADBEEF li t2, 0xCAFEBABE li t3, 0xABAD1DEA - li t4, 0x12345678 + li t4, 0xADD0BEE5 lui a0, MAIN_BASE // spaced out a bit just to see what happens. @@ -137,8 +137,8 @@ if 0 { ori a0, MAIN_FONT // show our results on-screen. - lli s0, 64 // s0: X - lli s1, 48 // s1: Y + lli s0, 16 // s0: X + lli s1, 12 // s1: Y lli s2, 0x20 / 4 // s2: number of words to draw lui s3, MAIN_BASE ori s3, MAIN_TO // s3: start of data to dump @@ -148,12 +148,12 @@ MainHexDumpLoop: lw s4, 0(s3) // s4: current word being drawn - addiu s0, 8 * 8 + addiu s0, 8 * FONT_WIDTH lli s5, 8 // s5: inner loop iteration count MainHexDumpInnerLoop: andi t0, s4, 0x0F - subiu s0, 8 + subiu s0, FONT_WIDTH lui a0, MAIN_BASE ori a0, MAIN_FONT @@ -169,10 +169,40 @@ MainHexDumpInnerLoop: srl s4, 4 subiu s2, 1 - addiu s1, 12 + addiu s1, FONT_HEIGHT bnez s2, MainHexDumpLoop addiu s3, 4 + la s3, TEXT +DrawTextLoop: + lbu a1, 0(s3) + addiu s3, 1 + + beqz a1, DrawTextDone + + lli t0, 0x0D // delay slot + beq a1, t0, DrawTextLoop + + lli t1, 0x0A // delay slot + bne a1, t1,+ + nop + addiu s1, FONT_HEIGHT // next line + b DrawTextLoop + lli s0, 16 // return to base X offset ++ + + lui a0, MAIN_BASE // delay slot + ori a0, MAIN_FONT + sll a2, s1, 16 + or a2, s0 + la a3, VIDEO_BUFFER + jal DrawChar16 + + addiu s0, FONT_WIDTH // delay slot + b DrawTextLoop + nop + +DrawTextDone: // use our old cache-poking utility for now. jal PokeDataCache nop @@ -180,50 +210,57 @@ MainHexDumpInnerLoop: lui a0, VI_BASE li t1, VIDEO_MODE li t2, VIDEO_BUFFER & ADDR_MASK - li t3, 640 // width in pixels (for the buffer) - li t4, 0 // interrupt on line + li t3, WIDTH // width of the buffer in pixels + li t4, 0 // interrupt on line, 0 to disable (i think?) li t5, 0 // current line; any write clears VI interrupt li t6, 0x03E52239 // timings (split into 4) - li t7, 525 - 1 // lines. subtracting by one enables interlacing. - sw t1, 4 * 0(a0) - sw t2, 4 * 1(a0) - sw t3, 4 * 2(a0) - sw t4, 4 * 3(a0) - sw t5, 4 * 4(a0) - sw t6, 4 * 5(a0) - sw t7, 4 * 6(a0) + li t7, 525 - 1 // lines; subtracting by one enables interlacing + sw t1, VI_STATUS(a0) // offset 0x00 + sw t2, VI_ORIGIN(a0) // offset 0x04 + sw t3, VI_WIDTH(a0) // offset 0x08 + sw t4, VI_V_INTR(a0) // offset 0x0C + sw t5, VI_V_CURRENT_LINE(a0) // offset 0x10 + sw t6, VI_TIMING(a0) // offset 0x14 + sw t7, VI_V_SYNC(a0) // offset 0x18 li t1, 0x00000C15 // divide VI clock to get proper NTSC rate li t2, 0x0C150C15 // likewise (this is only different on PAL) - li t3, 0x006C02EC // 640 pixels per row, starting at 108 units - li t4, 0x00230203 // 480 pixels per column, starting at 35 units + li t3, 0x008C02CC // 576 pixels per row, starting at ... units + li t4, 0x003B01EB // 432 pixels per column, starting at ... units li t5, 0x000E0204 // video burst starts at 14 and lasts for 502 units li t6, 0x00000400 // x offset and x step size (inverse scaling) - li t7, 0x00000800 // y offset and y step size (inverse scaling) - sw t1, 4 * 7(a0) - sw t2, 4 * 8(a0) - sw t3, 4 * 9(a0) - sw t4, 4 * 10(a0) - sw t5, 4 * 11(a0) - sw t6, 4 * 12(a0) - sw t7, 4 * 13(a0) + li t7, 0x02000800 // y offset and y step size (inverse scaling) + // setting y offset to 0.5 (it's Q10 fixed point) + // reduces interlacing jitter at the cost of a little image sharpness. + sw t1, VI_H_SYNC(a0) // offset 0x1C + sw t2, VI_H_SYNC_LEAP(a0) // offset 0x20 + sw t3, VI_H_VIDEO(a0) // offset 0x24 + sw t4, VI_V_VIDEO(a0) // offset 0x28 + sw t5, VI_V_BURST(a0) // offset 0x2C + sw t6, VI_X_SCALE(a0) // offset 0x30 + sw t7, VI_Y_SCALE(a0) // offset 0x34 VideoLoop: lui a0, VI_BASE li t1, VIDEO_BUFFER & ADDR_MASK - + // wait until we're done displaying the frame. lw t0, VI_V_CURRENT_LINE(a0) sltiu at, t0, 2 + 1 beqz at,- nop - andi t0, 1 - bnez t0,+ + andi t0, 1 // check if we're on an odd field. + li t2, 0x003B01EB + bnez t0,+ // if we're not, branch. nop - addiu t1, WIDTH * DEPTH + addiu t1, WIDTH * DEPTH // odd field, so offset the image by one row. + li t2, 0x003B01E9 // slightly shorter image so that + // the y offset doesn't cause sampling out of bounds + sw t1, VI_ORIGIN(a0) + sw t2, VI_V_VIDEO(a0) j VideoLoop nop @@ -250,4 +287,9 @@ PokeDataCache: jr ra nop -include "font.asm" +align(16) +insert TEXT, "text.txt" +db 0 +align(4) + +include "font.8x16.asm" diff --git a/dlist.asm b/dlist.asm index 6baff7c..c27c792 100644 --- a/dlist.asm +++ b/dlist.asm @@ -174,14 +174,25 @@ if HIRES { gMatrix(view_mat1, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION) gPipeSync() - gSetCombine(15,15,31,4,7,7,7,4, 15,15,31,4,7,7,7,4) - gSetOtherMode(G_PM_NPRIMITIVE | G_CYC_1CYCLE | G_TP_NONE | G_TD_CLAMP | G_TL_TILE | G_TT_NONE | G_TF_AVERAGE | G_TC_FILT | G_CK_NONE | G_CD_MAGICSQ | G_AD_PATTERN, G_AC_NONE | G_ZS_PIXEL | Z_CMP | Z_UPD) + gSetCombine(0,0,0,4,0,0,0,4, 0,0,0,4,0,0,0,4) +variable upper(G_PM_NPRIMITIVE | G_CYC_1CYCLE | G_TP_NONE | G_TD_CLAMP | G_TL_TILE | G_TT_NONE | G_TF_AVERAGE | G_TC_FILT | G_CK_NONE | G_CD_MAGICSQ | G_AD_PATTERN) +variable lower(AA_EN | Z_CMP | Z_UPD | IM_RD | CVG_DST_CLAMP | ZMODE_OPA | ALPHA_CVG_SEL | G_BL_CLR_IN << 30 | G_BL_A_IN << 26 | G_BL_CLR_MEM << 22 | G_BL_A_MEM << 18) + gSetOtherMode(upper, lower) gGeometryMode(0, G_ZBUFFER | G_SHADE | G_CULL_FRONT | G_SHADING_SMOOTH) gSetSegment6(model) gMatrix(model_mat, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW) gDisplayList((6 << 24) | MODEL_START) +if 0 { + // debug: display coverage values onscreen. + gPipeSync() + gSetOtherMode(G_CYC_1CYCLE, G_ZS_PRIM | IM_RD | FORCE_BL | G_BL_CLR_IN << 30 | G_BL_0 << 26 | G_BL_CLR_BL << 22 | G_BL_A_MEM << 18) + gSetBlendColor(0xFF,0xFF,0xFF,0xFF) + gSetPrimDepth(0xFFFF, 0xFFFF) + gFillRect(0, 0, WIDTH - 1, HEIGHT - 1) +} + // finish. gFullSync() gEndList() @@ -204,8 +215,8 @@ align(8) viewport: // note that the third parameters here affect the range of Z-buffering. - dh WIDTH/2*4, HEIGHT/2*4, 0x3FF, 0 // scale - dh WIDTH/2*4, HEIGHT/2*4, 0x000, 0 // translation + dh WIDTH/2*4, HEIGHT/2*4, 0x1FF, 0 // scale + dh WIDTH/2*4, HEIGHT/2*4, 0x1FF, 0 // translation view_mat0: if FOV90 { @@ -215,10 +226,10 @@ if FOV90 { Mat.X($0001'4C8D, $0000'0000, $0000'0000, $0000'0000) Mat.Y($0000'0000, $0001'BB67, $0000'0000, $0000'0000) } - Mat.Z($0000'0000, $0000'0000, $FFFE'FF00, $FFFF'0000) - Mat.W($0000'0000, $0000'0000, $FFFD'FF00, $0000'0000) + Mat.Z($0000'0000, $0000'0000, $FFFE'FF9A, $FFFF'0000) + Mat.W($0000'0000, $0000'0000, $FFEB'FC00, $0000'0000) Mat.rix() -constant PERSPECTIVE_NORMALIZATION($00FF) +constant PERSPECTIVE_NORMALIZATION($000A) view_mat1: Mat.X($0001'0000, $0000'0000, $0000'0000, $0000'0000) @@ -265,6 +276,6 @@ constant MODEL_START(pc() - model) gQuadTri(2, 3, 7, 6) gEndList() } else { - constant MODEL_START(0x32B0) + constant MODEL_START(0) insert model, "res/teapot.F3D" } diff --git a/font.8x16.asm b/font.8x16.asm new file mode 100644 index 0000000..5b67e53 --- /dev/null +++ b/font.8x16.asm @@ -0,0 +1,144 @@ +constant FONT_WIDTH(8) +constant FONT_HEIGHT(16) +constant FONT_SIZE16(FONT_WIDTH * FONT_HEIGHT * 2 * 95) // 0x5F00 + +LoadFont16: + // loads a 95-character, 8x16 font + // as an RGB5A1 (16-bpp) image to the specified address. + // a0: address to load font to (size: 0x5F00) + li t9, FONT_SIZE16 + addu a1, a0, t9 // a1: end of output (exclusive) + la a2, FONT // a2: start of input + la a3, FONT + FONT.size // a3: end of input (exclusive) + +LoadFont16Loop: + lhu t9, 0(a2) + addiu a2, 2 + + srl t1, t9, 12 + srl t2, t9, 8 + srl t3, t9, 4 + andi t4, t9, 0x0F + andi t2, 0x0F + andi t3, 0x0F + +if 0 { + sll t1, 2 + sll t2, 2 + sll t3, 2 + sll t4, 2 +} else { + // copy lsb to get the full 5-bit range of values. + sll t5, t1, 1 + sll t6, t2, 1 + sll t7, t3, 1 + sll t8, t4, 1 + andi t1, 1 + andi t2, 1 + andi t3, 1 + andi t4, 1 + or t1, t5 + or t2, t6 + or t3, t7 + or t4, t8 + sll t1, 1 + sll t2, 1 + sll t3, 1 + sll t4, 1 +} + + sll t5, t1, 5 + sll t6, t2, 5 + sll t7, t3, 5 + sll t8, t4, 5 + or t1, t5 + or t2, t6 + or t3, t7 + or t4, t8 + + sll t5, t1, 5 + sll t6, t2, 5 + sll t7, t3, 5 + sll t8, t4, 5 + or t1, t5 + or t2, t6 + or t3, t7 + or t4, t8 + + ori t1, 1 + ori t2, 1 + ori t3, 1 + ori t4, 1 + + sh t1, 0x0(a0) + sh t2, 0x2(a0) + sh t3, 0x4(a0) + sh t4, 0x6(a0) + + bne a2, a3, LoadFont16Loop + addiu a0, 0x8 + + jr ra + nop + +DrawChar16: + // draws a 16-bpp character on-screen at the specified coordinates. + // a0: font data address (same argument as LoadFont16) + // a1: character (range: 32 to 126 inclusive) + // a2: X, Y coordinate in pixels: X | Y << 16 + // a3: output image address + + // exit early if character is outside of valid range. + subiu a1, 0x20 + bltz a1, DrawCharDone + sltiu at, a1, 0x80 - 0x20 + beqz at, DrawCharDone + + lli t9, FONT_WIDTH * FONT_HEIGHT * 2 // delay slot + multu a1, t9 + mflo t9 + addu a0, t9 // a0: character data address + + andi a1, a2, 0xFFFF // a1: X + srl a2, 16 // a2: Y + + sll t0, a1, 1 + lli t9, WIDTH * 2 + multu a2, t9 + mflo t9 + addu a3, t0 // offset output by X + addu a3, t9 // offset output by Y + + lli t9, FONT_HEIGHT // t9: rows remaining + +DrawChar16Loop: + // character width hardcoded for 8. + lhu t1, 0x0(a0) + lhu t2, 0x2(a0) + lhu t3, 0x4(a0) + lhu t4, 0x6(a0) + lhu t5, 0x8(a0) + lhu t6, 0xA(a0) + lhu t7, 0xC(a0) + lhu t8, 0xE(a0) + + sh t1, 0x0(a3) + sh t2, 0x2(a3) + sh t3, 0x4(a3) + sh t4, 0x6(a3) + sh t5, 0x8(a3) + sh t6, 0xA(a3) + sh t7, 0xC(a3) + sh t8, 0xE(a3) + + addiu a0, 0x10 + subiu t9, 1 + + bnez t9, DrawChar16Loop + addiu a3, WIDTH * 2 + +DrawCharDone: + jr ra + nop + +insert FONT, "res/fonts/NotoSansMono-SemiCondensedMedium.16.i4" diff --git a/font.asm b/font.asm deleted file mode 100644 index 05bd3d7..0000000 --- a/font.asm +++ /dev/null @@ -1,103 +0,0 @@ -constant FONT_SIZE16(8 * 12 * 2 * 256) // 0xC000 - -LoadFont16: - // loads a 256-character, 8x12 font - // as an RGB5A1 (16-bpp) image to the specified address. - // a0: address to load font to (size: 0xC000) - li t9, FONT_SIZE16 - addu a1, a0, t9 // a1: end of output (exclusive) - la a2, FONT // a2: start of input - la a3, FONT + FONT.size // a3: end of input (exclusive) - -LoadFont16Loop: - lbu t9, 0(a2) - addiu a2, 1 - - // sign-extend every pixel to get our blacks and whites. - sll t1, t9, 24 - sll t2, t9, 25 - sll t3, t9, 26 - sll t4, t9, 27 - sll t5, t9, 28 - sll t6, t9, 29 - sll t7, t9, 30 - sll t8, t9, 31 - // - sra t1, 31 - sra t2, 31 - sra t3, 31 - sra t4, 31 - sra t5, 31 - sra t6, 31 - sra t7, 31 - sra t8, 31 - - sh t1, 0x0(a0) - sh t2, 0x2(a0) - sh t3, 0x4(a0) - sh t4, 0x6(a0) - sh t5, 0x8(a0) - sh t6, 0xA(a0) - sh t7, 0xC(a0) - sh t8, 0xE(a0) - - bne a2, a3, LoadFont16Loop - addiu a0, 0x10 - - jr ra - nop - -DrawChar16: - // draws a 16-bpp character on-screen at the specified coordinates. - // a0: font data address (same argument as LoadFont16) - // a1: character (range: 0 to 255 inclusive) - // a2: X, Y coordinate in pixels: X | Y << 16 - // a3: output image address - - lli t9, 8 * 12 * 2 - multu a1, t9 - mflo t9 - addu a0, t9 // a0: character data address - - andi a1, a2, 0xFFFF // a1: X - srl a2, 16 // a2: Y - - sll t0, a1, 1 - lli t9, WIDTH * 2 - multu a2, t9 - mflo t9 - addu a3, t0 // offset output by X - addu a3, t9 // offset output by Y - - lli t9, 12 // t9: rows remaining (character height) - -DrawChar16Loop: - // character width hardcoded for 8. - lhu t1, 0x0(a0) - lhu t2, 0x2(a0) - lhu t3, 0x4(a0) - lhu t4, 0x6(a0) - lhu t5, 0x8(a0) - lhu t6, 0xA(a0) - lhu t7, 0xC(a0) - lhu t8, 0xE(a0) - - sh t1, 0x0(a3) - sh t2, 0x2(a3) - sh t3, 0x4(a3) - sh t4, 0x6(a3) - sh t5, 0x8(a3) - sh t6, 0xA(a3) - sh t7, 0xC(a3) - sh t8, 0xE(a3) - - addiu a0, 0x10 - subiu t9, 1 - - bnez t9, DrawChar16Loop - addiu a3, WIDTH * 2 - - jr ra - nop - -align(16); insert FONT, "res/dwarf.1bpp" diff --git a/inc/F3DEX2.inc b/inc/F3DEX2.inc index 6943c4d..fd06814 100644 --- a/inc/F3DEX2.inc +++ b/inc/F3DEX2.inc @@ -159,16 +159,29 @@ constant CVG_DST_WRAP(0x100) constant CVG_DST_FULL(0x200) constant CVG_DST_SAVE(0x300) // more Z stuff -constant ZMODE_OPA(0) -constant ZMODE_INTER(0x400) -constant ZMODE_XLU(0x800) -constant ZMODE_DEC(0xC00) +constant ZMODE_OPA(0) // opaque +constant ZMODE_INTER(0x400) // interpenetrating +constant ZMODE_XLU(0x800) // translucent +constant ZMODE_DEC(0xC00) // decal // more coverage stuff constant CVG_X_ALPHA(0x1000) constant ALPHA_CVG_SEL(0x2000) // blending constant FORCE_BL(0x4000) // no effect? +// blending modes +constant G_BL_CLR_IN(0) +constant G_BL_CLR_MEM(1) +constant G_BL_CLR_BL(2) +constant G_BL_CLR_FOG(3) +constant G_BL_1MA(0) +constant G_BL_A_MEM(1) +constant G_BL_A_IN(0) +constant G_BL_A_FOG(1) +constant G_BL_A_SHADE(2) +constant G_BL_1(2) +constant G_BL_0(3) + macro gNoOp() { // stalls the RDP (and not the RSP?) _g(0x00, 0, 0) @@ -489,7 +502,7 @@ macro gSetPrimDepth(variable z, variable dz) { // fixes the Z value to a constant for the following primitives. // useful for 2.5D effects, maybe? // TODO: what are the ranges? - _g(0xEE, 0, (z << 16) | d) + _g(0xEE, 0, (z << 16) | dz) } macro gRdpSetOtherMode(variable omodeH, variable omodeL) { diff --git a/inc/main.inc b/inc/main.inc index 8f1ffb0..094e6fa 100644 --- a/inc/main.inc +++ b/inc/main.inc @@ -5,7 +5,7 @@ constant HICOLOR(0) constant MAIN_DECOMP_IMAGE(HIRES & HICOLOR) -constant MAIN_BASE(0x800E) +constant MAIN_BASE(0x8004) constant MAIN_COUNTS(0x0010) constant MAIN_SP_TASK(0x0040) constant MAIN_XXD(0x0080) @@ -13,6 +13,8 @@ constant MAIN_DLIST(0x1000) constant MAIN_DLIST_SIZE(0xF000) constant MAIN_DLIST_JUMPER(MAIN_DLIST - 0xA8) +constant FONT_BASE(0x8005) + if HIRES { constant WIDTH(640) constant HEIGHT(480) @@ -21,6 +23,12 @@ if HIRES { constant HEIGHT(240) } +if HICOLOR { + constant DEPTH(4) +} else { + constant DEPTH(2) +} + if HIRES { if HICOLOR { // 640x480, 32-bit @@ -44,7 +52,7 @@ constant TASK_DP_WAIT(0x0002) constant TASK_LOADABLE(0x0004) constant TASK_SP_ONLY(0x0008) -constant VIDEO_C_IMAGE_SIZE(WIDTH * HEIGHT * (HICOLOR * 2 + 2)) +constant VIDEO_C_IMAGE_SIZE(WIDTH * HEIGHT * DEPTH) constant VIDEO_Z_IMAGE_SIZE(WIDTH * HEIGHT * 2) constant VIDEO_OUTPUT_SIZE(0x18000) // technically a buffer? constant VIDEO_STACK_SIZE(0x8000) // used for dlist calls, pushing matrices, etc? diff --git a/inc/n64.inc b/inc/n64.inc index f347fb6..278c266 100644 --- a/inc/n64.inc +++ b/inc/n64.inc @@ -15,6 +15,11 @@ include "n64_si.inc" include "n64_pif.inc" include "n64_util.inc" +// Rates +constant CLOCK_RATE(93750000) +constant COUNT_RATE(CLOCK_RATE / 2) // for use with the CP0 Count register. +constant COP_RATE(CLOCK_RATE * 2 / 3) + // Memory Map constant RDRAM_BASE($A3F0) // $03F00000 RDRAM Base constant RDRAM_DEVICE_TYPE($00) // $03F00000 Device Type diff --git a/kernel.asm b/kernel.asm index 8686b35..211921d 100644 --- a/kernel.asm +++ b/kernel.asm @@ -408,7 +408,6 @@ K_MI_AI: andi s0, ~MI_INTR_AI K_MI_VI: - lw a0, KV_RES(k0) lw a1, KV_MODE(k0) jal SetScreenNTSC diff --git a/main.asm b/main.asm index 57824ac..ef7c62f 100644 --- a/main.asm +++ b/main.asm @@ -23,9 +23,13 @@ include "kernel.asm" Main: + lui s0, MAIN_BASE + + jal LoadFont16 + lui a0, FONT_BASE + if MAIN_DECOMP_IMAGE { DecompImage: - lui s0, MAIN_BASE nop; nop; nop; nop mfc0 t0, CP0_Count @@ -72,8 +76,7 @@ Start3D: mfc0 t0, CP0_Status mtc0 t0, CP0_Status - lui a0, MAIN_BASE - ori a0, MAIN_DLIST + ori a0, s0, MAIN_DLIST jal WriteDList or a1, s1, r0 jal PokeDataCache @@ -91,9 +94,8 @@ Start3D: // only the lowest 12 bits are used, so 00000000 is equivalent to 04001000. sw r0, SP_PC(a0) - lui a0, MAIN_BASE jal PushVideoTask - ori a0, MAIN_SP_TASK + ori a0, s0, MAIN_SP_TASK jal LoadRSPBoot nop @@ -106,6 +108,8 @@ Start3D: EnableInt() MainLoop: + mfc0 s2, CP0_Count + WriteString(S_SP_Wait) - mfc0 t0, CP0_Status @@ -117,6 +121,79 @@ MainLoop: beqz t0,- nop + mfc0 s3, CP0_Count + subu s3, s2 + addiu s3, COUNT_RATE / (60 * 200) // for rounding + li t9, COUNT_RATE / (60 * 100) + divu s3, t9 + mflo s2 // s2: frame budget spent in (integer) percent + + // there are faster ways, but i'll prefer smaller codesize for now. + lli t9, 10 + divu s2, t9 + mfhi s3 // s3: (RTL) first digit + mflo t0 + divu t0, t9 + mfhi s4 // s4: (RTL) second digit + mflo t0 + divu t0, t9 + mfhi s5 // s5: (RTL) third digit + +if !HICOLOR { + +if HIRES { + lli s6, 64 // s6: X position + lli s7, 48 // s7: Y position +} else { + lli s6, 32 // s6: X position + lli s7, 24 // s7: Y position +} + + la s8, VIDEO_C_IMAGE // s8: output image buffer + beqz s1,+ + nop + la s8, VIDEO_C_IMAGE_ALT ++ + + beqz s5,+ + lui a0, FONT_BASE + addiu a1, s5, '0' + sll a2, s7, 16 + or a2, s6 + move a3, s8 + jal DrawChar16 ++ + addiu s6, FONT_WIDTH + + or at, s5, s6 + beqz at,+ + lui a0, FONT_BASE + addiu a1, s4, '0' + sll a2, s7, 16 + or a2, s6 + move a3, s8 + jal DrawChar16 ++ + addiu s6, FONT_WIDTH + + lui a0, FONT_BASE + addiu a1, s3, '0' + sll a2, s7, 16 + or a2, s6 + move a3, s8 + jal DrawChar16 + addiu s6, FONT_WIDTH + + lui a0, FONT_BASE + addiu a1, r0, '%' + sll a2, s7, 16 + or a2, s6 + move a3, s8 + jal DrawChar16 + addiu s6, FONT_WIDTH + +} + // queue buffers to swap lui a0, K_BASE beqz s1, SwapToMain @@ -197,6 +274,7 @@ if MAIN_DECOMP_IMAGE { } include "dlist.asm" include "task.asm" +include "font.8x16.asm" if pc() > (MAIN_BASE << 16) { error "ran out of memory for code and data" diff --git a/res/fonts/NotoSansMono-SemiCondensedMedium.16.i4 b/res/fonts/NotoSansMono-SemiCondensedMedium.16.i4 new file mode 100644 index 0000000000000000000000000000000000000000..34c77aafaf88067aebd03d5be6b9884af4a5c86d GIT binary patch literal 6080 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 literal 0 HcmV?d00001 diff --git a/res/teapot.F3D b/res/teapot.F3D index c52a5e4aa6ea54f509778237bdf116bfffc77b09..490876546a1b954476a00f7eaf0470832f14e15c 100644 GIT binary patch literal 40496 zcmdU&3wTu3x%b!Hc4j6aC&rv1rfJSNCgU`QI3_Ws0a0tJRq=p&)M|>NpinPG1O%mY zP_80)1Fa+=7g4~r)IL}*)hgIx0R@$6MZDFPYH7tDL@{&Q>-)cJ?KOL5?TOLvd(QKm zZ$D4_^v&9PuXnBe{{HKI*B*91V)Agg*{KF=*lZYHH)C$E;buYR4>BX@FK1=textms zl2ufe8I=_wRu!r+LRB@ax~9sgGisT;)-Y<_VHOO#jc_o+$|FG|QeMw0>&uP$$|ws( zD~)I<#%f|Aqp7Zu)i%}{jkV1z++1rkhvO^~4;%4F3#)I57%lZ}EZSCYw711rG}dNx zHg&MZjwYj{aTseJ)@TeL7H7@zVZM0FaPnOGee7n#YrulvaG3d-VU$-?vNC_rs0dZD zs!I6lu4dI$A&jer)l|C;=C5V7HO%mr)v>x-zfoBgVd1(mqpG@|)kms~>Y6Bv)>j)f zwJ{cp))=+$yD?U4)P#*>+Hz$>s$7G1`R)R z`{7rxjQPugm_M4&D$FO%V}(&uUCX?+)ka;d!MsMT5w4@juQMXyauzHP8`1ho7OJc_ z;0vp)i5l>Q!8gof6RT^g#k|sd*5&52ttHCpqb<34>}YRfvBq}HV<&6sY{b0Md^Q=w znuoLa@MbxW$Mx@Tzt6uPfBSp>@#ZGh*wk#a#amcYOWf#aYiBL(ZN{*UPS)Pp;p-ee z%zmyz?B^ORD;LkTtg?btlp_x5xmHy6`&=t9Z}eO%s=_RS`4i7I-2Zb8H%G*Ct#1jl zc%<1t46^2MixF#&GQ^~Txnph7cB2vTh`BVH5r>@}O}^prJb#b3=Wif>YHO+)g3Q1Z zXYM+~s2hNv<|cZ6O-4&|oHfRqjdsLe9C6p)*2y|L!3p9HF({wo3AP{QWyCS&tGt2* zE6R;fWfiNa!n|4hQrsDcJ4CkdgW|0W^Hm?IWaX6+#9fG0hU#hha zS4+H}Me5^*iaX2|!`vC|G2%Cti@T!yw&p))pvj0|_-(-ND)#a zkm0XmECk^pers8sQC1Na{8oVDNVvkNkH!SYQOsLoENV2xn^`mF4|B#ahelgRJHs3r z9m6_VXM2ZlcxT>oJkg#%om-m6vb=e$fnQbdr$27>c_e;A;1?RgpeY74gh5k`{^l{K zgB`?8hcT?PnKdwp z4FeA?9Y!=Z96St@arfV*#1S4ABlo(HN4l3=KZe@RTX9=iK^$P+ z059d>MLlPo7sV~+3h^HWH}sq$c)qkwHR8FpG0Yvf=wO&T19QbNcfR`JF}weh(LWYj zTEEK5q5nbXRwdR6qujt+%b>lGiYkV+*YMUN?yG7Hf7r_qK}LDR&uYD418Xe{`y&R{ zTIe0-D;8o{dkw6$3~R4}wU%M+HE6!D_8N})vUCn>t1S|2fDq;HtN z{`9Rrig;@Wr;Wqe@L^Fof8TZb5iBngoR-u2iXfzXiPqQt@+DnwDPN+zslR*)YlPqw zYXtNI>q-n<#F5WnUBw(aI7N;pIHmO>$LX2&bMJ3mQ=Haf>X5xbm)*?3I)S;uIt4w$ zx`4Seuof`PT`t~Q;?T49cy2w#Tp=!p`Dos9{hwve8lDqKy-vBsI#p9-ovMPq z(K=NVtrq%*HI>DxqXu$07OiO%c_wl@!!d8ji)r2(JEE)|`7v@i@Y-RJz9F|Wta(Fz zO3ynkpZ7VozWC`mLtjXb={Zv#CG;3^NBJ?WuM~H6;RqwYqp>SA9+X= zc?i}6Xk2bR2zS=z5{@ z${6A;guFZ&qI@Kj%SZG)Sk)EnZBh8$X0%5sABpDjk)nCzFsK{?XS1 z6=#t!o0#)^@6Tzji_U)QNP5hsa~kg z)eHUQ9Ub5@hCH`DM)g80S1%OJ-!91KLgwj}RYAm6nL+D)S+Gj#3D$dEPe{)wPgi<| zxe|JYH2`@V*3EXTqfJ;hJ4r9vk=G4_UUU}f*@YC}Ua@`>m&}WmQ_CL}mwNwYVZW>s zkayJiQ75Rp1M#Kuj{N!$HI}FoXdOkY+3P6uOywQL^sH#!ak2fpRh;R0y3(_O;!Noo z)dvH{*(G+rLAReVzdKi#{%?u1qU&e(z;Q0}9W97A#D8Z8)+yA>!@xmPoOHEG>gwoXaaSqomj3n~sBWQs2Z}qYTWH@w z#NAiicc_me&&9q2)u%0yxTsI#l;?KDa`h={F0mfSe7A+xQR(mHal=&(B08R;+@heugQ9rDI# z-a2qa|8X}^-bni)?l0L7(d$yZu2OZW%GcC>2;HW#?g*_hN5+-^mly!{;K&>`wvRj)Oz_dR;&k@H<8z(oZmy|CUJiY$2}3=q zu}_G+Hd2fI8mb47-{$H8x7_cjZK*@P?KWz$zuH#UVnnbn(~f=CXd~@ok^YgsArCH= zZ(slQpQqXn4%ZT|wYmLEmA5(egBvl9TIk6D`@w2INyT4LUMJfAs{BdinTnTCCGEGO zp06fOX}^`?iTos9X`fZ{+T29>Xp_+#inE${$cWdpu)3BSqouBmMcV3&wn#gRwnuXL zq}rD$*iW6*zdw53TZ_~iSyHZE(fL*Lr}ExL=yHhkcz}H*)Cky5$NpOzaYFlqMfokP zQzuhATI&S%|AT(o_xB^8^BY0(6M1m34D|y1Ei1=|GI_tbts>2MxM~z4n1l_ zUMTnF8uj-R(`Y{X%TGgMf3u3-PlV*Y7`>m+^90%#qdcJ+`?0hyRuiknzD_;%cWPLD zbxiDwkshh{6RK{E*30>55BPfUW2-+oc1fQ-&cX%c+RHuabXkf#&uLuq&C{ zFVNqo`RmF=UR#0v$Z%PmsH5tzA4%_BsE*R#r!_Up_h~I{O$w_t(B^9UqM~iuh}e zN1@Nyhrzy8ENbstQM}Q6jDmQ(z5n=Ed6?KA&D+oTy76C!_dfJKITEkKdy6KqkI+;X zN4*8T#`_D3xBT@FS}61yTF9V{KGH?nmvsI2aD&}pKWC+j`o7WE)kVw|-oNAhMHA+$ zxkN5~JI=5l-xS5XHMZb=BkFVH+bzhu zqT;S#FDmlh+p71Cs@~IoFQC>5y*}38+gkZ{VV=0eo=5wA zWAXQWHMMo3ZV4mbu0y>Nt*^yC4Awc?_ePy8_SKNTi1$<2SEG80-sceyR8J`$==~Jw zul9aQ>~|ygmOR{7%&+3_S=4?v{T@W^(~I8?M6gca_aN2L3RYhw^Q}k)y-%xQQRG{y z{-bi_=o2K74}m6xe__|m*#8OAX{p6jlu6nQSqdsU@;zm-4l>b(-Z zzo+*jjZwU(A)cC}m|xTbwC@#{b#*(|WAXmO+VAT3{YNkP_5YU7(C-RVK2z-XK}yf- z@cx(dT)gMPdtg0&RXx+(g!eVrUvCn1H{MHI?`zoIc+e|W<@8B$Wxb#C;hhcX{`>S_ zIDF#+J!V4w?~k^Q<-Q98yUjV${@XU#J%Ju+G4{``3GSO8*sbjS+Y;QnC(x4>Z=xA{ zXX{zqyDhLgdxAV;@3ynKCl%<)&`+yyyu7uQdp-{A&QwWz$F>u>8`r0;>z~+q40pd0 z*q!c>?Z4jEz+JZndQ!`zy?kpmcl|i9JM}Zm-d4jIL;KazPHe3rdw1$)%ib2^zV8PP znX{$6Z^t<9yFGA7T8#aD2mId=IFuD{2pM~8$2r{dN#IbXQl7DWM>}`F8aR}0m-gd3 zqTDqza45B2+PyozLH!*{O_O$F$6)TeCSaOBk+$e>MZlDn)!*9zGaHqE*-QOp0%iu} z(y@Iz`r8{Y(+$#odfWruZ=s zPts!Sz!t>&4)8Hf{$>A`E4l9k#IqGa?``=W_nwaU{jF@@xuu1B1|c3_k@k~Yj^?iA zu&wx6u_Ywq)#@OzrIMQoJHE%lA9Ic!-($Z*d}m`eKWAZl*%NGj&c^f17&d>c;4f2U z^LHZn)7#UypW^u#@T{Mo?y&i8Ks=?E+4@n9=ac%GtsgbekJQ!nd{#j}Qa9VrHw69A z$7{~E`5GtrD&Se?O&@PIZu5E$c+FJWJhy}Abi2)a6#S*u+xqYg=tF9n&Cg)y1Ntk7 zXY(gEkK|uAYV&#yc+Gfip4-85y20kXf%?1O)(6qwSX&>g{tl(`crkCX`J#Bp4z_uv z=bQeQ%`-jEbg9j|cs@7Ve2M4rU3+{q|2m&~e>xxL^)^2guev{3pUoS^qr<;H*?hKv zPlvw;Y@R9p5HC4>5dL3m>w$;M4Q5JpaUPJ2>fq*Sh|!t?vju@NSdsXK&jp^u{+{ z+GDqVB>3}PXxZETHsJX*pFT?=6I=gD^Sj$OUtXWswtv9!>3k7?u9@=sZ|vxGjF)(I zzbf0KJ05q8m-zO4BHP<{Z0E$kcZamk+3^-9J@I{C+T(WY6Y=i5UD}Bqe-}KuZj(0k zpVD|Fe%*Uzdjs_kzC9Ufw{J&(;NSbUw9nar`Gr3Cu93FYpXpm6ZP6d%*|kvG^t@AA z{1OlD{j!~&H}T^=LfYc_f`9Ml(iYDX`k>~2ES?{p$06S{vRyn6@Zwq`Z4rMOA9{Z& z@aIm;^C|v_zXE@?`|19r!MC?jUf;I$C7}=b{*pfUj+E^bpQJbW{*%7=Hpq60PklTh z-rke#6o15{Yq_*TTUKzLe+Qr7*)vF4cTYq|8lfDg)F6>rP&yeR)wygq^Xp!`?y`fKn?`Lp8v zW$;eBT#EUX*_;z1WNRq5NLy&qvT7%I}rl{0;FpP<}tq_2PNt@vo0h zJik2ND1TNw_JT)!yp(S%ejf+F`gkcnSG;cr@A`NtpI3VE7I>xnU+K#}@U7?n@UMVR z%C{B2!awA{itoq4H|6V!|Lx%4i+o<`!(QqS`M=T=(Vv$8i}}dolk#U3{~{ick1Jm2 z`BOfw_!rL``LEIk@qCd#D?Y{Z)beZbeDZjq{8#Zs@k#ly;*sLd(LRtr$hQ^mFM)T; z&kOuRey;R{;*;|I0)LU;D}562srm2I>H*596^|<7P1 zvi-x2r#tEg?+X9ov{m1|zVSFmJ>famzc_V;y#CpZM>*;X*K_{G$!BHz%8i4BKIP{> z_^sCBq?L~(Hv0KM{u^JESk*IQ**Mi$ zS~5E=b|4tr(|at}$KzR8GCOHG{CsZ}*T>^}AMM|h{jcsV!T1Wt!x-8FvOUr3<|Shc zE4yHfFG%o`I%B43)r;u`qq%W~F*9?TJpYpgr*PjeV`l32(%!qEfqR}eW+qokd-H-L zxGQbU?E8zf*DUa&f7q*}omjwl$>Tn|ziH@iqHku_%1_eM(BGrJnVFD0|C4FxZ@O=0 zYJs%(PD6kH@XbtKB<;=9j^OS}-^{*yq`hVu^#^-|v=h_N-^bp0(!%qZfc_r#&da_p z|B{?=88`NN=cTRr{CL8t+_%a*FEvKC@0oBc_Zr@LNozmi`3Xll+6VH-eYAI8U$?y9 z>IsI!Km3+*UZ1t!lbC?#F@V2*=IN1lV=UE;@jT_}$?lbZIoy4@V?4&ko*uQIvA_E? z?%U|;Nu4jx|5Nw3pr4+eq}Bg(-68IddV2b-_*vB*fWNS<4inuz*k4{R=<#NL$L4Je zc+=~N%qE-H)4{7=U!=#{ydMYN9reZ)wq6_sz0m8AQk41(V1`A+gyTc7;UC!G)T zXq%_Y1W$$hIL4=qCvy|dx8mzm@T!j|U2gM!EO<|8<4LA$y{Lj-=;KL#&(@m~=#4&} zWWd%ZH}py8(e&B8js~xp(`=qk0ne%5+q^e`_vA`j4~~Ey^!>%w3orDdZ2S-2;`tGsy!VA6V8)54SgP!O-W#6~?yA1rL zQ#Q}1g6Gs2oA+bEdopV4!I97dM}OV6ei+aX-9N|mnty#&wjQ~mM+5n*^OxOg^LaV= z)W?(lm(BBO;8`DE>U^93Z-M`0m8}mU=tJN4Y&{7;Px|uo#s|I8>j{cS*K<-J25nq9 zAYa6<$|tD4pm_J3E3ZFlgLv^;_2F?FUw70Wq#wSO^7_*^e(0!2h)<(SwvXBP zDdxK{{~^6H60$w9@e58ox!#v{N$+Z|^W)%4J6|6U@$0eb*{a^>1;6?8rH_yF!27A} z|JdF=Tpu6l3EpLirQI+M{Xs8`N2Ptrv`;wcNy$WMkDf;T!G2uYiD~E$yt=J? z%$Ts6>--U~;-9wTk|QTPFL?2-lGh(Q0rRi*N4y!)8*ZV;jgF1FHdKQ?v%){^Xm=DM-^`?zXET1{XzMz;`v$dtk)xy zFDpHG9eUuXPf$N7J^2uN;;2_pe<=Pwg z59R-g|2^QJ>WBRCQ2n6vx$PkMR=urTJgOZd{h3f_}>fusXkEp@Co#R z>IbDKY3PYwUr_$7cv~%aE98svd&Tqf;Mupz=64VHr}{wY!^hAENBcnjpx#jWlZ5{0 z{^|L??l0xzioaEYze4|Y9`*51ey@0b4m?x-uXz6xc&GY6>A`;J0o4;qUk*cG2C6qo z_PSrRiF#y-`^D@&`Ik>ejuH3IuRn~J+%IN4vi-x6U5J@jb`^7%%`jsQ8zSrv)v|IX_7#ZLtGu+nOU&ao0!T%2T;H)X{ zf1v9MZrto1oN1Nz2VG}4+x78i{`*F`2d5sB*T33zJlFmA>~s%KTKj`fchzy-f4AE` zxNn=h{=u$FjHl3l*n|7#%JxK8xpO>B?Iz*(2}7|zvBkB}{118l--nLnM!9QYW|Fku z9g5#4^tl$MErEY|D1MJ{muq3t%153U8sV-FV4ot-Up{m&{DCb^mKcin!*gA=mdQSB zMSo|zYRyySU;fgH-xsWP)n=^u*wZ?Kdsn+^)0fNkZLKG9&nQ=I>Ic%^)Osv;ZFbcr zpOAKM>o=*t+T;vrCt8o*Am-23#m8?ScwkG;Cw`}N(8BOT+x-d*l}`|lcuImUbWCgMZ!s zYy)uy8qj3J*tEr>Hhc4we_nU`lXLY=hOTTo8PhEH#5oR`z-LC_S^iog8$^F zwmw9l56M$({TK}WNapEF74*eC)#i0v5neNo*?f-x-|5S3{!aq`sUO(-a4hs8`Gl<> z--Lc7XW0636!ZnG=XkwJ@LI?x*w69(pWqwp=lJgg|6o6-563|tz4ouxtKOJ?$ERPf`%ijeSic85qw52%`%n5((jo7EMHjt4E%cxG zE>Y`W*Fnd4h)>rC^7_F;mpjL&@k)Gq?vm$6hT``W;NROP?bf0A{RH&Eu--?UHS}Fh z`eAI5?PG`j9sao%mPixp|33wrCp^6UKi zgYwgo`{em6zXET1{XzM!@tVB;8St#vBb5L99+d5W9QnGVKB4*nzu*?f-$p*`s8^`| zQ2KRnWRdzst5+z$F6or_drY za(z5hZ+PC7_y6F#zY+CR{&=W9asQiaKhU+3JL)g3o}ql*_?KXiU6-#7kU zp8o`Rr+UEWmG;Zf1F9E1%ccF!&?iMb;j;FNJ{r1$m*nXS)f<$*8|TRLdyDW(`Mz(x zY~KXlsUGlJ_BQAN)eD~Q%l193kBfT3WmTMifxhVV2Ib$1zaGJFA+MD0`!108`xSVn zdcZqI+FPLqR4;fU(taI!fqU(AS--b=4|<}vQ+@Mg{^1$czz5#>*Jw%Knw3-Mbq3!#Vy&)$Us}ohKjnU&yWc+5ht!cwd+Qzm#pPoy?Ov{foKP zL-4U5@xEdHJCt3r?na*coB#Lf{IbV!eVhLUWtTjP>-+rwQvD5j68;SFA5gac=hNwZ zgmRA!dK&&6=eMeR?2P{+!M`}kZ&iE999F;{;*Y57Jr7RjUmWQ_NA>4jImOYgk4N|a z@In79H6GvU$qxSyzvBP3y1#GjREPhEf9-!y`Qv-|W{3ZWf9AJVE9`tf;vCQ6OG~Wm z3z@^R1V4P1zf9fF-E%WPe6;@r)xW2AD*ya||4QYrXT=o$`BVOTRlE1WoA~E<`=3weg4OFmHNyYIi5e|}>LSNC)O6!$y9A65OiADGHN z`)~ibx%Oo@@z0+3Uz@wW=NA6i&-~NW{po$?=NFbNRkrJ1*r$~As`FjFVy{9 zOK`uvxQPGz&~9DfT3lp2J!rRhaxE@0zESq|Y%#_=IIq7&7%$87XAyV@Uu>2f z@t`Mf7xAfV|Igs>#H+G>YZp4^L%$#C zLH>A150vd&y~r^h(i?Su@5(zI;~{-g{(2sq@6cae&xlubzIWAJ$M~H5`tQy4|KROH z50t;26?X`|Q0<=Hg+fo%{oOs7KTU6lU%P#-gJ0rZ-Os%Y{Xq{@f9?ks3cbj+{}k=e zlidC8pXbmU;#al1mZCqMSMaXx=UTcz=z;3bwFLb^FLLcm&>!?9cfWh*I@<^EN9W(+ zANW)EXN!vXtMN#BmGh7M2hYl8i=E^740@0o-{OV*Gw6pp-?bRygWlx&UySh$q(|R1 zekp0if8+NZpC50m&*{~C=!dRPea8Jtuh=~3m99^}GIHx9n+v_t_30Yphq>$L6w#|T zMJfA<}%dHpe zCakwN`Mhd;t|?gW&-A{b#^<^b?Gf)|>VB>pu>KtBU6bqY2ILQ8z0WG!H4*lu-k;^p z{}J+uF7K_m`&~DcAC7s?R{goJnZgg3d5=`>?(yjVuxG!z-hCDPKj7J;?(e<|`AoI9 zRJD7?As;)+YyG+a`3$xHyJxSupZ7}SXGeIJtNy%~Pv&0?@{CsPzV4~~i=bzSx}R~$ z&HM|$Czrn(-$#Dv_sq82r#tq$^#1kpb$|N4;W=CNUvd%r(Z{3t+t=!0%KyOmH#+?9 z8{vLJ-7k19?x&5X?{fD{<$vkfm@j>NSG#lZQ+5{e7hQi4zd1da4L#EJN8`((Kj1;> z3!4Ky()90_>c@)!D(vu9!b()`u+O81}mRM)$1aE?dkRUaSm zuKai1i1~#cD1Tj3u>L_WR6Dx~?a&w1ADae$b^XzJC7#v!?yHawfOmC2_ju$B&;!+< z`x@jE&x(@Bo6Lr1oN4Q?o8$F&W{?+-$CG!L?>VCfNg+dQhf8NU%3B6G5-Yf4A zJgVzGG<*}hHYA*sOTjENlWo0J~&TN2zq z%=h=4{S&<3-Ra$9o-F^8x^@irU+;^X)_!Vc@_1?=q<&9+__ER5-{YNUTJICm*Nj8E z=QMTwq4Tcf{y%u$QP-!goPc)s!^*#dXI{_!|L|O*{7ZIEqV}Ag(ffOUpQm2=m%Jpw z1K%>v=GO0<`JBl-@bAWrs-4e6`wx5{n)Bpe%%5Dx11o&vOzYo2XQyA|@F(!T_XBl) zb`t#2#^bnN^EX)OovHjWub<-ZHyHI^sQfdpE#m*$l4?HO^7qc0oa0%He9k)fT}8(C zj`4=M-MW6^4Llgd?{}^D;pV&_@!$+{b%C-hfvQ? zkblW8o6ZA!@jW~1$o3?7u(f0_A7r)PKZOV9mTWLTvFs(2dGO7Wn^n7c&lFx7D!D-2 z&%EnqUV3~i2Kv-?D7% zUzASt|5CMQR-pYk|7vx=Oz#X{`mW#F-IV{H!_go9y&1nx-Y>ashO_^j_P^&&t}o)x zb-3Po!pTRbJNzrd?^F4y^8Tr{XxIELTWVjQdIB zr}IO+xvc$zgJ<6Bh)?3rWj)Vi_nm@Ym$koq=)4tz7nk*Wiqw@qMZ0&9`aRg;%hm{9 zT>Gr&aSirwp&#n{)U_*xeyHm+lOI64Z>sVqJ9VkhC)K|><8EsIQhm~SBL0}QUu|B0 z2Pd9gw@81olkOG#tNq^W^!tQ9sCM%wOC0T_clrL3e&+i}`lI^ea~3)DQ`e(`{MGp* zKG_S>e~x@a9}n^DI>~Ck^-jUNYqYe@dH3)D^gy+n3-96q=!?3ac_;3t>50xG@yZ^s z?swNb!823q$31rl-d&ZlJ-cL)&;!+;y&v-r{ZQ9um(6$Rht4C#JDV==m+74&_+Zal z_KF39C-$ba)7ZZtJy7lGRSSfEsO!^f<~sP*`5>OywbuRC%;tLgK>iS~>}T@&)I;+e z{t@qNv(>(KzQf;A=!LpI`3Tyfe+B+SkCcDOb#ol!3F>;J=~rny@EdMDzvO!884vtM zdc#<9LxNNP1$wn1@GQSno{@ayX0Gd1*%^W7_!-uC9-hv1y(-%m*rKi{{nGVn(CEMm z${*6NL5l;Hx-driUiyQ;F22g@AO4m$1#-X75dOXqc#V&@+SkFq8v}3f|5E;-zkdt7 zrP@<#Z{dR$1m0EmOFxAEo(jAt&+ezc8MvSIJd^98uX_LI2L7n7FXm6_0rYR}U?i_M5kCcC@BI6nKL}0)2FZ~eaa~|gN*YYo7JfrjG z6YE#$CxP8+eWdlSY--?5ZvB2C^)UQTWB#o5IJI^LA2c@bfx2Jnp_zQpqQGAY`nxDE zzVJS&^p?Og{I^#B>t^!OHv+#^>sRs-^mjJmWxi}rJq-K#!0%PN=Ht`H1JuR)3G6zZI~1^6pRT?_$fwc=Z0t#^#-m`E~fGT|Y?kuWX~;e|in( zNAtHVk5`I!U2hzEr13%gFe@KRL$7rGA^zAS*7%@Dy8aNq>^EvY<_rGw^`{j4tLu}G zK<_pEAw5z3C)Y!d^2Q_bAx&R&-iSvwM)se62zrb7XUi;q*3K2Yu%9dcpr_znwI|oX zhCZn4seheMJ^lxQ7iQ&SDe4b=vD^FakN8x-m`Xi7*WsUjy~987ukN2*H`nPe^hNoT zTtD04f2pP~4*$U)v);F-9=eS?#;fs2{HpaNwRVo+o8@?ef8bxW)A~Ysq3$pI)AWMk zotzie_$-6UlpGm*REVI;wu>{BO zA&xA&jawP(Qu7whzJNVZUU9EEm9v*%pD69S%o{kn2X;W(3(e~}JGbmM^MJJHn%C0( zZ!`ZY?Sy#^ca1OW;#LLIo0-mCQ_6Dn)A9`Z{|?%%=d&y`iM#w|U1p!WerX2x`z`F} zqx+)~RM#(J~3|7oQMxTP;WS@a)z8D%<7`9e0rSt&UYwo4*`=_bm-hVZRp<}4HD8G=`ae`^(P_=^KT2=s*7;&S zcf+0|ufNxv!r3oiUm$HUpFf7(EbTjJK2L*f{d;pcpRfnXcAC!t`}<{Sy*!(-yXkpv zD6Qw#@0;#6XK<$b^8nh{$t&(Or#bzbiT2sDeUUktGu_`y&~EAb0&|kn|Krha&G#I0 zBF$gnco^ExQ{zMX&frevzlhftgFE?l`4=&tzk+S$hax`jfo;X>oz(vX*dyinB0f)o z-6?H3pRik{P4me&+3|Zzk@(el;S=rnpNi+jPqg{C0etWPY|EeP!H; zap0XlZ}TIcXJNeYIj}9>rqJ`d0Jg=Wcz(^WEncq&uRH|X;+dWwA7t|`pI>48^4IKm zpMiMi>tI`aOhbI~*|061CW9wF0k*~8B=Cp%$niQ6yz=vGo+pZUL3*@x2|k z75_9J4u2j%yTyx$XWhSvXt&~d68W$DI}+^{j}-3?|2xrc@hj#}8xL=_d8hf*<4xy} z;+35%`MuVhD|ln~Nc(zoq2Q6dB<&l_yEyU6zL54*^IpNb>qu$eVlJilz0LJaX(!D4 zxQX~@<_#!YsI@RvxGbR)2=7IDDV&bxx(`PH)en8 z@RxXXS^JmMvpo*~iEo$nJQCSn$9RYzW?fIuOCO)k69<3n$I_qc%{v62>=)7&&lCKz z-O`>y&l9|}e@I(CPuPc~P0y3!ksUAXiRK&!PZZDW64^e<#6Ji}{Ii+To@_1>yr})4 zY37}rcvH_~hIu#U1NJXfe|K|^cxNX`Tg0ap-yFQCdPKx4_)_sZiS7>`*{|gFB3{8S zdr{if_zLDM){uNXCdaB@Tf>%Air2I|A z?|c!zh4D)Ho+~WpcQN9h@vsnI@-#(x}$j8)p=E(Rh;6;yThrh`86b}m! zuMYo_FDiZ(fgj2@3&w+dQ}K2uc%%GO@p(7+WP1LJ{7uEboKM(0<@2I=r+iQGBH|PI zpo+&k$v@`Kaq8oPU-) zV^s?Egr1*rw7X<`PYU&fp07R`yhFY3T9Ue)Gd+JDgLdnC4GUAZe5NQm&8BeNwQSUn%{2`rt&zc->L97KJkjn|b8cAIa=Gd3N(f-`NruGMJ2 zT(&=Y@Ivl74z@?y4;~!B*@MCEtd%d^eeewEE9}Q*d*Wah_Z%N=<<@%r^1f^EJ+fe{ zY-Q|~eF^S$Y(k##<}UpEW!+!xe!732S%J4Rv*h)E*wx|i*L`c??ew#rZ%OR>hGRUwy!{ON{<+VK@AdY|{^0-u3aC9<*C}F#>v# z-EQm88PK2XF2nczR$VC%zB=tJf;TTfb{ zCz(yQp2VRi=&yiJ^Ie+PsVJrRV8^_+EO()(i2y@x1+g`d+%8j}uR9jN%FP zqL!bVI!{`@PVvj0l-FOLS|WH?`G&~f5&sOMrsJwqk29YKKkEJVb*WWc&(BG3T-FLZ zHMN%FuQ1<%KB;^;k$RX9biO`b;)^{f{X65}-Kcj9^+V@PA20FhI!<0c;^2dV_x$k^ z@2=Id{lbHf3cbi5FX@FVE8DL)xQUa#xUG6=;=!kByxr~*vORI|8Nny}tF&A9-RIzw zcy?Lq<%>lk@` zbk75h_$FT5$IABjo{fTck6YTq_iPb*;CWQq=k0lglU{fSOMCpDx2V5T@5$0m?0JW$ z!IR6%SL$}*d+HiLY4GU2Rh}Q)waL+620VLa$@Y$2e-J$9`=?(|dX?`V>4~>XUO!^j zn+|_TU%W5K_HnyDaQIJrd#&G3C3fv|j3)&?U02EL#rN{H@uf6g)8N~^R-P}upAY^$ zRy{1frw)Da&XVood+N{+_3uHg@2U6T7o*~!@2NZZan=(BJh3r0e+!8}y}m%ct>SqJ z_@(?_@h|EPJvx(q17{v>ovV_j8Ef@ z@^8iC-Nd6dUdq=MzYl_6%J1{XOZmOx|55O-kC*BLr5~H1A5?EBJ$f2?q}LZZpOmjF ze(wXnl+P=^*Mo1$_Z9z7fq$w8ls^0(`at!A(vz2=Cwl#$^GW%-;`agYOZmLwXCwHg zd|L6p1^iPzp!jHh2Wi9Q}$@AG+dAwO5Vitnc*fA`Lkyo&GVBfnR?i|?oB z>4EruI_d+ZC*u3*TK(X@m>JC4Pv00=#NF4ywvNXFl#kd)0&hUwM=`@SRQ_#4-~sM> z8uruDUK3cu(PrQraXb*%z;SN_eTZXGU?bhnKp)~r1RlfuF-z?k+ZIB-_9+|evaWwI zw1neZd4t{7?|+{Qt>x~IV1Fd9-xS)!(Kc9pPj7ALIY)oE*I?XJ97{qkI{b57ula`y z2dj8agtiUnukRQZm%TCedKmxzTc5MI>k9do*TVNZ#^b$_#ofzg`}Xjoj`4XOVR8IN z9M6WIUS*ZtG~J-+U*{_pv9|BHMtpkO>0SdRiQ z(fGcD7ddik{NRyaV)Hc_e3@Hq{>Fnp^Guu1(cm-tk7W}%wLYT^T1pBQ=7+Az+?K~Y+g?Quc?_f&oS_vI@{*G0lb^zZGNsU!cQh; z^EL{+rC+ppJQ+Nu_S(E24_;H>v3YI)w-_Xv0&=zBE!_iOa;-K4bdBAB>3OqQH2;2N)5@=)h>f56%(C_`pSiFZ3af z$$|3(k6=U`iNHuld{BIY8F73&be)KQFd~lALfwK7JY#W;2#w&xkNW?U=nkDGc*8x! zab4)!f;YAQnF!(gYFhl^|JyK*7!*fy_=h6?5mVx54__$a9}I}&^ziAN;th<5ZKfe+%iI@aLu7Y$RufH)Gdh~T5ZU;TS+8ZUZ0Q~ZMwanSeP z^zXwFKR8Dm^gTKK`*Ub0_&yzMBR^i{mGZxd2ZsDi#rGn_H|1|C{vSa6Q+}uTSOY#N z|5N;I06&yJD*iTtKRth>{7c32k|Oa;`In0SwTOSL9fMUoZ2}*Z|0#Z+13#1>D!yI> zU&Zpr{PuzTq5Mh3|NS!l2j}yKnJVDH;V<$r#nYqU$>BfpH^twx;7=b9<$sFT?ckO2 zMaA=L;91WnDgRRZJWzxm^q=ExBY2~HPVu-IJW{@==64r(rF>BF{3dwT^F_*ERQxZK z@m~<{l>aDxehGdkUs62203IpdQ@p+kUMc@leC!3!dcKJHE{I>sm-4@FO8J$FfBOC@ kRw!bv?u5XQngS*!7{d-=5@IKe!Lh+H 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 0xFC0 { + li t5, 0xFC0 +} else { li t5, F3DZEX_IMEM.size +} li t6, F3DZEX_DMEM & ADDR_MASK li t7, F3DZEX_DMEM.size sw t0, 0x00(a0) @@ -25,7 +30,7 @@ PushVideoTask: li t2, (VIDEO_OUTPUT & ADDR_MASK) | UNCACHED // most commercial games re-use the yield pointer, so i assume it's fine: li t3, VIDEO_YIELD & ADDR_MASK // stores output buffer size - li t4, ((MAIN_BASE << 16) | MAIN_DLIST_JUMPER) & ADDR_MASK // initial DList + li t4, ((MAIN_BASE << 16) | MAIN_DLIST_JUMPER) // initial DList lli t5, 8 // size of one jump command. this is ignored and 0xA8 is used instead li t6, VIDEO_YIELD & ADDR_MASK li t7, VIDEO_YIELD_SIZE @@ -38,6 +43,8 @@ PushVideoTask: sw t6, 0x38(a0) sw t7, 0x3C(a0) + nop; nop; nop; nop + // tell data cache to write itself out cache 0x19, 0x00(a0) cache 0x19, 0x10(a0) diff --git a/text.txt b/text.txt new file mode 100644 index 0000000..fa310e3 --- /dev/null +++ b/text.txt @@ -0,0 +1,12 @@ +Done with the battles he once +waged across time, he embarked +on a journey. A secret and +personal journey... + +A journey in search of a +beloved and invaluable friend... + +A friend with whom he parted +ways when he finally fulfilled his +heroic destiny and took his place +among legends...