diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index 8ed1b8c1ba..c47b6ae02c 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -184,9 +184,17 @@ goos::Object SimpleAtom::to_form(const std::vector& labels, con case Kind::VARIABLE: return m_variable.to_form(env); case Kind::INTEGER_CONSTANT: { - if (m_int == 0x70000000 && env.scratchpad_type()) { + constexpr s64 kScratchpadBase = 0x70000000; + constexpr s64 kScratchpadSize = 16 * 1024; + if (m_int >= kScratchpadBase && m_int < kScratchpadBase + kScratchpadSize && + env.scratchpad_type()) { env.note_scratchpad_access(); - return pretty_print::to_symbol("spad"); + const auto offset = m_int - kScratchpadBase; + if (offset == 0) { + return pretty_print::to_symbol("spad"); + } + return pretty_print::build_list("&+", pretty_print::to_symbol("spad"), + goos::Object::make_integer_no_hex(offset)); } if (m_display_int_as_float) { float f; diff --git a/decompiler/IR2/OpenGoalMapping.cpp b/decompiler/IR2/OpenGoalMapping.cpp index ac8dae8aad..d4dfa4cb51 100644 --- a/decompiler/IR2/OpenGoalMapping.cpp +++ b/decompiler/IR2/OpenGoalMapping.cpp @@ -18,6 +18,7 @@ const std::map MIPS_ASM_TO_OPEN_GOAL_FUN {InstructionKind::MFPC, {".mfpc", {}}}, {InstructionKind::SYNCP, {".sync.p", {}}}, {InstructionKind::SYNCL, {".sync.l", {}}}, + {InstructionKind::CACHE_DXWBIN, {".cache dxwbin", {}}}, // Shifts and such {InstructionKind::PSLLW, {".pw.sll", {}}}, diff --git a/decompiler/config/jak1/all-types.gc b/decompiler/config/jak1/all-types.gc index dbea8d6e75..1693538901 100644 --- a/decompiler/config/jak1/all-types.gc +++ b/decompiler/config/jak1/all-types.gc @@ -7673,7 +7673,7 @@ level is hidden or uses the special or special-vis rendering path.") (deftype gsf-info (structure) ((ptr-iks uint32 :offset-assert 0) - (ptr-verts uint32 :offset-assert 4) + (ptr-verts (inline-array gsf-vertex) :offset-assert 4) (ptr-fx uint32 :offset-assert 8) (dummy2 uint32 :offset-assert 12) ) @@ -11227,7 +11227,7 @@ active channel." ;; - Functions -(define-extern invalidate-cache-line "Write back and invalidate both ways of an EE data-cache line." (function pointer int)) +(define-extern invalidate-cache-line "Write back and invalidate both ways of an EE data-cache line." (function object int)) ;; - Symbols diff --git a/decompiler/config/jak1/demacro.jsonc b/decompiler/config/jak1/demacro.jsonc index 8c687cce19..ede16dbf96 100644 --- a/decompiler/config/jak1/demacro.jsonc +++ b/decompiler/config/jak1/demacro.jsonc @@ -443,6 +443,52 @@ "match": "(set! (-> $font flags) (font-flags $*flags))", "rewrite": "(set-flags! $font (font-flags $*flags))" }, + { + // perf-stat.reset! is commonly inlined. Anchor the rule on *perf-stats* so the method's own + // definition is not rewritten into a recursive call. + "name": "perf-stat-reset", + "match": [ + "(let* (($stat (-> *perf-stats* data $bucket)) ($ctrl (-> $stat ctrl))) (+! (-> $stat count) 1) (b! (zero? $ctrl) $done :delay (nop!)) (.mtc0 Perf 0) (.sync.l) (.sync.p) (.mtpc pcr0 0) (.mtpc pcr1 0) (.sync.l) (.sync.p) (.mtc0 Perf $ctrl))", + "(.sync.l)", + "(.sync.p)", + "(label $done)", + "0" + ], + "rewrite": "(reset! (-> *perf-stats* data $bucket))" + }, + { + // perf-stat.read! has the same branch-and-label expansion but accumulates both counters. + "name": "perf-stat-read", + "match": [ + "(let (($stat (-> *perf-stats* data $bucket))) (b! (zero? (-> $stat ctrl)) $done :delay (nop!)) (.mtc0 Perf 0) (.sync.l) (.sync.p) (.mfpc $counter0 pcr0) (+! (-> $stat accum0) $counter0) (.mfpc $counter1 pcr1) (+! (-> $stat accum1) $counter1))", + "(label $done)", + "0" + ], + "rewrite": "(read! (-> *perf-stats* data $bucket))" + }, + { + // DMA accounting is always guarded by the debug usage block. Its high-water total mirrors + // the cumulative used byte count rather than accumulating independently aligned sizes. + "name": "dma-mem-usage-add-{{symbol}}", + "for_each": "mem-usage-id", + "match": "(let (($usage *dma-mem-usage*)) (when (nonzero? $usage) (set! (-> $usage length) (max {{next}} (-> $usage length))) (set! (-> $usage data {{value}} name) \"{{symbol}}\") (+! (-> $usage data {{value}} count) $count) (+! (-> $usage data {{value}} used) $bytes) (set! (-> $usage data {{value}} total) (-> $usage data {{value}} used))))", + "rewrite": "(dma-mem-usage-add! {{symbol}} $count $bytes)" + }, + { + "name": "dma-mem-usage-add-enum-index-{{symbol}}", + "for_each": "mem-usage-id", + "match": "(let (($usage *dma-mem-usage*)) (when (nonzero? $usage) (set! (-> $usage length) (max {{next}} (-> $usage length))) (set! (-> $usage data (mem-usage-id {{symbol}}) name) \"{{symbol}}\") (+! (-> $usage data (mem-usage-id {{symbol}}) count) $count) (+! (-> $usage data (mem-usage-id {{symbol}}) used) $bytes) (set! (-> $usage data (mem-usage-id {{symbol}}) total) (-> $usage data (mem-usage-id {{symbol}}) used))))", + "rewrite": "(dma-mem-usage-add! {{symbol}} $count $bytes)" + }, + { + "name": "invalidate-cache-line", + "match": [ + "(let (($address $value)) (.sync.l) (.cache dxwbin $address 0) (.sync.l) (.cache dxwbin $address 1))", + "(.sync.l)", + "0" + ], + "rewrite": "(invalidate-cache-line $value)" + }, { "name": "mem-usage-add-{{symbol}}", "for_each": "mem-usage-id", diff --git a/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc b/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc index 08041ce897..24c5fac59d 100644 --- a/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc +++ b/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc @@ -5,5 +5,17 @@ "field_score_overrides": [ {"type": "work-area", "field": "background", "score": 1000} ] + }, + "generic-merc": { + "type": "terrain-context", + "field_score_overrides": [ + {"type": "foreground-area", "field": "generic-work", "score": 1000000000000} + ] + }, + "generic-tie": { + "type": "terrain-context", + "field_score_overrides": [ + {"type": "foreground-area", "field": "generic-work", "score": 1000000000000} + ] } } diff --git a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc index b7a4e105bf..649f307dba 100644 --- a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc +++ b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc @@ -5766,16 +5766,10 @@ [281, "v1", "merc-fragment"] ], "generic-merc-execute-all": [ - [[165, 170], "v1", "terrain-context"], - [92, "a0", "terrain-context"], - [96, "v1", "terrain-context"], - [100, "v1", "terrain-context"], - [103, "a0", "generic-envmap-saves"], + [100, "v1", "(pointer int32)"], + [103, "a0", "(pointer int32)"], [105, "v1", "(pointer int32)"], - [47, "v1", "terrain-context"], - [49, "v1", "terrain-context"], - [51, "v1", "terrain-context"], - [66, "a0", "terrain-context"], + [109, "a0", "(pointer int32)"], [[114, 117], "v1", "dma-packet"] ], "generic-initialize-without-sink": [ @@ -6127,10 +6121,6 @@ [[47, 55], "a0", "dma-packet"] ], "(method 12 effect-control)": [["_stack_", 112, "res-tag"]], - "generic-tie-execute": [ - [118, "v1", "terrain-context"], - [124, "v1", "terrain-context"] - ], "target-collision-reaction": [ ["_stack_", 96, "collide-status"], ["_stack_", 104, "cshape-reaction-flags"] diff --git a/decompiler/config/jak1/ntsc_v1/var_names.jsonc b/decompiler/config/jak1/ntsc_v1/var_names.jsonc index 677caa4380..371925fa05 100644 --- a/decompiler/config/jak1/ntsc_v1/var_names.jsonc +++ b/decompiler/config/jak1/ntsc_v1/var_names.jsonc @@ -31646,6 +31646,16 @@ "i" ] }, + "generic-tie-debug": { + "vars": { + "gp-0": "vertices", + "s5-0": "i", + "a2-0": "vertex", + "f2-0": "x", + "f1-0": "y", + "f0-0": "z" + } + }, "generic-tie-execute": { "args": [ "sink", @@ -31662,6 +31672,13 @@ "calls", "generic-tie-calls" ], + "v1-27": "time-of-day", + "a0-26": "work", + "v1-28": "env-color-32", + "v1-29": "env-color-16", + "v1-30": "env-color-8", + "a0-33": "pcr0", + "a0-35": "pcr1", "v1-37": "dma-usage" } }, diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 66d88db0b1..80b23c315b 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -163,7 +163,6 @@ set(RUNTIME_SOURCE mips2c/jak1_functions/collide_probe.cpp mips2c/jak1_functions/draw_string.cpp mips2c/jak1_functions/generic_merc.cpp - mips2c/jak1_functions/generic_tie.cpp mips2c/jak1_functions/merc_blend_shape.cpp mips2c/jak1_functions/ocean_vu0.cpp mips2c/jak1_functions/ocean.cpp diff --git a/game/mips2c/jak1_functions/generic_tie.cpp b/game/mips2c/jak1_functions/generic_tie.cpp deleted file mode 100644 index 2e2b814838..0000000000 --- a/game/mips2c/jak1_functions/generic_tie.cpp +++ /dev/null @@ -1,2185 +0,0 @@ -// clang-format off -//--------------------------MIPS2C--------------------- -#include "game/mips2c/mips2c_private.h" -#include "game/kernel/jak1/kscheme.h" -using namespace jak1; -namespace Mips2C::jak1 { -namespace generic_tie_dma_to_spad_sync { -struct Cache { - void* fake_scratchpad_data; // *fake-scratchpad-data* -} cache; - -u64 execute(void* ctxt) { - auto* c = (ExecutionContext*)ctxt; - // nop // sll r0, r0, 0 - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - c->ori(a2, r0, 65535); // ori a2, r0, 65535 - c->lui(v1, 4096); // lui v1, 4096 - // nop // sll r0, r0, 0 - c->ori(v1, v1, 54272); // ori v1, v1, 54272 // SPR TO - c->and_(a2, a1, a2); // and a2, a1, a2 - /* - block_1: - c->lw(a3, 0, v1); // lw a3, 0(v1) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->andi(a3, a3, 256); // andi a3, a3, 256 - // nop // sll r0, r0, 0 - bc = c->sgpr64(a3) == 0; // beq a3, r0, L90 - // nop // sll r0, r0, 0 - if (bc) {goto block_3;} // branch non-likely - - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->daddiu(a1, a1, 1); // daddiu a1, a1, 1 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - //beq r0, r0, L89 // beq r0, r0, L89 - // nop // sll r0, r0, 0 - goto block_1; // branch always - */ - - - c->addiu(a3, r0, 324); // addiu a3, r0, 324 - // c->sw(a2, 128, v1); // sw a2, 128(v1) - u32 sadr = c->sgpr64(a2); - // c->sw(a0, 48, v1); // sw a0, 48(v1) - u32 tadr = c->sgpr64(a0); - // c->sw(r0, 32, v1); // sw r0, 32(v1) - // Unknown instr: sync.l - // c->sw(a3, 0, v1); // sw a3, 0(v1) - // same and hack as generic merc. - spad_to_dma_blerc_chain(cache.fake_scratchpad_data, sadr & 0x3fff, tadr); - - /* - block_4: - c->lw(a0, 0, v1); // lw a0, 0(v1) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->andi(a0, a0, 256); // andi a0, a0, 256 - // nop // sll r0, r0, 0 - bc = c->sgpr64(a0) == 0; // beq a0, r0, L92 - // nop // sll r0, r0, 0 - if (bc) {goto block_6;} // branch non-likely - - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->daddiu(a1, a1, 1); // daddiu a1, a1, 1 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - //beq r0, r0, L91 // beq r0, r0, L91 - // nop // sll r0, r0, 0 - goto block_4; // branch always - */ - - - c->gprs[v0].du64[0] = 0; // or v0, r0, r0 - //jr ra // jr ra - c->daddu(sp, sp, r0); // daddu sp, sp, r0 - goto end_of_function; // return - - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - end_of_function: - return c->gprs[v0].du64[0]; -} - -void link() { - cache.fake_scratchpad_data = intern_from_c("*fake-scratchpad-data*").c(); - gLinkedFunctionTable.reg("generic-tie-dma-to-spad-sync", execute, 128); -} - -} // namespace generic_tie_dma_to_spad_sync -} // namespace Mips2C - -//--------------------------MIPS2C--------------------- -#include "game/mips2c/mips2c_private.h" - -namespace Mips2C::jak1 { - -namespace generic_prepare_dma_double { -extern u64 execute(void* ctxt); -} - -namespace generic_tie_convert { -struct Cache { - void* fake_scratchpad_data; // *fake-scratchpad-data* -} cache; - -u16 vis[16]; - -u8 vu0_data_mem[1024 * 4]; - -void sq_buffer(Mask mask, const Vf& data, u32 qw) { - ASSERT(qw * 16 < sizeof(vu0_data_mem)); - for (int i = 0; i < 4; i++) { - if ((u64)mask & (1 << i)) { - memcpy(vu0_data_mem + qw * 16 + i * 4, data.data + i, 4); - } - } -} - -void lq_buffer(Mask mask, Vf& data, u32 qw) { - ASSERT(qw * 16 < sizeof(vu0_data_mem)); - for (int i = 0; i < 4; i++) { - if ((u64)mask & (1 << i)) { - memcpy(data.data + i, vu0_data_mem + qw * 16 + i * 4, 4); - } - } -} - - -void vcallms_104() { - // iaddi vi02, vi00, 0x0 | nop 104 - vis[vi02] = 0; - // iadd vi03, vi02, vi08 | nop 105 - vis[vi03] = vis[vi02] + vis[vi08]; - // iaddiu vi03, vi03, 0x10 | nop 106 - vis[vi03] = vis[vi03] + 0x10; /* 16 */ - // iadd vi01, vi03, vi09 | nop 107 - vis[vi01] = vis[vi03] + vis[vi09]; - // iadd vi01, vi01, vi09 | nop 108 - vis[vi01] = vis[vi01] + vis[vi09]; - // iaddiu vi01, vi01, 0x10 | nop 109 - vis[vi01] = vis[vi01] + 0x10; /* 16 */ - // iaddi vi10, vi00, 0x0 | nop 110 - vis[vi10] = 0; - // ior vi11, vi03, vi00 | nop 111 - vis[vi11] = vis[vi03]; - // ior vi12, vi01, vi00 | nop :e 112 - vis[vi12] = vis[vi01]; - // iadd vi13, vi01, vi08 | nop 113 - vis[vi13] = vis[vi01] + vis[vi08]; -} - -void vcallms_114(ExecutionContext* c) { - // nop | itof12.xyzw vf09, vf05 114 - c->vfs[vf09].vf.itof12(Mask::xyzw, c->vf_src(vf05).vf); - // nop | itof12.xyzw vf10, vf06 115 - c->vfs[vf10].vf.itof12(Mask::xyzw, c->vf_src(vf06).vf); - // nop | itof12.xyzw vf11, vf07 116 - c->vfs[vf11].vf.itof12(Mask::xyzw, c->vf_src(vf07).vf); - // nop | itof12.xyzw vf12, vf08 117 - c->vfs[vf12].vf.itof12(Mask::xyzw, c->vf_src(vf08).vf); - // sqi.xyzw vf09, vi01 | nop 118 - sq_buffer(Mask::xyzw, c->vf_src(vf09).vf, vis[vi01]++); - // sqi.xyzw vf10, vi01 | nop 119 - sq_buffer(Mask::xyzw, c->vf_src(vf10).vf, vis[vi01]++); - // sqi.xyzw vf11, vi01 | nop :e 120 - sq_buffer(Mask::xyzw, c->vf_src(vf11).vf, vis[vi01]++); - // sqi.xyzw vf12, vi01 | nop 121 - sq_buffer(Mask::xyzw, c->vf_src(vf12).vf, vis[vi01]++); -} - -void vcallms_122(ExecutionContext* c) { - // mr32.w vf17, vf13 | itof0.xyz vf09, vf01 122 - c->vfs[vf09].vf.itof0(Mask::xyz, c->vf_src(vf01).vf); c->vfs[vf17].vf.mr32(Mask::w, c->vf_src(vf13).vf); - // mr32.w vf18, vf14 | itof0.xyz vf10, vf02 123 - c->vfs[vf10].vf.itof0(Mask::xyz, c->vf_src(vf02).vf); c->vfs[vf18].vf.mr32(Mask::w, c->vf_src(vf14).vf); - // mr32.w vf19, vf15 | itof0.xyz vf11, vf03 124 - c->vfs[vf11].vf.itof0(Mask::xyz, c->vf_src(vf03).vf); c->vfs[vf19].vf.mr32(Mask::w, c->vf_src(vf15).vf); - // mr32.w vf20, vf16 | itof0.xyz vf12, vf04 125 - c->vfs[vf12].vf.itof0(Mask::xyz, c->vf_src(vf04).vf); c->vfs[vf20].vf.mr32(Mask::w, c->vf_src(vf16).vf); - // move.w vf09, vf17 | nop 126 - c->vfs[vf09].vf.move(Mask::w, c->vf_src(vf17).vf); - // move.w vf10, vf18 | nop 127 - c->vfs[vf10].vf.move(Mask::w, c->vf_src(vf18).vf); - // move.w vf11, vf19 | nop 128 - c->vfs[vf11].vf.move(Mask::w, c->vf_src(vf19).vf); - // move.w vf12, vf20 | nop 129 - c->vfs[vf12].vf.move(Mask::w, c->vf_src(vf20).vf); - // sqi.xyzw vf09, vi02 | nop 130 - sq_buffer(Mask::xyzw, c->vf_src(vf09).vf, vis[vi02]++); - // sqi.xyzw vf10, vi02 | nop 131 - sq_buffer(Mask::xyzw, c->vf_src(vf10).vf, vis[vi02]++); - // sqi.xyzw vf11, vi02 | nop :e 132 - sq_buffer(Mask::xyzw, c->vf_src(vf11).vf, vis[vi02]++); - // sqi.xyzw vf12, vi02 | nop 133 - sq_buffer(Mask::xyzw, c->vf_src(vf12).vf, vis[vi02]++); -} - -void vcallms_134(ExecutionContext* c) { - // mr32.w vf17, vf13 | itof0.xyz vf09, vf01 134 - c->vfs[vf09].vf.itof0(Mask::xyz, c->vf_src(vf01).vf); c->vfs[vf17].vf.mr32(Mask::w, c->vf_src(vf13).vf); - // mr32.w vf18, vf14 | itof0.xyz vf10, vf02 135 - c->vfs[vf10].vf.itof0(Mask::xyz, c->vf_src(vf02).vf); c->vfs[vf18].vf.mr32(Mask::w, c->vf_src(vf14).vf); - // nop | itof0.xyz vf11, vf03 136 - c->vfs[vf11].vf.itof0(Mask::xyz, c->vf_src(vf03).vf); - // nop | nop 137 - - // move.w vf09, vf17 | nop 138 - c->vfs[vf09].vf.move(Mask::w, c->vf_src(vf17).vf); - // move.w vf10, vf18 | nop 139 - c->vfs[vf10].vf.move(Mask::w, c->vf_src(vf18).vf); - // nop | nop 140 - - // iaddi vi03, vi03, 0x2 | nop 141 - vis[vi03] = vis[vi03] + 2; - // sqi.xyzw vf09, vi02 | nop 142 - sq_buffer(Mask::xyzw, c->vf_src(vf09).vf, vis[vi02]++); - // sq.xyzw vf10, -2(vi03) | nop :e 143 - sq_buffer(Mask::xyzw, c->vf_src(vf10).vf, vis[vi03] + -2); - // sq.xyzw vf11, -1(vi03) | nop 144 - sq_buffer(Mask::xyzw, c->vf_src(vf11).vf, vis[vi03] + -1); -} - -void vcallms_145(ExecutionContext* c) { - // mr32.w vf17, vf13 | itof0.xyz vf09, vf01 145 - c->vfs[vf09].vf.itof0(Mask::xyz, c->vf_src(vf01).vf); c->vfs[vf17].vf.mr32(Mask::w, c->vf_src(vf13).vf); - // mr32.w vf18, vf14 | itof0.xyz vf10, vf02 146 - c->vfs[vf10].vf.itof0(Mask::xyz, c->vf_src(vf02).vf); c->vfs[vf18].vf.mr32(Mask::w, c->vf_src(vf14).vf); - // mr32.w vf19, vf15 | itof0.xyz vf11, vf03 147 - c->vfs[vf11].vf.itof0(Mask::xyz, c->vf_src(vf03).vf); c->vfs[vf19].vf.mr32(Mask::w, c->vf_src(vf15).vf); - // nop | itof0.xyz vf12, vf04 148 - c->vfs[vf12].vf.itof0(Mask::xyz, c->vf_src(vf04).vf); - // move.w vf09, vf17 | nop 149 - c->vfs[vf09].vf.move(Mask::w, c->vf_src(vf17).vf); - // move.w vf10, vf18 | nop 150 - c->vfs[vf10].vf.move(Mask::w, c->vf_src(vf18).vf); - // move.w vf11, vf19 | nop 151 - c->vfs[vf11].vf.move(Mask::w, c->vf_src(vf19).vf); - // iaddi vi03, vi03, 0x2 | nop 152 - vis[vi03] = vis[vi03] + 2; - // sqi.xyzw vf09, vi02 | nop 153 - sq_buffer(Mask::xyzw, c->vf_src(vf09).vf, vis[vi02]++); - // sqi.xyzw vf10, vi02 | nop 154 - sq_buffer(Mask::xyzw, c->vf_src(vf10).vf, vis[vi02]++); - // sq.xyzw vf11, -2(vi03) | nop :e 155 - sq_buffer(Mask::xyzw, c->vf_src(vf11).vf, vis[vi03] + -2); - // sq.xyzw vf12, -1(vi03) | nop 156 - sq_buffer(Mask::xyzw, c->vf_src(vf12).vf, vis[vi03] + -1); -} - -void vcallms_157(ExecutionContext* c) { - // mr32.w vf17, vf13 | itof0.xyz vf09, vf01 157 - c->vfs[vf09].vf.itof0(Mask::xyz, c->vf_src(vf01).vf); c->vfs[vf17].vf.mr32(Mask::w, c->vf_src(vf13).vf); - // mr32.w vf18, vf14 | itof0.xyz vf10, vf02 158 - c->vfs[vf10].vf.itof0(Mask::xyz, c->vf_src(vf02).vf); c->vfs[vf18].vf.mr32(Mask::w, c->vf_src(vf14).vf); - // mr32.w vf19, vf15 | itof0.xyz vf11, vf03 159 - c->vfs[vf11].vf.itof0(Mask::xyz, c->vf_src(vf03).vf); c->vfs[vf19].vf.mr32(Mask::w, c->vf_src(vf15).vf); - // nop | nop 160 - - // move.w vf09, vf17 | nop 161 - c->vfs[vf09].vf.move(Mask::w, c->vf_src(vf17).vf); - // move.w vf10, vf18 | nop 162 - c->vfs[vf10].vf.move(Mask::w, c->vf_src(vf18).vf); - // move.w vf11, vf19 | nop 163 - c->vfs[vf11].vf.move(Mask::w, c->vf_src(vf19).vf); - // nop | nop 164 - - // sqi.xyzw vf09, vi02 | nop 165 - sq_buffer(Mask::xyzw, c->vf_src(vf09).vf, vis[vi02]++); - // sqi.xyzw vf10, vi02 | nop :e 166 - sq_buffer(Mask::xyzw, c->vf_src(vf10).vf, vis[vi02]++); - // sqi.xyzw vf11, vi02 | nop 167 - sq_buffer(Mask::xyzw, c->vf_src(vf11).vf, vis[vi02]++); -} - -void vcallms_168(ExecutionContext* c) { - // mr32.w vf17, vf13 | itof0.xyz vf09, vf01 168 - c->vfs[vf09].vf.itof0(Mask::xyz, c->vf_src(vf01).vf); c->vfs[vf17].vf.mr32(Mask::w, c->vf_src(vf13).vf); - // mr32.w vf19, vf14 | itof0.xyz vf10, vf02 169 - c->vfs[vf10].vf.itof0(Mask::xyz, c->vf_src(vf02).vf); c->vfs[vf19].vf.mr32(Mask::w, c->vf_src(vf14).vf); - // nop | itof0.xyz vf11, vf03 170 - c->vfs[vf11].vf.itof0(Mask::xyz, c->vf_src(vf03).vf); - // iaddi vi03, vi03, 0x4 | itof0.xyz vf12, vf04 171 - c->vfs[vf12].vf.itof0(Mask::xyz, c->vf_src(vf04).vf); vis[vi03] = vis[vi03] + 4; - // move.w vf09, vf17 | nop 172 - c->vfs[vf09].vf.move(Mask::w, c->vf_src(vf17).vf); - // move.w vf11, vf19 | nop 173 - c->vfs[vf11].vf.move(Mask::w, c->vf_src(vf19).vf); - // sq.xyz vf10, -3(vi03) | nop 174 - sq_buffer(Mask::xyz, c->vf_src(vf10).vf, vis[vi03] + -3); - // sq.xyz vf12, -1(vi03) | nop 175 - sq_buffer(Mask::xyz, c->vf_src(vf12).vf, vis[vi03] + -1); - // sq.xyzw vf09, -4(vi03) | nop :e 176 - sq_buffer(Mask::xyzw, c->vf_src(vf09).vf, vis[vi03] + -4); - // sq.xyzw vf11, -2(vi03) | nop 177 - sq_buffer(Mask::xyzw, c->vf_src(vf11).vf, vis[vi03] + -2); -} - -void vcallms_183(ExecutionContext* c); - -void vcallms_178(ExecutionContext* c) { - // ior vi02, vi10, vi00 | nop 178 - vis[vi02] = vis[vi10]; - // ior vi01, vi12, vi00 | nop 179 - vis[vi01] = vis[vi12]; - // lqi.xyzw vf05, vi02 | nop 180 - lq_buffer(Mask::xyzw, c->vfs[vf05].vf, vis[vi02]++); - // nop | nop 181 - - // nop | nop 182 - vcallms_183(c); -} - -void vcallms_183(ExecutionContext* c) { - // lqi.xyzw vf09, vi01 | mulaw.xyzw ACC, vf04, vf00 183 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf09].vf, vis[vi01]++); - // move.xyzw vf19, vf21 | maddax.xyzw ACC, vf01, vf05 184 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf05].vf.x()); c->vfs[vf19].vf.move(Mask::xyzw, c->vf_src(vf21).vf); - // move.xyzw vf16, vf22 | madday.xyzw ACC, vf02, vf05 185 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf05].vf.y()); c->vfs[vf16].vf.move(Mask::xyzw, c->vf_src(vf22).vf); - // move.xyzw vf20, vf23 | maddz.xyz vf13, vf03, vf05 186 - c->acc.vf.madd(Mask::xyz, c->vfs[vf13].vf, c->vf_src(vf03).vf, c->vf_src(vf05).vf.z()); c->vfs[vf20].vf.move(Mask::xyzw, c->vf_src(vf23).vf); - // lqi.xyzw vf06, vi02 | mulax.xyzw ACC, vf01, vf09 187 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf09).vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf06].vf, vis[vi02]++); - // move.xyzw vf15, vf24 | madday.xyzw ACC, vf02, vf09 188 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf09].vf.y()); c->vfs[vf15].vf.move(Mask::xyzw, c->vf_src(vf24).vf); - // nop | maddz.xyzw vf17, vf03, vf09 189 - c->acc.vf.madd(Mask::xyzw, c->vfs[vf17].vf, c->vf_src(vf03).vf, c->vf_src(vf09).vf.z()); - // lqi.xyzw vf10, vi01 | mulaw.xyzw ACC, vf04, vf00 190 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf10].vf, vis[vi01]++); - // move.w vf13, vf05 | maddax.xyzw ACC, vf01, vf06 191 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf06].vf.x()); c->vfs[vf13].vf.move(Mask::w, c->vf_src(vf05).vf); - // nop | madday.xyzw ACC, vf02, vf06 192 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf06].vf.y()); - // nop | maddz.xyz vf14, vf03, vf06 193 - c->acc.vf.madd(Mask::xyz, c->vfs[vf14].vf, c->vf_src(vf03).vf, c->vf_src(vf06).vf.z()); - // lqi.xyzw vf07, vi02 | mulax.xyzw ACC, vf01, vf10 194 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf10).vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf07].vf, vis[vi02]++); - // nop | madday.xyzw ACC, vf02, vf10 195 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf10].vf.y()); - // nop | maddz.xyzw vf18, vf03, vf10 196 - c->acc.vf.madd(Mask::xyzw, c->vfs[vf18].vf, c->vf_src(vf03).vf, c->vf_src(vf10).vf.z()); - // lqi.xyzw vf11, vi01 | mulaw.xyzw ACC, vf04, vf00 197 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf11].vf, vis[vi01]++); - // move.w vf14, vf06 | maddax.xyzw ACC, vf01, vf07 198 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf07].vf.x()); c->vfs[vf14].vf.move(Mask::w, c->vf_src(vf06).vf); - // nop | madday.xyzw ACC, vf02, vf07 199 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf07].vf.y()); - // nop | maddz.xyz vf24, vf03, vf07 200 - c->acc.vf.madd(Mask::xyz, c->vfs[vf24].vf, c->vf_src(vf03).vf, c->vf_src(vf07).vf.z()); - // lqi.xyzw vf08, vi02 | mulax.xyzw ACC, vf01, vf11 201 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf11).vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf08].vf, vis[vi02]++); - // nop | madday.xyzw ACC, vf02, vf11 202 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf11].vf.y()); - // nop | maddz.xyzw vf21, vf03, vf11 203 - c->acc.vf.madd(Mask::xyzw, c->vfs[vf21].vf, c->vf_src(vf03).vf, c->vf_src(vf11).vf.z()); - // lqi.xyzw vf12, vi01 | mulaw.xyzw ACC, vf04, vf00 204 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf12].vf, vis[vi01]++); - // move.w vf24, vf07 | maddax.xyzw ACC, vf01, vf08 205 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf08].vf.x()); c->vfs[vf24].vf.move(Mask::w, c->vf_src(vf07).vf); - // nop | madday.xyzw ACC, vf02, vf08 206 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf08].vf.y()); - // nop | maddz.xyz vf22, vf03, vf08 207 - c->acc.vf.madd(Mask::xyz, c->vfs[vf22].vf, c->vf_src(vf03).vf, c->vf_src(vf08).vf.z()); - // lqi.xyzw vf05, vi02 | mulax.xyzw ACC, vf01, vf12 208 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf12).vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf05].vf, vis[vi02]++); - // move.w vf22, vf08 | madday.xyzw ACC, vf02, vf12 :e 209 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf12].vf.y()); c->vfs[vf22].vf.move(Mask::w, c->vf_src(vf08).vf); - // nop | maddz.xyzw vf23, vf03, vf12 210 - c->acc.vf.madd(Mask::xyzw, c->vfs[vf23].vf, c->vf_src(vf03).vf, c->vf_src(vf12).vf.z()); -} - -void vcallms_211(ExecutionContext* c) { - // move.xyzw vf19, vf21 | nop 211 - c->vfs[vf19].vf.move(Mask::xyzw, c->vf_src(vf21).vf); - // move.xyzw vf16, vf22 | nop 212 - c->vfs[vf16].vf.move(Mask::xyzw, c->vf_src(vf22).vf); - // move.xyzw vf20, vf23 | nop :e 213 - c->vfs[vf20].vf.move(Mask::xyzw, c->vf_src(vf23).vf); - // move.xyzw vf15, vf24 | nop 214 - c->vfs[vf15].vf.move(Mask::xyzw, c->vf_src(vf24).vf); -} -void vcallms_221(ExecutionContext* c); - -void vcallms_215(ExecutionContext* c) { - // ior vi03, vi11, vi00 | nop 215 - vis[vi03] = vis[vi11]; - // ior vi01, vi13, vi00 | nop 216 - vis[vi01] = vis[vi13]; - // lqi.xyzw vf05, vi03 | nop 217 - lq_buffer(Mask::xyzw, c->vfs[vf05].vf, vis[vi03]++); - // lqi.xyzw vf25, vi03 | nop 218 - lq_buffer(Mask::xyzw, c->vfs[vf25].vf, vis[vi03]++); - // lqi.xyzw vf09, vi01 | nop 219 - lq_buffer(Mask::xyzw, c->vfs[vf09].vf, vis[vi01]++); - // nop | nop 220 - vcallms_221(c); -} - -void vcallms_221(ExecutionContext* c) { - // move.xyzw vf16, vf21 | mulax.xyz ACC, vf05, vf29 221 - c->acc.vf.mula(Mask::xyz, c->vf_src(vf05).vf, c->vf_src(vf29).vf.x()); c->vfs[vf16].vf.move(Mask::xyzw, c->vf_src(vf21).vf); - // nop | maddw.xyz vf30, vf25, vf00 222 - c->acc.vf.madd(Mask::xyz, c->vfs[vf30].vf, c->vf_src(vf25).vf, c->vf_src(vf00).vf.w()); - // nop | mulax.xyzw ACC, vf01, vf09 223 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf09).vf.x()); - // nop | madday.xyzw ACC, vf02, vf09 224 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf09].vf.y()); - // move.w vf13, vf05 | maddz.xyz vf17, vf03, vf09 225 - c->acc.vf.madd(Mask::xyz, c->vfs[vf17].vf, c->vf_src(vf03).vf, c->vf_src(vf09).vf.z()); c->vfs[vf13].vf.move(Mask::w, c->vf_src(vf05).vf); - // lqi.xyzw vf06, vi03 | mulaw.xyzw ACC, vf04, vf00 226 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf06].vf, vis[vi03]++); - // lqi.xyzw vf26, vi03 | maddax.xyzw ACC, vf01, vf30 227 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf30].vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf26].vf, vis[vi03]++); - // lqi.xyzw vf10, vi01 | madday.xyzw ACC, vf02, vf30 228 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf30].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf10].vf, vis[vi01]++); - // nop | maddz.xyz vf13, vf03, vf30 229 - c->acc.vf.madd(Mask::xyz, c->vfs[vf13].vf, c->vf_src(vf03).vf, c->vf_src(vf30).vf.z()); - // nop | mulax.xyz ACC, vf06, vf29 230 - c->acc.vf.mula(Mask::xyz, c->vf_src(vf06).vf, c->vf_src(vf29).vf.x()); - // nop | maddw.xyz vf30, vf26, vf00 231 - c->acc.vf.madd(Mask::xyz, c->vfs[vf30].vf, c->vf_src(vf26).vf, c->vf_src(vf00).vf.w()); - // nop | mulax.xyzw ACC, vf01, vf10 232 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf10).vf.x()); - // nop | madday.xyzw ACC, vf02, vf10 233 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf10].vf.y()); - // move.w vf14, vf06 | maddz.xyz vf18, vf03, vf10 234 - c->acc.vf.madd(Mask::xyz, c->vfs[vf18].vf, c->vf_src(vf03).vf, c->vf_src(vf10).vf.z()); c->vfs[vf14].vf.move(Mask::w, c->vf_src(vf06).vf); - // lqi.xyzw vf07, vi03 | mulaw.xyzw ACC, vf04, vf00 235 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf07].vf, vis[vi03]++); - // lqi.xyzw vf27, vi03 | maddax.xyzw ACC, vf01, vf30 236 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf30].vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf27].vf, vis[vi03]++); - // lqi.xyzw vf11, vi01 | madday.xyzw ACC, vf02, vf30 237 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf30].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf11].vf, vis[vi01]++); - // nop | maddz.xyz vf14, vf03, vf30 238 - c->acc.vf.madd(Mask::xyz, c->vfs[vf14].vf, c->vf_src(vf03).vf, c->vf_src(vf30).vf.z()); - // nop | mulax.xyz ACC, vf07, vf29 239 - c->acc.vf.mula(Mask::xyz, c->vf_src(vf07).vf, c->vf_src(vf29).vf.x()); - // nop | maddw.xyz vf30, vf27, vf00 240 - c->acc.vf.madd(Mask::xyz, c->vfs[vf30].vf, c->vf_src(vf27).vf, c->vf_src(vf00).vf.w()); - // nop | mulax.xyzw ACC, vf01, vf11 241 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf11).vf.x()); - // nop | madday.xyzw ACC, vf02, vf11 242 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf11].vf.y()); - // move.w vf15, vf07 | maddz.xyz vf19, vf03, vf11 243 - c->acc.vf.madd(Mask::xyz, c->vfs[vf19].vf, c->vf_src(vf03).vf, c->vf_src(vf11).vf.z()); c->vfs[vf15].vf.move(Mask::w, c->vf_src(vf07).vf); - // lqi.xyzw vf08, vi03 | mulaw.xyzw ACC, vf04, vf00 244 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf08].vf, vis[vi03]++); - // lqi.xyzw vf28, vi03 | maddax.xyzw ACC, vf01, vf30 245 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf30].vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf28].vf, vis[vi03]++); - // lqi.xyzw vf12, vi01 | madday.xyzw ACC, vf02, vf30 246 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf30].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf12].vf, vis[vi01]++); - // nop | maddz.xyz vf15, vf03, vf30 247 - c->acc.vf.madd(Mask::xyz, c->vfs[vf15].vf, c->vf_src(vf03).vf, c->vf_src(vf30).vf.z()); - // nop | mulax.xyz ACC, vf08, vf29 248 - c->acc.vf.mula(Mask::xyz, c->vf_src(vf08).vf, c->vf_src(vf29).vf.x()); - // nop | maddw.xyz vf30, vf28, vf00 249 - c->acc.vf.madd(Mask::xyz, c->vfs[vf30].vf, c->vf_src(vf28).vf, c->vf_src(vf00).vf.w()); - // nop | mulax.xyzw ACC, vf01, vf12 250 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf12).vf.x()); - // nop | madday.xyzw ACC, vf02, vf12 251 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf12].vf.y()); - // move.w vf21, vf08 | maddz.xyz vf20, vf03, vf12 252 - c->acc.vf.madd(Mask::xyz, c->vfs[vf20].vf, c->vf_src(vf03).vf, c->vf_src(vf12).vf.z()); c->vfs[vf21].vf.move(Mask::w, c->vf_src(vf08).vf); - // lqi.xyzw vf05, vi03 | mulaw.xyzw ACC, vf04, vf00 253 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf05].vf, vis[vi03]++); - // lqi.xyzw vf25, vi03 | maddax.xyzw ACC, vf01, vf30 254 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf30].vf.x()); lq_buffer(Mask::xyzw, c->vfs[vf25].vf, vis[vi03]++); - // lqi.xyzw vf09, vi01 | madday.xyzw ACC, vf02, vf30 :e 255 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf30].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf09].vf, vis[vi01]++); - // nop | maddz.xyz vf21, vf03, vf30 256 - c->acc.vf.madd(Mask::xyz, c->vfs[vf21].vf, c->vf_src(vf03).vf, c->vf_src(vf30).vf.z()); -} - -void vcallms_257(ExecutionContext* c) { - // move.xyzw vf16, vf21 | nop :e 257 - c->vfs[vf16].vf.move(Mask::xyzw, c->vf_src(vf21).vf); - // nop | nop 258 -} - -void vcallms_264(ExecutionContext* c); -void vcallms_259(ExecutionContext* c) { - // ior vi01, vi13, vi00 | nop 259 - vis[vi01] = vis[vi13]; - // lqi.xyzw vf09, vi01 | nop 260 - lq_buffer(Mask::xyzw, c->vfs[vf09].vf, vis[vi01]++); - // ior vi03, vi11, vi00 | nop 261 - vis[vi03] = vis[vi11]; - // lq.xyzw vf05, 1(vi03) | nop 262 - lq_buffer(Mask::xyzw, c->vfs[vf05].vf, vis[vi03] + 1); - // iaddi vi03, vi03, 0x2 | nop 263 - vis[vi03] = vis[vi03] + 2; - vcallms_264(c); -} - -void vcallms_264(ExecutionContext* c) { - // move.xyzw vf15, vf21 | mulax.xyzw ACC, vf01, vf09 264 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf09).vf.x()); c->vfs[vf15].vf.move(Mask::xyzw, c->vf_src(vf21).vf); - // move.xyzw vf20, vf22 | madday.xyzw ACC, vf02, vf09 265 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf09].vf.y()); c->vfs[vf20].vf.move(Mask::xyzw, c->vf_src(vf22).vf); - // lq.w vf13, -2(vi03) | maddz.xyz vf17, vf03, vf09 266 - c->acc.vf.madd(Mask::xyz, c->vfs[vf17].vf, c->vf_src(vf03).vf, c->vf_src(vf09).vf.z()); lq_buffer(Mask::w, c->vfs[vf13].vf, vis[vi03] + -2); - // lqi.xyzw vf10, vi01 | mulaw.xyzw ACC, vf04, vf00 267 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf10].vf, vis[vi01]++); - // move.xyzw vf16, vf23 | maddax.xyzw ACC, vf01, vf05 268 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf05].vf.x()); c->vfs[vf16].vf.move(Mask::xyzw, c->vf_src(vf23).vf); - // lq.xyzw vf06, 1(vi03) | madday.xyzw ACC, vf02, vf05 269 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf05].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf06].vf, vis[vi03] + 1); - // iaddi vi03, vi03, 0x2 | maddz.xyz vf13, vf03, vf05 270 - c->acc.vf.madd(Mask::xyz, c->vfs[vf13].vf, c->vf_src(vf03).vf, c->vf_src(vf05).vf.z()); vis[vi03] = vis[vi03] + 2; - // nop | mulax.xyzw ACC, vf01, vf10 271 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf10).vf.x()); - // nop | madday.xyzw ACC, vf02, vf10 272 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf10].vf.y()); - // lq.w vf14, -2(vi03) | maddz.xyz vf18, vf03, vf10 273 - c->acc.vf.madd(Mask::xyz, c->vfs[vf18].vf, c->vf_src(vf03).vf, c->vf_src(vf10).vf.z()); lq_buffer(Mask::w, c->vfs[vf14].vf, vis[vi03] + -2); - // lqi.xyzw vf11, vi01 | mulaw.xyzw ACC, vf04, vf00 274 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf11].vf, vis[vi01]++); - // nop | maddax.xyzw ACC, vf01, vf06 275 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf06].vf.x()); - // lq.xyzw vf07, 1(vi03) | madday.xyzw ACC, vf02, vf06 276 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf06].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf07].vf, vis[vi03] + 1); - // iaddi vi03, vi03, 0x2 | maddz.xyz vf14, vf03, vf06 277 - c->acc.vf.madd(Mask::xyz, c->vfs[vf14].vf, c->vf_src(vf03).vf, c->vf_src(vf06).vf.z()); vis[vi03] = vis[vi03] + 2; - // nop | mulax.xyzw ACC, vf01, vf11 278 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf11).vf.x()); - // nop | madday.xyzw ACC, vf02, vf11 279 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf11].vf.y()); - // lq.w vf21, -2(vi03) | maddz.xyz vf19, vf03, vf11 280 - c->acc.vf.madd(Mask::xyz, c->vfs[vf19].vf, c->vf_src(vf03).vf, c->vf_src(vf11).vf.z()); lq_buffer(Mask::w, c->vfs[vf21].vf, vis[vi03] + -2); - // lqi.xyzw vf12, vi01 | mulaw.xyzw ACC, vf04, vf00 281 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf12].vf, vis[vi01]++); - // nop | maddax.xyzw ACC, vf01, vf07 282 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf07].vf.x()); - // lq.xyzw vf08, 1(vi03) | madday.xyzw ACC, vf02, vf07 283 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf07].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf08].vf, vis[vi03] + 1); - // iaddi vi03, vi03, 0x2 | maddz.xyz vf21, vf03, vf07 284 - c->acc.vf.madd(Mask::xyz, c->vfs[vf21].vf, c->vf_src(vf03).vf, c->vf_src(vf07).vf.z()); vis[vi03] = vis[vi03] + 2; - // nop | mulax.xyzw ACC, vf01, vf12 285 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf01).vf, c->vf_src(vf12).vf.x()); - // nop | madday.xyzw ACC, vf02, vf12 286 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf12].vf.y()); - // lq.w vf23, -2(vi03) | maddz.xyz vf22, vf03, vf12 287 - c->acc.vf.madd(Mask::xyz, c->vfs[vf22].vf, c->vf_src(vf03).vf, c->vf_src(vf12).vf.z()); lq_buffer(Mask::w, c->vfs[vf23].vf, vis[vi03] + -2); - // lqi.xyzw vf09, vi01 | mulaw.xyzw ACC, vf04, vf00 288 - c->acc.vf.mula(Mask::xyzw, c->vf_src(vf04).vf, c->vf_src(vf00).vf.w()); lq_buffer(Mask::xyzw, c->vfs[vf09].vf, vis[vi01]++); - // nop | maddax.xyzw ACC, vf01, vf08 289 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf01].vf, c->vfs[vf08].vf.x()); - // lq.xyzw vf05, 1(vi03) | madday.xyzw ACC, vf02, vf08 :e 290 - c->acc.vf.madda(Mask::xyzw, c->vfs[vf02].vf, c->vfs[vf08].vf.y()); lq_buffer(Mask::xyzw, c->vfs[vf05].vf, vis[vi03] + 1); - // iaddi vi03, vi03, 0x2 | maddz.xyz vf23, vf03, vf08 291 - c->acc.vf.madd(Mask::xyz, c->vfs[vf23].vf, c->vf_src(vf03).vf, c->vf_src(vf08).vf.z()); vis[vi03] = vis[vi03] + 2; -} - -void vcallms_292(ExecutionContext* c) { - // move.xyzw vf15, vf21 | nop 292 - c->vfs[vf15].vf.move(Mask::xyzw, c->vf_src(vf21).vf); - // move.xyzw vf20, vf22 | nop :e 293 - c->vfs[vf20].vf.move(Mask::xyzw, c->vf_src(vf22).vf); - // move.xyzw vf16, vf23 | nop 294 - c->vfs[vf16].vf.move(Mask::xyzw, c->vf_src(vf23).vf); -} - -u64 execute(void* ctxt) { - auto* c = (ExecutionContext*)ctxt; - bool bc = false; - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - [[maybe_unused]] u32 call_addr = 0; - u32 madr, sadr, qwc; - c->daddiu(sp, sp, -128); // daddiu sp, sp, -128 - c->sd(ra, 384, at); // sd ra, 384(at) - c->sq(s0, 400, at); // sq s0, 400(at) - c->sq(s1, 416, at); // sq s1, 416(at) - c->sq(s2, 432, at); // sq s2, 432(at) - c->sq(s3, 448, at); // sq s3, 448(at) - c->sq(s4, 464, at); // sq s4, 464(at) - c->sq(s5, 480, at); // sq s5, 480(at) - c->sq(gp, 496, at); // sq gp, 496(at) - - block_1: - c->lui(v1, 4096); // lui v1, 4096 - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - c->ori(v1, v1, 54272); // ori v1, v1, 54272 - // nop // sll r0, r0, 0 - - /* - block_2: - c->lw(a0, 0, v1); // lw a0, 0(v1) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->andi(a0, a0, 256); // andi a0, a0, 256 - // nop // sll r0, r0, 0 - bc = c->sgpr64(a0) == 0; // beq a0, r0, L11 - // nop // sll r0, r0, 0 - if (bc) {goto block_4;} // branch non-likely - - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->daddiu(gp, gp, 1); // daddiu gp, gp, 1 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - //beq r0, r0, L10 // beq r0, r0, L10 - // nop // sll r0, r0, 0 - goto block_2; // branch always - */ - - - // nop // sll r0, r0, 0 - c->lw(v1, 724, at); // lw v1, 724(at) - // nop // sll r0, r0, 0 - c->lw(a2, 728, at); // lw a2, 728(at) - c->dsubu(a0, v1, r0); // dsubu a0, v1, r0 - c->lw(a1, 732, at); // lw a1, 732(at) - bc = c->sgpr64(a0) != 0; // bne a0, r0, L15 - c->daddiu(a0, at, 640); // daddiu a0, at, 640 - if (bc) {goto block_13;} // branch non-likely - - c->addiu(t0, a1, 16); // addiu t0, a1, 16 - c->lhu(a3, 0, a1); // lhu a3, 0(a1) - c->sll(t2, a3, 4); // sll t2, a3, 4 - c->lw(t1, 8, a1); // lw t1, 8(a1) - c->addiu(a2, at, 7632); // addiu a2, at, 7632 - c->addu(a1, t0, t2); // addu a1, t0, t2 - // nop // sll r0, r0, 0 - c->lq(t2, 0, t1); // lq t2, 0(t1) - // nop // sll r0, r0, 0 - c->lq(t3, 16, t1); // lq t3, 16(t1) - // nop // sll r0, r0, 0 - c->lq(t4, 32, t1); // lq t4, 32(t1) - // nop // sll r0, r0, 0 - c->lq(t5, 48, t1); // lq t5, 48(t1) - // nop // sll r0, r0, 0 - c->lq(t1, 64, t1); // lq t1, 64(t1) - // nop // sll r0, r0, 0 - c->sq(t2, 12064, at); // sq t2, 12064(at) - // nop // sll r0, r0, 0 - c->sq(t3, 12080, at); // sq t3, 12080(at) - // nop // sll r0, r0, 0 - c->sq(t4, 12096, at); // sq t4, 12096(at) - // nop // sll r0, r0, 0 - c->sq(t5, 12112, at); // sq t5, 12112(at) - // nop // sll r0, r0, 0 - c->sq(t1, 12128, at); // sq t1, 12128(at) - c->mov64(t1, a2); // or t1, a2, r0 - // nop // sll r0, r0, 0 - c->daddiu(t2, a3, -4); // daddiu t2, a3, -4 - c->mov64(t1, t1); // or t1, t1, r0 - bc = ((s64)c->sgpr64(t2)) < 0; // bltz t2, L13 - c->mov64(t0, t0); // or t0, t0, r0 - if (bc) {goto block_7;} // branch non-likely - - - block_6: - // nop // sll r0, r0, 0 - c->lq(t5, 0, t0); // lq t5, 0(t0) - // nop // sll r0, r0, 0 - c->lq(t2, 16, t0); // lq t2, 16(t0) - c->daddiu(a3, a3, -4); // daddiu a3, a3, -4 - c->lq(t3, 32, t0); // lq t3, 32(t0) - c->daddiu(t1, t1, 64); // daddiu t1, t1, 64 - c->lq(t4, 48, t0); // lq t4, 48(t0) - c->daddiu(t0, t0, 64); // daddiu t0, t0, 64 - c->sq(t5, -64, t1); // sq t5, -64(t1) - c->daddiu(t5, a3, -4); // daddiu t5, a3, -4 - c->sq(t2, -48, t1); // sq t2, -48(t1) - // nop // sll r0, r0, 0 - c->sq(t3, -32, t1); // sq t3, -32(t1) - bc = ((s64)c->sgpr64(t5)) >= 0; // bgez t5, L12 - c->sq(t4, -16, t1); // sq t4, -16(t1) - if (bc) {goto block_6;} // branch non-likely - - - block_7: - bc = c->sgpr64(a3) == 0; // beq a3, r0, L14 - c->lq(t2, 0, t0); // lq t2, 0(t0) - if (bc) {goto block_12;} // branch non-likely - - c->daddiu(t0, t0, 16); // daddiu t0, t0, 16 - c->daddiu(t1, t1, 16); // daddiu t1, t1, 16 - c->daddiu(a3, a3, -1); // daddiu a3, a3, -1 - c->sq(t2, -16, t1); // sq t2, -16(t1) - bc = c->sgpr64(a3) == 0; // beq a3, r0, L14 - c->lq(t2, 0, t0); // lq t2, 0(t0) - if (bc) {goto block_12;} // branch non-likely - - c->daddiu(t0, t0, 16); // daddiu t0, t0, 16 - c->daddiu(t1, t1, 16); // daddiu t1, t1, 16 - c->daddiu(a3, a3, -1); // daddiu a3, a3, -1 - c->sq(t2, -16, t1); // sq t2, -16(t1) - bc = c->sgpr64(a3) == 0; // beq a3, r0, L14 - c->lq(t2, 0, t0); // lq t2, 0(t0) - if (bc) {goto block_12;} // branch non-likely - - c->daddiu(t0, t0, 16); // daddiu t0, t0, 16 - c->daddiu(t1, t1, 16); // daddiu t1, t1, 16 - c->daddiu(a3, a3, -1); // daddiu a3, a3, -1 - c->sq(t2, -16, t1); // sq t2, -16(t1) - bc = c->sgpr64(a3) == 0; // beq a3, r0, L14 - c->lq(t2, 0, t0); // lq t2, 0(t0) - if (bc) {goto block_12;} // branch non-likely - - c->daddiu(t0, t0, 16); // daddiu t0, t0, 16 - c->daddiu(t0, t1, 16); // daddiu t0, t1, 16 - c->daddiu(a3, a3, -1); // daddiu a3, a3, -1 - c->sq(t2, -16, t0); // sq t2, -16(t0) - - block_12: - c->gprs[a3].du64[0] = 0; // or a3, r0, r0 - //beq r0, r0, L16 // beq r0, r0, L16 - c->sw(a2, 640, at); // sw a2, 640(at) - goto block_15; // branch always - - - block_13: - c->daddiu(a3, v1, -1); // daddiu a3, v1, -1 - // nop // sll r0, r0, 0 - bc = c->sgpr64(a3) != 0; // bne a3, r0, L17 - c->mov64(t2, a2); // or t2, a2, r0 - if (bc) {goto block_16;} // branch non-likely - - // nop // sll r0, r0, 0 - c->daddiu(a1, a1, 528); // daddiu a1, a1, 528 - // nop // sll r0, r0, 0 - - block_15: - c->addiu(a2, a1, 16); // addiu a2, a1, 16 - c->lhu(a3, 0, a1); // lhu a3, 0(a1) - c->sll(t0, a3, 4); // sll t0, a3, 4 - c->lwu(t1, 28, a2); // lwu t1, 28(a2) - c->sll(t2, t1, 2); // sll t2, t1, 2 - c->lwu(a3, 12, a1); // lwu a3, 12(a1) - c->addu(t1, t2, t1); // addu t1, t2, t1 - c->lwu(a1, 8, a1); // lwu a1, 8(a1) - c->addu(t0, a2, t0); // addu t0, a2, t0 - c->sw(a3, 48, a0); // sw a3, 48(a0) - c->sll(a3, t1, 4); // sll a3, t1, 4 - c->sw(a1, 52, a0); // sw a1, 52(a0) - c->daddiu(a1, t0, 16); // daddiu a1, t0, 16 - c->lhu(t0, 0, t0); // lhu t0, 0(t0) - c->daddu(a3, a2, a3); // daddu a3, a2, a3 - c->sw(a2, 4, a0); // sw a2, 4(a0) - c->sll(a2, t0, 4); // sll a2, t0, 4 - c->sw(a3, 8, a0); // sw a3, 8(a0) - c->daddu(a2, a1, a2); // daddu a2, a1, a2 - c->sw(a1, 12, a0); // sw a1, 12(a0) - c->daddiu(a1, a2, 16); // daddiu a1, a2, 16 - c->lhu(a2, 0, a2); // lhu a2, 0(a2) - c->sll(t2, a2, 4); // sll t2, a2, 4 - c->lhu(a2, 14, a1); // lhu a2, 14(a1) - c->daddiu(a3, a1, 32); // daddiu a3, a1, 32 - c->lhu(t0, 10, a1); // lhu t0, 10(a1) - c->daddu(t0, t0, a1); // daddu t0, t0, a1 - c->lhu(t1, 12, a1); // lhu t1, 12(a1) - c->daddu(t1, t1, a1); // daddu t1, t1, a1 - c->lhu(t3, 0, a1); // lhu t3, 0(a1) - c->daddu(t2, a1, t2); // daddu t2, a1, t2 - c->sw(a1, 16, a0); // sw a1, 16(a0) - c->daddiu(t2, t2, 16); // daddiu t2, t2, 16 - c->sw(a3, 20, a0); // sw a3, 20(a0) - c->daddu(a1, a2, a1); // daddu a1, a2, a1 - c->sw(t1, 28, a0); // sw t1, 28(a0) - // nop // sll r0, r0, 0 - c->sw(a1, 32, a0); // sw a1, 32(a0) - //beq r0, r0, L17 // beq r0, r0, L17 - c->sw(t0, 24, a0); // sw t0, 24(a0) - goto block_16; // branch always - - - block_16: - c->daddiu(a1, t2, 16); // daddiu a1, t2, 16 - c->lwu(a3, 12, t2); // lwu a3, 12(t2) - c->daddiu(a2, a1, 112); // daddiu a2, a1, 112 - c->sw(a3, 44, a0); // sw a3, 44(a0) - // nop // sll r0, r0, 0 - c->lhu(a3, -16, a2); // lhu a3, -16(a2) - // nop // sll r0, r0, 0 - c->sw(a1, 36, a0); // sw a1, 36(a0) - // nop // sll r0, r0, 0 - c->sw(a2, 40, a0); // sw a2, 40(a0) - // nop // sll r0, r0, 0 - c->sw(v1, 56, a0); // sw v1, 56(a0) - // nop // sll r0, r0, 0 - c->lw(v1, 744, at); // lw v1, 744(at) - // nop // sll r0, r0, 0 - c->lw(a0, 76, at); // lw a0, 76(at) - c->dsubu(v1, a0, v1); // dsubu v1, a0, v1 - // nop // sll r0, r0, 0 - bc = ((s64)c->sgpr64(v1)) >= 0; // bgez v1, L51 - // nop // sll r0, r0, 0 - if (bc) {goto block_70;} // branch non-likely - - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->lw(v1, 684, at); // lw v1, 684(at) - c->lui(a1, 1); // lui a1, 1 - c->lw(a0, 688, at); // lw a0, 688(at) - c->dsubu(a1, v1, a1); // dsubu a1, v1, a1 - c->lw(a2, 692, at); // lw a2, 692(at) - bc = ((s64)c->sgpr64(a1)) > 0; // bgtz a1, L19 - c->lw(a1, 728, at); // lw a1, 728(at) - if (bc) {goto block_23;} // branch non-likely - - bc = c->sgpr64(a2) != 0; // bne a2, r0, L18 - c->lw(v1, 732, at); // lw v1, 732(at) - if (bc) {goto block_21;} // branch non-likely - - c->xori(a1, v1, 3248); // xori a1, v1, 3248 - c->mov64(v1, a0); // or v1, a0, r0 - c->daddiu(a3, a1, 2880); // daddiu a3, a1, 2880 - c->sw(a1, 732, at); // sw a1, 732(at) - c->xori(a2, a3, 7264); // xori a2, a3, 7264 - c->sw(a3, 728, at); // sw a3, 728(at) - c->addiu(a3, r0, 0); // addiu a3, r0, 0 - c->daddu(a1, a1, r0); // daddu a1, a1, r0 - c->andi(a2, a2, 65535); // andi a2, a2, 65535 - c->sw(a3, 724, at); // sw a3, 724(at) - bc = c->sgpr64(a0) != 0; // bne a0, r0, L20 - c->sw(a2, 736, at); // sw a2, 736(at) - if (bc) {goto block_24;} // branch non-likely - - c->addiu(v1, r0, 1); // addiu v1, r0, 1 - // nop // sll r0, r0, 0 - //beq r0, r0, L21 // beq r0, r0, L21 - c->sw(v1, 740, at); // sw v1, 740(at) - goto block_25; // branch always - - - block_21: - c->xori(a1, v1, 3248); // xori a1, v1, 3248 - c->mov64(v1, a0); // or v1, a0, r0 - c->daddiu(a3, a1, 2880); // daddiu a3, a1, 2880 - c->sw(a1, 732, at); // sw a1, 732(at) - c->xori(a2, a3, 7264); // xori a2, a3, 7264 - c->sw(a3, 728, at); // sw a3, 728(at) - c->addiu(a3, r0, 1); // addiu a3, r0, 1 - c->daddiu(a1, a1, 528); // daddiu a1, a1, 528 - c->andi(a2, a2, 65535); // andi a2, a2, 65535 - c->sw(a3, 724, at); // sw a3, 724(at) - bc = c->sgpr64(a0) != 0; // bne a0, r0, L20 - c->sw(a2, 736, at); // sw a2, 736(at) - if (bc) {goto block_24;} // branch non-likely - - c->addiu(v1, r0, 1); // addiu v1, r0, 1 - // nop // sll r0, r0, 0 - //beq r0, r0, L21 // beq r0, r0, L21 - c->sw(v1, 740, at); // sw v1, 740(at) - goto block_25; // branch always - - - block_23: - c->mov64(v1, v1); // or v1, v1, r0 - c->lw(a2, 736, at); // lw a2, 736(at) - c->addiu(a0, r0, 2); // addiu a0, r0, 2 - // nop // sll r0, r0, 0 - c->xor_(a1, a1, a2); // xor a1, a1, a2 - c->sw(a0, 724, at); // sw a0, 724(at) - //beq r0, r0, L20 // beq r0, r0, L20 - c->sw(a1, 728, at); // sw a1, 728(at) - goto block_24; // branch always - - - block_24: - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - c->ori(a2, r0, 65535); // ori a2, r0, 65535 - c->lui(a0, 4096); // lui a0, 4096 - // nop // sll r0, r0, 0 - c->ori(a0, a0, 54272); // ori a0, a0, 54272 - c->and_(a1, a1, a2); // and a1, a1, a2 - c->addiu(a2, r0, 324); // addiu a2, r0, 324 - { - // spr to - sadr = c->sgpr64(a1); - u32 tadr = c->sgpr64(v1); - spad_to_dma_blerc_chain(cache.fake_scratchpad_data, sadr & 0x3fff, tadr); - } -// c->sw(a1, 128, a0); // sw a1, 128(a0) -// c->sw(v1, 48, a0); // sw v1, 48(a0) -// c->sw(r0, 32, a0); // sw r0, 32(a0) -// c->sw(a2, 0, a0); // sw a2, 0(a0) - - block_25: - // nop // sll r0, r0, 0 - c->gprs[v1].du64[0] = 0; // or v1, r0, r0 - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - c->daddiu(t2, at, 640); // daddiu t2, at, 640 - c->lw(a0, 716, at); // lw a0, 716(at) - // nop // sll r0, r0, 0 - c->lw(a1, 56, t2); // lw a1, 56(t2) - // nop // sll r0, r0, 0 - c->lw(v1, 4, t2); // lw v1, 4(t2) - // nop // sll r0, r0, 0 - c->lw(v1, 8, t2); // lw v1, 8(t2) - // nop // sll r0, r0, 0 - c->lw(t3, 12, t2); // lw t3, 12(t2) - // nop // sll r0, r0, 0 - c->lw(t1, 16, t2); // lw t1, 16(t2) - // nop // sll r0, r0, 0 - c->lw(v1, 32, t2); // lw v1, 32(t2) - // nop // sll r0, r0, 0 - c->lw(t5, 20, t2); // lw t5, 20(t2) - // nop // sll r0, r0, 0 - c->lw(t6, 24, t2); // lw t6, 24(t2) - // nop // sll r0, r0, 0 - c->lw(t4, 28, t2); // lw t4, 28(t2) - // nop // sll r0, r0, 0 - c->lw(a3, 36, t2); // lw a3, 36(t2) - // nop // sll r0, r0, 0 - c->lw(v1, 40, t2); // lw v1, 40(t2) - // nop // sll r0, r0, 0 - c->lw(t0, 0, t2); // lw t0, 0(t2) - // nop // sll r0, r0, 0 - c->lhu(a2, 20, t1); // lhu a2, 20(t1) - // nop // sll r0, r0, 0 - c->lhu(t8, 18, t1); // lhu t8, 18(t1) - c->daddiu(a2, a1, -2); // daddiu a2, a1, -2 - c->lbu(a1, 2, t1); // lbu a1, 2(t1) - bc = c->sgpr64(a2) == 0; // beq a2, r0, L38 - c->lbu(a2, 3, t1); // lbu a2, 3(t1) - if (bc) {goto block_52;} // branch non-likely - - c->daddu(t7, t8, t8); // daddu t7, t8, t8 - c->addiu(t9, r0, -16); // addiu t9, r0, -16 - c->daddiu(ra, t7, 15); // daddiu ra, t7, 15 - c->daddiu(t7, a0, 32); // daddiu t7, a0, 32 - c->and_(t9, ra, t9); // and t9, ra, t9 - c->lq(ra, 16, t1); // lq ra, 16(t1) - c->daddu(t9, t7, t9); // daddu t9, t7, t9 - c->sq(ra, 16, a0); // sq ra, 16(a0) - c->dsll(ra, t8, 5); // dsll ra, t8, 5 - c->sw(t7, 0, a0); // sw t7, 0(a0) - c->daddu(ra, t9, ra); // daddu ra, t9, ra - c->sw(t9, 4, a0); // sw t9, 4(a0) - c->daddiu(t8, t8, 7); // daddiu t8, t8, 7 - c->sw(ra, 8, a0); // sw ra, 8(a0) - c->dsra(t9, t8, 3); // dsra t9, t8, 3 - // nop // sll r0, r0, 0 - c->addiu(t8, r0, 2); // addiu t8, r0, 2 - // nop // sll r0, r0, 0 - c->mult3(s3, t8, t9); // mult3 s3, t8, t9 - c->mov64(ra, t6); // or ra, t6, r0 - c->mov64(t6, t8); // or t6, t8, r0 - c->mov64(t7, t7); // or t7, t7, r0 - c->addiu(t8, r0, 513); // addiu t8, r0, 513 - c->addiu(t9, r0, 257); // addiu t9, r0, 257 - c->dsll(s1, t8, 18); // dsll s1, t8, 18 - c->dsll(s2, t9, 16); // dsll s2, t9, 16 - c->or_(t8, t8, s1); // or t8, t8, s1 - c->or_(t9, t9, s2); // or t9, t9, s2 - c->dsll32(s1, t8, 4); // dsll32 s1, t8, 4 - c->dsll32(s2, t9, 0); // dsll32 s2, t9, 0 - c->or_(t8, t8, s1); // or t8, t8, s1 - c->or_(t9, t9, s2); // or t9, t9, s2 - c->pcpyld(t8, t8, t8); // pcpyld t8, t8, t8 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pcpyld(t9, t9, t9); // pcpyld t9, t9, t9 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->daddu(s3, s3, ra); // daddu s3, s3, ra - c->mfc1(r0, f31); // mfc1 r0, f31 - c->lhu(s2, 0, ra); // lhu s2, 0(ra) - c->daddu(ra, ra, t6); // daddu ra, ra, t6 - // nop // sll r0, r0, 0 - c->mov64(t5, t5); // or t5, t5, r0 - // nop // sll r0, r0, 0 - c->pextlb(s2, s2, s2); // pextlb s2, s2, s2 - //beq r0, r0, L23 // beq r0, r0, L23 - c->pextlb(s1, s2, s2); // pextlb s1, s2, s2 - goto block_28; // branch always - - - block_27: - c->daddiu(t5, t5, 16); // daddiu t5, t5, 16 - // nop // sll r0, r0, 0 - c->daddu(ra, ra, t6); // daddu ra, ra, t6 - c->daddiu(t7, t7, 32); // daddiu t7, t7, 32 - c->pextlb(s1, s1, s1); // pextlb s1, s1, s1 - c->sq(s2, -16, t7); // sq s2, -16(t7) - - block_28: - c->pextlb(s2, s1, s1); // pextlb s2, s1, s1 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pand(s2, s2, t8); // pand s2, s2, t8 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pceqb(s1, s2, t8); // pceqb s1, s2, t8 - c->lq(s2, 0, t5); // lq s2, 0(t5) - c->pand(v0, s1, t9); // pand v0, s1, t9 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlb(s1, v0, s2); // pextlb s1, v0, s2 - c->lhu(s0, 0, ra); // lhu s0, 0(ra) - c->pextub(s2, v0, s2); // pextub s2, v0, s2 - c->sq(s1, 0, t7); // sq s1, 0(t7) - bc = c->sgpr64(ra) != c->sgpr64(s3); // bne ra, s3, L22 - c->pextlb(s1, s0, s0); // pextlb s1, s0, s0 - if (bc) {goto block_27;} // branch non-likely - - // nop // sll r0, r0, 0 - c->sq(s2, 16, t7); // sq s2, 16(t7) - c->gprs[t5].du64[0] = 0; // or t5, r0, r0 - bc = c->sgpr64(a2) == 0; // beq a2, r0, L24 - // nop // sll r0, r0, 0 - if (bc) {goto block_31;} // branch non-likely - - c->daddiu(t6, a2, 7); // daddiu t6, a2, 7 - c->daddiu(t5, at, 700); // daddiu t5, at, 700 - // nop // sll r0, r0, 0 - c->lw(t2, 32, t2); // lw t2, 32(t2) - c->sra(t6, t6, 3); // sra t6, t6, 3 - c->sh(r0, 0, t5); // sh r0, 0(t5) - // nop // sll r0, r0, 0 - c->sh(a1, 4, t5); // sh a1, 4(t5) - // nop // sll r0, r0, 0 - c->sh(a2, 2, t5); // sh a2, 2(t5) - // nop // sll r0, r0, 0 - c->sw(t2, 8, t5); // sw t2, 8(t5) - // nop // sll r0, r0, 0 - c->sw(t5, 80, at); // sw t5, 80(at) - //beq r0, r0, L25 // beq r0, r0, L25 - // nop // sll r0, r0, 0 - goto block_32; // branch always - - - block_31: - // nop // sll r0, r0, 0 - c->sw(r0, 80, at); // sw r0, 80(at) - // nop // sll r0, r0, 0 - - block_32: - c->daddu(t5, a1, a2); // daddu t5, a1, a2 - c->mov64(t2, t4); // or t2, t4, r0 - c->daddiu(t4, t5, 3); // daddiu t4, t5, 3 - vis[vi09] = c->gpr_src(a2).du16[0]; // ctc2.i vi9, a2 - c->sra(t5, t4, 2); // sra t5, t4, 2 - c->lq(t4, 0, t2); // lq t4, 0(t2) - c->sll(t5, t5, 4); // sll t5, t5, 4 - c->daddiu(t2, t2, 16); // daddiu t2, t2, 16 - c->pextub(t7, t4, r0); // pextub t7, t4, r0 - vis[vi08] = c->gpr_src(a1).du16[0]; // ctc2.i vi8, a1 - c->pextlb(t6, t4, r0); // pextlb t6, t4, r0 - vis[vi09] = c->gpr_src(a2).du16[0]; // ctc2.i vi9, a2 - c->daddu(t4, t2, t5); // daddu t4, t2, t5 - // Unknown instr: vcallms 104 - vcallms_104(); - - block_33: - c->pextuh(t5, t7, r0); // pextuh t5, t7, r0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlh(t7, t7, r0); // pextlh t7, t7, r0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextuh(t8, t6, r0); // pextuh t8, t6, r0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlh(t6, t6, r0); // pextlh t6, t6, r0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psraw(t9, t6, 19); // psraw t9, t6, 19 - c->lq(t6, 0, t2); // lq t6, 0(t2) - c->psraw(t8, t8, 19); // psraw t8, t8, 19 - c->mov128_vf_gpr(vf5, t9); // qmtc2.ni vf5, t9 - c->psraw(t7, t7, 19); // psraw t7, t7, 19 - c->mov128_vf_gpr(vf6, t8); // qmtc2.ni vf6, t8 - c->psraw(t5, t5, 19); // psraw t5, t5, 19 - c->mov128_vf_gpr(vf7, t7); // qmtc2.ni vf7, t7 - c->pextub(t7, t6, r0); // pextub t7, t6, r0 - c->mov128_vf_gpr(vf8, t5); // qmtc2.ni vf8, t5 - c->pextlb(t6, t6, r0); // pextlb t6, t6, r0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - // Unknown instr: vcallms 114 - vcallms_114(c); - bc = c->sgpr64(t2) != c->sgpr64(t4); // bne t2, t4, L26 - c->daddiu(t2, t2, 16); // daddiu t2, t2, 16 - if (bc) {goto block_33;} // branch non-likely - - // nop // sll r0, r0, 0 - c->mov64(t2, t3); // or t2, t3, r0 - bc = c->sgpr64(a1) == 0; // beq a1, r0, L34 - c->daddiu(t4, a1, -4); // daddiu t4, a1, -4 - if (bc) {goto block_48;} // branch non-likely - - bc = ((s64)c->sgpr64(t4)) <= 0; // blez t4, L29 - // nop // sll r0, r0, 0 - if (bc) {goto block_39;} // branch non-likely - - c->mov64(t2, t3); // or t2, t3, r0 - c->ld(t5, 0, t3); // ld t5, 0(t3) - c->daddiu(t4, a1, -8); // daddiu t4, a1, -8 - c->ld(t6, 16, t3); // ld t6, 16(t3) - c->pextlh(t7, t5, r0); // pextlh t7, t5, r0 - c->ld(t5, 32, t3); // ld t5, 32(t3) - c->pextlh(ra, t6, r0); // pextlh ra, t6, r0 - c->ld(t6, 48, t3); // ld t6, 48(t3) - c->pextlh(t8, t5, r0); // pextlh t8, t5, r0 - c->lwu(t5, 8, t3); // lwu t5, 8(t3) - c->pextlh(t9, t6, r0); // pextlh t9, t6, r0 - c->lwu(t6, 24, t3); // lwu t6, 24(t3) - c->psraw(s3, t7, 10); // psraw s3, t7, 10 - c->lwu(t7, 40, t3); // lwu t7, 40(t3) - c->psraw(ra, ra, 10); // psraw ra, ra, 10 - c->lwu(t3, 56, t3); // lwu t3, 56(t3) - c->psraw(t8, t8, 10); // psraw t8, t8, 10 - c->mov128_vf_gpr(vf1, s3); // qmtc2.ni vf1, s3 - c->psraw(t9, t9, 10); // psraw t9, t9, 10 - c->mov128_vf_gpr(vf2, ra); // qmtc2.ni vf2, ra - c->daddiu(t2, t2, 64); // daddiu t2, t2, 64 - c->mov128_vf_gpr(vf3, t8); // qmtc2.ni vf3, t8 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf4, t9); // qmtc2.ni vf4, t9 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf13, t5); // qmtc2.ni vf13, t5 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf14, t6); // qmtc2.ni vf14, t6 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf15, t7); // qmtc2.ni vf15, t7 - bc = ((s64)c->sgpr64(t4)) <= 0; // blez t4, L28 - c->mov128_vf_gpr(vf16, t3); // qmtc2.ni vf16, t3 - if (bc) {goto block_38;} // branch non-likely - - - block_37: - // Unknown instr: vcallms 122 - vcallms_122(c); - c->ld(t3, 0, t2); // ld t3, 0(t2) - c->daddiu(t4, t4, -4); // daddiu t4, t4, -4 - c->ld(t5, 16, t2); // ld t5, 16(t2) - c->pextlh(t8, t3, r0); // pextlh t8, t3, r0 - c->ld(t3, 32, t2); // ld t3, 32(t2) - c->pextlh(t9, t5, r0); // pextlh t9, t5, r0 - c->ld(t5, 48, t2); // ld t5, 48(t2) - c->pextlh(t6, t3, r0); // pextlh t6, t3, r0 - c->lwu(t3, 8, t2); // lwu t3, 8(t2) - c->pextlh(t7, t5, r0); // pextlh t7, t5, r0 - c->lwu(t5, 24, t2); // lwu t5, 24(t2) - c->psraw(ra, t8, 10); // psraw ra, t8, 10 - c->lwu(t8, 40, t2); // lwu t8, 40(t2) - c->psraw(t9, t9, 10); // psraw t9, t9, 10 - c->lwu(s3, 56, t2); // lwu s3, 56(t2) - c->psraw(t6, t6, 10); // psraw t6, t6, 10 - c->mov128_vf_gpr(vf1, ra); // qmtc2.ni vf1, ra - c->psraw(t7, t7, 10); // psraw t7, t7, 10 - c->mov128_vf_gpr(vf2, t9); // qmtc2.ni vf2, t9 - c->daddiu(t2, t2, 64); // daddiu t2, t2, 64 - c->mov128_vf_gpr(vf3, t6); // qmtc2.ni vf3, t6 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf4, t7); // qmtc2.ni vf4, t7 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf13, t3); // qmtc2.ni vf13, t3 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf14, t5); // qmtc2.ni vf14, t5 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf15, t8); // qmtc2.ni vf15, t8 - bc = ((s64)c->sgpr64(t4)) > 0; // bgtz t4, L27 - c->mov128_vf_gpr(vf16, s3); // qmtc2.ni vf16, s3 - if (bc) {goto block_37;} // branch non-likely - - - block_38: - // Unknown instr: vcallms 122 - vcallms_122(c); - // nop // sll r0, r0, 0 - - block_39: - bc = c->sgpr64(a2) == 0; // beq a2, r0, L33 - c->andi(t3, a1, 3); // andi t3, a1, 3 - if (bc) {goto block_47;} // branch non-likely - - bc = c->sgpr64(t3) == 0; // beq t3, r0, L33 - c->daddiu(t3, t3, -1); // daddiu t3, t3, -1 - if (bc) {goto block_47;} // branch non-likely - - bc = c->sgpr64(t3) == 0; // beq t3, r0, L30 - c->daddiu(t3, t3, -1); // daddiu t3, t3, -1 - if (bc) {goto block_44;} // branch non-likely - - bc = c->sgpr64(t3) == 0; // beq t3, r0, L31 - c->daddiu(t3, t3, -1); // daddiu t3, t3, -1 - if (bc) {goto block_45;} // branch non-likely - - //beq r0, r0, L32 // beq r0, r0, L32 - // nop // sll r0, r0, 0 - goto block_46; // branch always - - - block_44: - // nop // sll r0, r0, 0 - c->ld(t3, 0, t2); // ld t3, 0(t2) - // nop // sll r0, r0, 0 - c->ld(t4, 16, t2); // ld t4, 16(t2) - c->pextlh(t3, t3, r0); // pextlh t3, t3, r0 - c->ld(t6, 24, t2); // ld t6, 24(t2) - c->pextlh(t4, t4, r0); // pextlh t4, t4, r0 - c->lwu(t5, 8, t2); // lwu t5, 8(t2) - c->pextlh(t6, t6, r0); // pextlh t6, t6, r0 - c->lwu(t7, 32, t2); // lwu t7, 32(t2) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->psraw(t3, t3, 10); // psraw t3, t3, 10 - c->mov128_vf_gpr(vf13, t5); // qmtc2.ni vf13, t5 - c->psraw(t4, t4, 10); // psraw t4, t4, 10 - c->mov128_vf_gpr(vf14, t7); // qmtc2.ni vf14, t7 - c->psraw(t5, t6, 10); // psraw t5, t6, 10 - c->mov128_vf_gpr(vf1, t3); // qmtc2.ni vf1, t3 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf2, t4); // qmtc2.ni vf2, t4 - c->daddiu(t2, t2, 40); // daddiu t2, t2, 40 - c->mov128_vf_gpr(vf3, t5); // qmtc2.ni vf3, t5 - //beq r0, r0, L34 // beq r0, r0, L34 - // Unknown instr: vcallms 134 - vcallms_134(c); - goto block_48; // branch always - - - block_45: - // nop // sll r0, r0, 0 - c->ld(t4, 0, t2); // ld t4, 0(t2) - // nop // sll r0, r0, 0 - c->ld(t3, 16, t2); // ld t3, 16(t2) - c->pextlh(t8, t4, r0); // pextlh t8, t4, r0 - c->ld(t5, 32, t2); // ld t5, 32(t2) - c->pextlh(t4, t3, r0); // pextlh t4, t3, r0 - c->ld(t3, 40, t2); // ld t3, 40(t2) - c->pextlh(t5, t5, r0); // pextlh t5, t5, r0 - c->lwu(t6, 8, t2); // lwu t6, 8(t2) - c->pextlh(t7, t3, r0); // pextlh t7, t3, r0 - c->lwu(t3, 24, t2); // lwu t3, 24(t2) - c->psraw(t9, t8, 10); // psraw t9, t8, 10 - c->lwu(t8, 48, t2); // lwu t8, 48(t2) - c->psraw(t4, t4, 10); // psraw t4, t4, 10 - c->mov128_vf_gpr(vf13, t6); // qmtc2.ni vf13, t6 - c->psraw(t5, t5, 10); // psraw t5, t5, 10 - c->mov128_vf_gpr(vf1, t9); // qmtc2.ni vf1, t9 - c->psraw(t6, t7, 10); // psraw t6, t7, 10 - c->mov128_vf_gpr(vf2, t4); // qmtc2.ni vf2, t4 - c->daddiu(t2, t2, 56); // daddiu t2, t2, 56 - c->mov128_vf_gpr(vf3, t5); // qmtc2.ni vf3, t5 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf4, t6); // qmtc2.ni vf4, t6 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf14, t3); // qmtc2.ni vf14, t3 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf15, t8); // qmtc2.ni vf15, t8 - //beq r0, r0, L34 // beq r0, r0, L34 - // Unknown instr: vcallms 145 - vcallms_145(c); - goto block_48; // branch always - - - block_46: - // nop // sll r0, r0, 0 - c->ld(t4, 0, t2); // ld t4, 0(t2) - // nop // sll r0, r0, 0 - c->ld(t3, 16, t2); // ld t3, 16(t2) - c->pextlh(t7, t4, r0); // pextlh t7, t4, r0 - c->ld(t4, 32, t2); // ld t4, 32(t2) - c->pextlh(t5, t3, r0); // pextlh t5, t3, r0 - c->lwu(t3, 8, t2); // lwu t3, 8(t2) - c->pextlh(t6, t4, r0); // pextlh t6, t4, r0 - c->lwu(t4, 24, t2); // lwu t4, 24(t2) - c->psraw(t7, t7, 10); // psraw t7, t7, 10 - c->lwu(t8, 40, t2); // lwu t8, 40(t2) - c->psraw(t5, t5, 10); // psraw t5, t5, 10 - c->mov128_vf_gpr(vf13, t3); // qmtc2.ni vf13, t3 - c->psraw(t3, t6, 10); // psraw t3, t6, 10 - c->mov128_vf_gpr(vf1, t7); // qmtc2.ni vf1, t7 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf2, t5); // qmtc2.ni vf2, t5 - c->daddiu(t2, t2, 48); // daddiu t2, t2, 48 - c->mov128_vf_gpr(vf3, t3); // qmtc2.ni vf3, t3 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf14, t4); // qmtc2.ni vf14, t4 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf15, t8); // qmtc2.ni vf15, t8 - //beq r0, r0, L34 // beq r0, r0, L34 - // Unknown instr: vcallms 157 - vcallms_157(c); - goto block_48; // branch always - - - block_47: - // nop // sll r0, r0, 0 - c->ld(t3, 0, t2); // ld t3, 0(t2) - // nop // sll r0, r0, 0 - c->ld(t4, 16, t2); // ld t4, 16(t2) - c->pextlh(t7, t3, r0); // pextlh t7, t3, r0 - c->ld(t3, 32, t2); // ld t3, 32(t2) - c->pextlh(t8, t4, r0); // pextlh t8, t4, r0 - c->ld(t4, 48, t2); // ld t4, 48(t2) - c->pextlh(t5, t3, r0); // pextlh t5, t3, r0 - c->lwu(t3, 8, t2); // lwu t3, 8(t2) - c->pextlh(t6, t4, r0); // pextlh t6, t4, r0 - c->lwu(t4, 24, t2); // lwu t4, 24(t2) - c->psraw(t9, t7, 10); // psraw t9, t7, 10 - c->lwu(t7, 40, t2); // lwu t7, 40(t2) - c->psraw(t8, t8, 10); // psraw t8, t8, 10 - c->lwu(ra, 56, t2); // lwu ra, 56(t2) - c->psraw(t5, t5, 10); // psraw t5, t5, 10 - c->mov128_vf_gpr(vf1, t9); // qmtc2.ni vf1, t9 - c->psraw(t6, t6, 10); // psraw t6, t6, 10 - c->mov128_vf_gpr(vf2, t8); // qmtc2.ni vf2, t8 - c->daddiu(t2, t2, 64); // daddiu t2, t2, 64 - c->mov128_vf_gpr(vf3, t5); // qmtc2.ni vf3, t5 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf4, t6); // qmtc2.ni vf4, t6 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf13, t3); // qmtc2.ni vf13, t3 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf14, t4); // qmtc2.ni vf14, t4 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf15, t7); // qmtc2.ni vf15, t7 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf16, ra); // qmtc2.ni vf16, ra - bc = c->sgpr64(a2) == 0; // beq a2, r0, L37 - // Unknown instr: vcallms 122 - vcallms_122(c); - if (bc) {goto block_51;} // branch non-likely - - - block_48: - // nop // sll r0, r0, 0 - c->ld(t4, 0, t2); // ld t4, 0(t2) - c->daddiu(t3, a2, -2); // daddiu t3, a2, -2 - c->ld(t5, 8, t2); // ld t5, 8(t2) - c->pextlh(t4, t4, r0); // pextlh t4, t4, r0 - c->ld(t6, 24, t2); // ld t6, 24(t2) - c->pextlh(t5, t5, r0); // pextlh t5, t5, r0 - c->ld(t7, 32, t2); // ld t7, 32(t2) - c->pextlh(t6, t6, r0); // pextlh t6, t6, r0 - c->lwu(t8, 16, t2); // lwu t8, 16(t2) - c->pextlh(t7, t7, r0); // pextlh t7, t7, r0 - c->lwu(t9, 40, t2); // lwu t9, 40(t2) - c->psraw(t4, t4, 10); // psraw t4, t4, 10 - c->mov128_vf_gpr(vf13, t8); // qmtc2.ni vf13, t8 - c->psraw(t5, t5, 10); // psraw t5, t5, 10 - c->mov128_vf_gpr(vf14, t9); // qmtc2.ni vf14, t9 - c->psraw(t6, t6, 10); // psraw t6, t6, 10 - c->mov128_vf_gpr(vf1, t4); // qmtc2.ni vf1, t4 - c->psraw(t4, t7, 10); // psraw t4, t7, 10 - c->mov128_vf_gpr(vf2, t5); // qmtc2.ni vf2, t5 - c->daddiu(t2, t2, 48); // daddiu t2, t2, 48 - c->mov128_vf_gpr(vf3, t6); // qmtc2.ni vf3, t6 - bc = ((s64)c->sgpr64(t3)) <= 0; // blez t3, L36 - c->mov128_vf_gpr(vf4, t4); // qmtc2.ni vf4, t4 - if (bc) {goto block_50;} // branch non-likely - - - block_49: - // Unknown instr: vcallms 168 - vcallms_168(c); - c->ld(t4, 0, t2); // ld t4, 0(t2) - c->daddiu(t3, t3, -2); // daddiu t3, t3, -2 - c->ld(t5, 8, t2); // ld t5, 8(t2) - c->pextlh(t4, t4, r0); // pextlh t4, t4, r0 - c->ld(t6, 24, t2); // ld t6, 24(t2) - c->pextlh(t5, t5, r0); // pextlh t5, t5, r0 - c->ld(t7, 32, t2); // ld t7, 32(t2) - c->pextlh(t6, t6, r0); // pextlh t6, t6, r0 - c->lwu(t8, 16, t2); // lwu t8, 16(t2) - c->pextlh(t7, t7, r0); // pextlh t7, t7, r0 - c->lwu(t9, 40, t2); // lwu t9, 40(t2) - c->psraw(t4, t4, 10); // psraw t4, t4, 10 - c->mov128_vf_gpr(vf13, t8); // qmtc2.ni vf13, t8 - c->psraw(t5, t5, 10); // psraw t5, t5, 10 - c->mov128_vf_gpr(vf14, t9); // qmtc2.ni vf14, t9 - c->psraw(t6, t6, 10); // psraw t6, t6, 10 - c->mov128_vf_gpr(vf1, t4); // qmtc2.ni vf1, t4 - c->psraw(t4, t7, 10); // psraw t4, t7, 10 - c->mov128_vf_gpr(vf2, t5); // qmtc2.ni vf2, t5 - c->daddiu(t2, t2, 48); // daddiu t2, t2, 48 - c->mov128_vf_gpr(vf3, t6); // qmtc2.ni vf3, t6 - bc = ((s64)c->sgpr64(t3)) > 0; // bgtz t3, L35 - c->mov128_vf_gpr(vf4, t4); // qmtc2.ni vf4, t4 - if (bc) {goto block_49;} // branch non-likely - - - block_50: - // Unknown instr: vcallms 168 - vcallms_168(c); - // nop // sll r0, r0, 0 - - block_51: - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - - block_52: - c->mov64(t3, a1); // or t3, a1, r0 - c->lw(t6, 80, a3); // lw t6, 80(a3) - c->mov64(t4, v1); // or t4, v1, r0 - c->lw(t1, 4, t1); // lw t1, 4(t1) - c->daddiu(t5, at, 12048); // daddiu t5, at, 12048 - c->sw(t6, 92, at); // sw t6, 92(at) - c->pextlb(t2, r0, t1); // pextlb t2, r0, t1 - c->lq(t1, 0, a3); // lq t1, 0(a3) - c->pextlh(t7, t6, t6); // pextlh t7, t6, t6 - c->lq(t6, 16, a3); // lq t6, 16(a3) - c->pextlh(t8, t7, t7); // pextlh t8, t7, t7 - c->lq(t7, 32, a3); // lq t7, 32(a3) - c->pmulth(r0, t2, t8); // pmulth r0, t2, t8 - c->lq(t2, 48, a3); // lq t2, 48(a3) - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf1, t1); // qmtc2.ni vf1, t1 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf2, t6); // qmtc2.ni vf2, t6 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf3, t7); // qmtc2.ni vf3, t7 - // nop // sll r0, r0, 0 - c->mov128_vf_gpr(vf4, t2); // qmtc2.ni vf4, t2 - // Unknown instr: vcallms 178 - vcallms_178(c); - c->mov64(t1, t0); // or t1, t0, r0 - c->pextlw(t2, t1, t1); // pextlw t2, t1, t1 - c->lw(t1, 4, a0); // lw t1, 4(a0) - c->pcpyld(t2, t2, t2); // pcpyld t2, t2, t2 - c->lw(t8, 96, at); // lw t8, 96(at) - c->pmfhl_lh(t7); // pmfhl.lh t7 - c->lw(t6, 84, a3); // lw t6, 84(a3) - c->pextlb(t8, r0, t8); // pextlb t8, r0, t8 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psrlh(t7, t7, 7); // psrlh t7, t7, 7 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pmulth(r0, t7, t8); // pmulth r0, t7, t8 - c->mfc1(r0, f31); // mfc1 r0, f31 - bc = c->sgpr64(t6) != 0; // bne t6, r0, L39 - c->mfc1(r0, f31); // mfc1 r0, f31 - if (bc) {goto block_54;} // branch non-likely - - // nop // sll r0, r0, 0 - c->addiu(t6, r0, 8); // addiu t6, r0, 8 - //beq r0, r0, L40 // beq r0, r0, L40 - c->sh(t6, 11984, at); // sh t6, 11984(at) - goto block_55; // branch always - - - block_54: - // nop // sll r0, r0, 0 - c->addiu(t6, r0, 6); // addiu t6, r0, 6 - // nop // sll r0, r0, 0 - c->sh(t6, 11984, at); // sh t6, 11984(at) - - block_55: - c->pmfhl_lh(t6); // pmfhl.lh t6 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psrlh(t6, t6, 7); // psrlh t6, t6, 7 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->ppacb(t6, r0, t6); // ppacb t6, r0, t6 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->lw(t7, 0, t4); // lw t7, 0(t4) - c->daddiu(t4, t4, 4); // daddiu t4, t4, 4 - c->sw(t6, 0, t5); // sw t6, 0(t5) - c->pextlb(t7, r0, t7); // pextlb t7, r0, t7 - c->sw(t6, 4, t5); // sw t6, 4(t5) - c->pextlh(t7, r0, t7); // pextlh t7, r0, t7 - c->sw(t6, 8, t5); // sw t6, 8(t5) - c->psllw(t7, t7, 2); // psllw t7, t7, 2 - c->sw(t6, 12, t5); // sw t6, 12(t5) - c->paddw(t9, t7, t2); // paddw t9, t7, t2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->dsrl32(s3, t9, 0); // dsrl32 s3, t9, 0 - c->mov128_gpr_vf(t7, vf17); // qmfc2.ni t7, vf17 - c->pcpyud(t5, t9, r0); // pcpyud t5, t9, r0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->dsrl32(t6, t5, 0); // dsrl32 t6, t5, 0 - c->mov128_gpr_vf(ra, vf13); // qmfc2.ni ra, vf13 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t8, vf14); // qmfc2.ni t8, vf14 - // nop // sll r0, r0, 0 - c->lwu(t9, 0, t9); // lwu t9, 0(t9) - // nop // sll r0, r0, 0 - c->lwu(s3, 0, s3); // lwu s3, 0(s3) - // nop // sll r0, r0, 0 - c->lwu(t5, 0, t5); // lwu t5, 0(t5) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(s0, vf18); // qmfc2.ni s0, vf18 - // nop // sll r0, r0, 0 - c->lwu(t6, 0, t6); // lwu t6, 0(t6) - // nop // sll r0, r0, 0 - c->sq(ra, 0, t1); // sq ra, 0(t1) - c->daddiu(t3, t3, -4); // daddiu t3, t3, -4 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - bc = ((s64)c->sgpr64(t3)) <= 0; // blez t3, L42 - // nop // sll r0, r0, 0 - if (bc) {goto block_57;} // branch non-likely - - - block_56: - // Unknown instr: vcallms 183 - vcallms_183(c); - c->sq(t7, 16, t1); // sq t7, 16(t1) - // nop // sll r0, r0, 0 - c->sw(t9, 28, t1); // sw t9, 28(t1) - // nop // sll r0, r0, 0 - c->sq(t8, 32, t1); // sq t8, 32(t1) - // nop // sll r0, r0, 0 - c->sq(s0, 48, t1); // sq s0, 48(t1) - // nop // sll r0, r0, 0 - c->sw(s3, 60, t1); // sw s3, 60(t1) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - // nop // sll r0, r0, 0 - c->lw(t8, 0, t4); // lw t8, 0(t4) - c->daddiu(t4, t4, 4); // daddiu t4, t4, 4 - c->mov128_gpr_vf(t7, vf19); // qmfc2.ni t7, vf19 - c->pextlb(t9, r0, t8); // pextlb t9, r0, t8 - c->mov128_gpr_vf(t8, vf20); // qmfc2.ni t8, vf20 - c->pextlh(t9, r0, t9); // pextlh t9, r0, t9 - c->mov128_gpr_vf(s3, vf15); // qmfc2.ni s3, vf15 - c->psllw(ra, t9, 2); // psllw ra, t9, 2 - c->mov128_gpr_vf(t9, vf16); // qmfc2.ni t9, vf16 - c->paddw(s0, ra, t2); // paddw s0, ra, t2 - c->sq(t7, 80, t1); // sq t7, 80(t1) - c->dsrl32(v0, s0, 0); // dsrl32 v0, s0, 0 - c->mov128_gpr_vf(t7, vf17); // qmfc2.ni t7, vf17 - c->pcpyud(ra, s0, r0); // pcpyud ra, s0, r0 - c->sq(s3, 64, t1); // sq s3, 64(t1) - c->dsrl32(s2, ra, 0); // dsrl32 s2, ra, 0 - c->mov128_gpr_vf(s1, vf13); // qmfc2.ni s1, vf13 - // nop // sll r0, r0, 0 - c->sq(t8, 112, t1); // sq t8, 112(t1) - // nop // sll r0, r0, 0 - c->sw(t5, 92, t1); // sw t5, 92(t1) - // nop // sll r0, r0, 0 - c->sw(t6, 124, t1); // sw t6, 124(t1) - // nop // sll r0, r0, 0 - c->sq(t9, 96, t1); // sq t9, 96(t1) - // nop // sll r0, r0, 0 - c->lwu(t9, 0, s0); // lwu t9, 0(s0) - // nop // sll r0, r0, 0 - c->lwu(s3, 0, v0); // lwu s3, 0(v0) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t8, vf14); // qmfc2.ni t8, vf14 - c->daddiu(t1, t1, 128); // daddiu t1, t1, 128 - c->mov128_gpr_vf(s0, vf18); // qmfc2.ni s0, vf18 - c->daddiu(t3, t3, -4); // daddiu t3, t3, -4 - c->lwu(t5, 0, ra); // lwu t5, 0(ra) - // nop // sll r0, r0, 0 - c->sq(s1, 0, t1); // sq s1, 0(t1) - bc = ((s64)c->sgpr64(t3)) > 0; // bgtz t3, L41 - c->lwu(t6, 0, s2); // lwu t6, 0(s2) - if (bc) {goto block_56;} // branch non-likely - - - block_57: - // Unknown instr: vcallms 211 - vcallms_211(c); - c->sq(t7, 16, t1); // sq t7, 16(t1) - // nop // sll r0, r0, 0 - c->sw(t9, 28, t1); // sw t9, 28(t1) - // nop // sll r0, r0, 0 - c->sq(t8, 32, t1); // sq t8, 32(t1) - // nop // sll r0, r0, 0 - c->sq(s0, 48, t1); // sq s0, 48(t1) - // nop // sll r0, r0, 0 - c->sw(s3, 60, t1); // sw s3, 60(t1) - // nop // sll r0, r0, 0 - // nop // vnop - // nop // sll r0, r0, 0 - c->sqc2(vf19, 80, t1); // sqc2 vf19, 80(t1) - // nop // sll r0, r0, 0 - c->sqc2(vf15, 64, t1); // sqc2 vf15, 64(t1) - // nop // sll r0, r0, 0 - c->sqc2(vf16, 96, t1); // sqc2 vf16, 96(t1) - // nop // sll r0, r0, 0 - c->sqc2(vf20, 112, t1); // sqc2 vf20, 112(t1) - // nop // sll r0, r0, 0 - c->sw(t5, 92, t1); // sw t5, 92(t1) - // nop // sll r0, r0, 0 - c->sw(t6, 124, t1); // sw t6, 124(t1) - bc = c->sgpr64(a2) == 0; // beq a2, r0, L48 - // nop // sll r0, r0, 0 - if (bc) {goto block_65;} // branch non-likely - - c->mov64(t1, t0); // or t1, t0, r0 - c->lqc2(vf29, 64, a3); // lqc2 vf29, 64(a3) - c->mov64(t0, v1); // or t0, v1, r0 - c->lwc1(f0, 72, a3); // lwc1 f0, 72(a3) - c->daddiu(t0, a1, 3); // daddiu t0, a1, 3 - c->lwc1(f2, 76, a3); // lwc1 f2, 76(a3) - c->addiu(a3, r0, -4); // addiu a3, r0, -4 - // nop // sll r0, r0, 0 - c->cvtws(f0, f0); // cvt.w.s f0, f0 - c->and_(a3, t0, a3); // and a3, t0, a3 - c->cvtws(f2, f2); // cvt.w.s f2, f2 - c->lw(t2, 4, a0); // lw t2, 4(a0) - c->mfc1(a0, f0); // mfc1 a0, f0 - c->mov64(t0, a2); // or t0, a2, r0 - c->mfc1(t3, f2); // mfc1 t3, f2 - c->dsll(a2, a1, 5); // dsll a2, a1, 5 - c->dsra(a1, t3, 1); // dsra a1, t3, 1 - c->daddu(a3, v1, a3); // daddu a3, v1, a3 - bc = c->sgpr64(a1) == 0; // beq a1, r0, L45 - c->daddu(a2, t2, a2); // daddu a2, t2, a2 - if (bc) {goto block_62;} // branch non-likely - - c->pcpyh(v1, a0); // pcpyh v1, a0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pcpyld(v1, v1, v1); // pcpyld v1, v1, v1 - c->sh(v1, 712, at); // sh v1, 712(at) - c->pcpyh(a0, a1); // pcpyh a0, a1 - c->sh(a0, 714, at); // sh a0, 714(at) - c->pcpyld(a0, a0, a0); // pcpyld a0, a0, a0 - c->ldr(s5, 0, a3); // ldr s5, 0(a3) - c->pextlw(a1, t1, t1); // pextlw a1, t1, t1 - c->ldl(s5, 7, a3); // ldl s5, 7(a3) - c->pcpyld(a1, a1, a1); // pcpyld a1, a1, a1 - c->ldr(s4, 8, a3); // ldr s4, 8(a3) - c->pextlb(t2, r0, s5); // pextlb t2, r0, s5 - c->ldl(s4, 15, a3); // ldl s4, 15(a3) - c->pextuh(t1, r0, t2); // pextuh t1, r0, t2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlh(t3, r0, t2); // pextlh t3, r0, t2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlb(t4, r0, s4); // pextlb t4, r0, s4 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextuh(t2, r0, t4); // pextuh t2, r0, t4 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlh(t5, r0, t4); // pextlh t5, r0, t4 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t3, t3, 2); // psllw t3, t3, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t4, t1, 2); // psllw t4, t1, 2 - // Unknown instr: vcallms 215 - vcallms_215(c); - - c->psllw(t6, t5, 2); // psllw t6, t5, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t5, t2, 2); // psllw t5, t2, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->paddw(t1, t3, a1); // paddw t1, t3, a1 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->paddw(t4, t4, a1); // paddw t4, t4, a1 - c->lwu(t3, 0, t1); // lwu t3, 0(t1) - c->paddw(t2, t6, a1); // paddw t2, t6, a1 - c->lwu(t9, 0, t4); // lwu t9, 0(t4) - c->paddw(t5, t5, a1); // paddw t5, t5, a1 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->dsrl32(s4, t1, 0); // dsrl32 s4, t1, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->dsrl32(s3, t4, 0); // dsrl32 s3, t4, 0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->dsrl32(ra, t2, 0); // dsrl32 ra, t2, 0 - c->daddiu(a2, a2, -128); // daddiu a2, a2, -128 - c->dsrl32(t8, t5, 0); // dsrl32 t8, t5, 0 - c->daddiu(a3, a3, 16); // daddiu a3, a3, 16 - c->pcpyud(t7, t1, r0); // pcpyud t7, t1, r0 - c->lwu(s5, 0, t2); // lwu s5, 0(t2) - c->pcpyud(t6, t4, r0); // pcpyud t6, t4, r0 - c->lwu(s2, 0, t5); // lwu s2, 0(t5) - c->pcpyud(t4, t2, r0); // pcpyud t4, t2, r0 - c->lwu(s4, 0, s4); // lwu s4, 0(s4) - c->pcpyud(t5, t5, r0); // pcpyud t5, t5, r0 - c->lwu(s3, 0, s3); // lwu s3, 0(s3) - c->pextlw(t9, t9, t3); // pextlw t9, t9, t3 - c->lwu(ra, 0, ra); // lwu ra, 0(ra) - c->pextlw(t3, s2, s5); // pextlw t3, s2, s5 - c->lwu(s2, 0, t8); // lwu s2, 0(t8) - c->pextlw(s5, s3, s4); // pextlw s5, s3, s4 - c->lwu(t8, 0, t7); // lwu t8, 0(t7) - c->pextlw(t7, s2, ra); // pextlw t7, s2, ra - c->lwu(ra, 0, t6); // lwu ra, 0(t6) - c->pextlb(t6, r0, t9); // pextlb t6, r0, t9 - c->lwu(t9, 0, t4); // lwu t9, 0(t4) - c->pextlb(t4, r0, s5); // pextlb t4, r0, s5 - c->lwu(s5, 0, t5); // lwu s5, 0(t5) - c->pextlw(ra, ra, t8); // pextlw ra, ra, t8 - c->mov128_gpr_vf(t5, vf17); // qmfc2.ni t5, vf17 - c->pextlw(t8, s5, t9); // pextlw t8, s5, t9 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlb(t9, r0, ra); // pextlb t9, r0, ra - c->mfc1(r0, f31); // mfc1 r0, f31 - c->paddh(t9, t4, t9); // paddh t9, t4, t9 - c->mov128_gpr_vf(t4, vf13); // qmfc2.ni t4, vf13 - c->pmulth(r0, t9, a0); // pmulth r0, t9, a0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlb(t3, r0, t3); // pextlb t3, r0, t3 - c->sq(t5, 144, a2); // sq t5, 144(a2) - c->pmaddh(r0, t6, v1); // pmaddh r0, t6, v1 - c->sq(t4, 128, a2); // sq t4, 128(a2) - c->pextlb(t5, r0, t7); // pextlb t5, r0, t7 - c->ldr(t1, 0, a3); // ldr t1, 0(a3) - c->pextlb(t7, r0, t8); // pextlb t7, r0, t8 - c->ldl(t1, 7, a3); // ldl t1, 7(a3) - c->pmfhl_lh(t6); // pmfhl.lh t6 - c->mov128_gpr_vf(t4, vf18); // qmfc2.ni t4, vf18 - c->paddh(t5, t5, t7); // paddh t5, t5, t7 - c->ldr(t2, 8, a3); // ldr t2, 8(a3) - c->psrlh(t6, t6, 8); // psrlh t6, t6, 8 - c->ldl(t2, 15, a3); // ldl t2, 15(a3) - c->ppacb(t6, r0, t6); // ppacb t6, r0, t6 - c->sq(t4, 176, a2); // sq t4, 176(a2) - c->dsrl32(t4, t6, 0); // dsrl32 t4, t6, 0 - c->daddiu(t0, t0, -4); // daddiu t0, t0, -4 - c->pmulth(r0, t5, a0); // pmulth r0, t5, a0 - c->mov128_gpr_vf(t5, vf14); // qmfc2.ni t5, vf14 - c->pextlb(t1, r0, t1); // pextlb t1, r0, t1 - c->sw(t6, 156, a2); // sw t6, 156(a2) - c->pmaddh(r0, t3, v1); // pmaddh r0, t3, v1 - c->sw(t4, 188, a2); // sw t4, 188(a2) - c->pextlb(t3, r0, t2); // pextlb t3, r0, t2 - c->sq(t5, 160, a2); // sq t5, 160(a2) - c->pextuh(t2, r0, t1); // pextuh t2, r0, t1 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pmfhl_lh(t6); // pmfhl.lh t6 - c->mov128_gpr_vf(t5, vf19); // qmfc2.ni t5, vf19 - c->pextlh(t4, r0, t1); // pextlh t4, r0, t1 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psrlh(t1, t6, 8); // psrlh t1, t6, 8 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->ppacb(t6, r0, t1); // ppacb t6, r0, t1 - c->sq(t5, 208, a2); // sq t5, 208(a2) - c->dsrl32(t1, t6, 0); // dsrl32 t1, t6, 0 - c->sw(t6, 220, a2); // sw t6, 220(a2) - c->pextuh(t5, r0, t3); // pextuh t5, r0, t3 - c->mov128_gpr_vf(t6, vf15); // qmfc2.ni t6, vf15 - c->pextlh(t7, r0, t3); // pextlh t7, r0, t3 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t3, t4, 2); // psllw t3, t4, 2 - c->sq(t6, 192, a2); // sq t6, 192(a2) - c->psllw(t4, t2, 2); // psllw t4, t2, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t6, t7, 2); // psllw t6, t7, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t5, t5, 2); // psllw t5, t5, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->paddw(s5, t3, a1); // paddw s5, t3, a1 - c->mov128_gpr_vf(t2, vf20); // qmfc2.ni t2, vf20 - c->paddw(t4, t4, a1); // paddw t4, t4, a1 - c->lwu(t3, 0, s5); // lwu t3, 0(s5) - c->paddw(s4, t6, a1); // paddw s4, t6, a1 - c->lwu(t9, 0, t4); // lwu t9, 0(t4) - bc = ((s64)c->sgpr64(t0)) <= 0; // blez t0, L44 - c->paddw(t5, t5, a1); // paddw t5, t5, a1 - if (bc) {goto block_61;} // branch non-likely - - - block_60: - c->dsrl32(s3, s5, 0); // dsrl32 s3, s5, 0 - // Unknown instr: vcallms 221 - vcallms_221(c); - - c->dsrl32(s0, t4, 0); // dsrl32 s0, t4, 0 - c->dsrl32(s2, s4, 0); // dsrl32 s2, s4, 0 - c->dsrl32(t8, t5, 0); // dsrl32 t8, t5, 0 - c->daddiu(a2, a2, 128); // daddiu a2, a2, 128 - c->pcpyud(t7, s5, r0); // pcpyud t7, s5, r0 - c->lwu(ra, 0, s4); // lwu ra, 0(s4) - c->pcpyud(t6, t4, r0); // pcpyud t6, t4, r0 - c->lwu(s1, 0, t5); // lwu s1, 0(t5) - c->pcpyud(t4, s4, r0); // pcpyud t4, s4, r0 - c->lwu(s3, 0, s3); // lwu s3, 0(s3) - c->pcpyud(t5, t5, r0); // pcpyud t5, t5, r0 - c->lwu(s0, 0, s0); // lwu s0, 0(s0) - c->pextlw(t9, t9, t3); // pextlw t9, t9, t3 - c->lwu(s2, 0, s2); // lwu s2, 0(s2) - c->pextlw(t3, s1, ra); // pextlw t3, s1, ra - c->lwu(s1, 0, t8); // lwu s1, 0(t8) - c->pextlw(ra, s0, s3); // pextlw ra, s0, s3 - c->lwu(t8, 0, t7); // lwu t8, 0(t7) - c->pextlw(t7, s1, s2); // pextlw t7, s1, s2 - c->lwu(s3, 0, t6); // lwu s3, 0(t6) - c->pextlb(t6, r0, t9); // pextlb t6, r0, t9 - c->lwu(t9, 0, t4); // lwu t9, 0(t4) - c->pextlb(t4, r0, ra); // pextlb t4, r0, ra - c->lwu(t5, 0, t5); // lwu t5, 0(t5) - c->pextlw(t8, s3, t8); // pextlw t8, s3, t8 - c->sq(t2, 112, a2); // sq t2, 112(a2) - c->pextlw(t2, t5, t9); // pextlw t2, t5, t9 - c->mov128_gpr_vf(t5, vf17); // qmfc2.ni t5, vf17 - c->pextlb(t8, r0, t8); // pextlb t8, r0, t8 - c->sw(t1, 124, a2); // sw t1, 124(a2) - c->paddh(t1, t4, t8); // paddh t1, t4, t8 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pmulth(r0, t1, a0); // pmulth r0, t1, a0 - c->mov128_gpr_vf(t4, vf13); // qmfc2.ni t4, vf13 - c->pextlb(t1, r0, t3); // pextlb t1, r0, t3 - c->sq(t5, 144, a2); // sq t5, 144(a2) - c->pmaddh(r0, t6, v1); // pmaddh r0, t6, v1 - c->ldr(s5, 16, a3); // ldr s5, 16(a3) - c->pextlb(t3, r0, t7); // pextlb t3, r0, t7 - c->sq(t4, 128, a2); // sq t4, 128(a2) - c->pextlb(t2, r0, t2); // pextlb t2, r0, t2 - c->ldl(s5, 23, a3); // ldl s5, 23(a3) - c->pmfhl_lh(t4); // pmfhl.lh t4 - c->ldr(s4, 24, a3); // ldr s4, 24(a3) - c->paddh(t2, t3, t2); // paddh t2, t3, t2 - c->mov128_gpr_vf(t5, vf18); // qmfc2.ni t5, vf18 - c->psrlh(t3, t4, 8); // psrlh t3, t4, 8 - c->ldl(s4, 31, a3); // ldl s4, 31(a3) - c->ppacb(t3, r0, t3); // ppacb t3, r0, t3 - c->sq(t5, 176, a2); // sq t5, 176(a2) - c->dsrl32(t5, t3, 0); // dsrl32 t5, t3, 0 - c->daddiu(t0, t0, -4); // daddiu t0, t0, -4 - c->pmulth(r0, t2, a0); // pmulth r0, t2, a0 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlb(t4, r0, s5); // pextlb t4, r0, s5 - c->mov128_gpr_vf(t6, vf14); // qmfc2.ni t6, vf14 - c->pmaddh(r0, t1, v1); // pmaddh r0, t1, v1 - c->sw(t3, 156, a2); // sw t3, 156(a2) - c->pextlb(t3, r0, s4); // pextlb t3, r0, s4 - c->sw(t5, 188, a2); // sw t5, 188(a2) - c->pextuh(t2, r0, t4); // pextuh t2, r0, t4 - c->sq(t6, 160, a2); // sq t6, 160(a2) - c->pmfhl_lh(t6); // pmfhl.lh t6 - c->mov128_gpr_vf(t1, vf16); // qmfc2.ni t1, vf16 - c->pextlh(t4, r0, t4); // pextlh t4, r0, t4 - c->mov128_gpr_vf(t5, vf19); // qmfc2.ni t5, vf19 - c->psrlh(t6, t6, 8); // psrlh t6, t6, 8 - c->sq(t1, 96, a2); // sq t1, 96(a2) - c->ppacb(t6, r0, t6); // ppacb t6, r0, t6 - c->sq(t5, 208, a2); // sq t5, 208(a2) - c->dsrl32(t1, t6, 0); // dsrl32 t1, t6, 0 - c->daddiu(a3, a3, 16); // daddiu a3, a3, 16 - c->pextuh(t5, r0, t3); // pextuh t5, r0, t3 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->pextlh(t7, r0, t3); // pextlh t7, r0, t3 - c->mov128_gpr_vf(t8, vf15); // qmfc2.ni t8, vf15 - c->psllw(t3, t4, 2); // psllw t3, t4, 2 - c->sw(t6, 220, a2); // sw t6, 220(a2) - c->psllw(t4, t2, 2); // psllw t4, t2, 2 - c->sq(t8, 192, a2); // sq t8, 192(a2) - c->psllw(t6, t7, 2); // psllw t6, t7, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->psllw(t5, t5, 2); // psllw t5, t5, 2 - c->mfc1(r0, f31); // mfc1 r0, f31 - c->paddw(s5, t3, a1); // paddw s5, t3, a1 - c->mov128_gpr_vf(t2, vf20); // qmfc2.ni t2, vf20 - c->paddw(t4, t4, a1); // paddw t4, t4, a1 - c->lwu(t3, 0, s5); // lwu t3, 0(s5) - c->paddw(s4, t6, a1); // paddw s4, t6, a1 - c->lwu(t9, 0, t4); // lwu t9, 0(t4) - bc = ((s64)c->sgpr64(t0)) > 0; // bgtz t0, L43 - c->paddw(t5, t5, a1); // paddw t5, t5, a1 - if (bc) {goto block_60;} // branch non-likely - - - block_61: - // nop // sll r0, r0, 0 - // Unknown instr: vcallms 257 - vcallms_257(c); - - // nop // sll r0, r0, 0 - c->sq(t2, 240, a2); // sq t2, 240(a2) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(v1, vf16); // qmfc2.i v1, vf16 - // nop // sll r0, r0, 0 - c->sw(t1, 252, a2); // sw t1, 252(a2) - //beq r0, r0, L48 // beq r0, r0, L48 - c->sq(v1, 224, a2); // sq v1, 224(a2) - goto block_65; // branch always - - - block_62: - // Unknown instr: vcallms 259 - vcallms_259(c); - - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->sh(a0, 712, at); // sh a0, 712(at) - // nop // sll r0, r0, 0 - c->sh(a1, 714, at); // sh a1, 714(at) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->lbu(v1, 0, a3); // lbu v1, 0(a3) - // nop // sll r0, r0, 0 - c->lbu(a0, 4, a3); // lbu a0, 4(a3) - // nop // sll r0, r0, 0 - c->lbu(a1, 8, a3); // lbu a1, 8(a3) - c->dsll(v1, v1, 2); // dsll v1, v1, 2 - c->lbu(t2, 12, a3); // lbu t2, 12(a3) - c->dsll(a0, a0, 2); // dsll a0, a0, 2 - // nop // sll r0, r0, 0 - c->dsll(t3, a1, 2); // dsll t3, a1, 2 - // nop // sll r0, r0, 0 - c->dsll(t4, t2, 2); // dsll t4, t2, 2 - c->mov128_gpr_vf(t2, vf17); // qmfc2.i t2, vf17 - c->daddu(s5, v1, t1); // daddu s5, v1, t1 - c->daddiu(v1, a3, 16); // daddiu v1, a3, 16 - c->daddu(a1, a0, t1); // daddu a1, a0, t1 - // nop // sll r0, r0, 0 - c->daddu(s4, t3, t1); // daddu s4, t3, t1 - // nop // sll r0, r0, 0 - c->daddu(a0, t4, t1); // daddu a0, t4, t1 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t7, vf13); // qmfc2.ni t7, vf13 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(a3, vf18); // qmfc2.ni a3, vf18 - // nop // sll r0, r0, 0 - c->lwu(t6, 0, s5); // lwu t6, 0(s5) - // nop // sll r0, r0, 0 - c->lwu(t3, 0, a1); // lwu t3, 0(a1) - // nop // sll r0, r0, 0 - c->lwu(t4, 0, s4); // lwu t4, 0(s4) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t5, vf14); // qmfc2.ni t5, vf14 - // nop // sll r0, r0, 0 - c->lwu(a0, 0, a0); // lwu a0, 0(a0) - c->daddiu(a1, t0, -4); // daddiu a1, t0, -4 - c->sq(t7, 0, a2); // sq t7, 0(a2) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t0, vf19); // qmfc2.ni t0, vf19 - // nop // sll r0, r0, 0 - c->sq(t2, 16, a2); // sq t2, 16(a2) - // nop // sll r0, r0, 0 - c->sw(t6, 28, a2); // sw t6, 28(a2) - bc = ((s64)c->sgpr64(a1)) <= 0; // blez a1, L47 - c->sq(a3, 48, a2); // sq a3, 48(a2) - if (bc) {goto block_64;} // branch non-likely - - - block_63: - // Unknown instr: vcallms 264 - vcallms_264(c); - - c->sw(t3, 60, a2); // sw t3, 60(a2) - // nop // sll r0, r0, 0 - c->sq(t5, 32, a2); // sq t5, 32(a2) - // nop // sll r0, r0, 0 - c->sq(t0, 80, a2); // sq t0, 80(a2) - // nop // sll r0, r0, 0 - c->sw(t4, 92, a2); // sw t4, 92(a2) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - c->lbu(a3, 0, v1); // lbu a3, 0(v1) - // nop // sll r0, r0, 0 - c->lbu(t0, 4, v1); // lbu t0, 4(v1) - // nop // sll r0, r0, 0 - c->lbu(t2, 8, v1); // lbu t2, 8(v1) - c->dsll(t3, a3, 2); // dsll t3, a3, 2 - c->lbu(a3, 12, v1); // lbu a3, 12(v1) - c->dsll(t6, t0, 2); // dsll t6, t0, 2 - c->mov128_gpr_vf(t0, vf15); // qmfc2.i t0, vf15 - c->dsll(t4, t2, 2); // dsll t4, t2, 2 - c->mov128_gpr_vf(t2, vf20); // qmfc2.ni t2, vf20 - c->dsll(t5, a3, 2); // dsll t5, a3, 2 - c->mov128_gpr_vf(a3, vf17); // qmfc2.ni a3, vf17 - c->daddu(s5, t3, t1); // daddu s5, t3, t1 - c->daddiu(v1, v1, 16); // daddiu v1, v1, 16 - c->daddu(t3, t6, t1); // daddu t3, t6, t1 - c->mov128_gpr_vf(t6, vf16); // qmfc2.ni t6, vf16 - c->daddu(s4, t4, t1); // daddu s4, t4, t1 - c->sq(t0, 64, a2); // sq t0, 64(a2) - c->daddu(t0, t5, t1); // daddu t0, t5, t1 - c->sq(t2, 112, a2); // sq t2, 112(a2) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t2, vf13); // qmfc2.ni t2, vf13 - // nop // sll r0, r0, 0 - c->sw(a0, 124, a2); // sw a0, 124(a2) - // nop // sll r0, r0, 0 - c->sq(t6, 96, a2); // sq t6, 96(a2) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t6, vf18); // qmfc2.ni t6, vf18 - // nop // sll r0, r0, 0 - c->lwu(t7, 0, s5); // lwu t7, 0(s5) - // nop // sll r0, r0, 0 - c->lwu(t3, 0, t3); // lwu t3, 0(t3) - // nop // sll r0, r0, 0 - c->lwu(t4, 0, s4); // lwu t4, 0(s4) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t5, vf14); // qmfc2.ni t5, vf14 - c->daddiu(a2, a2, 128); // daddiu a2, a2, 128 - c->lwu(a0, 0, t0); // lwu a0, 0(t0) - c->daddiu(a1, a1, -4); // daddiu a1, a1, -4 - c->sq(t2, 0, a2); // sq t2, 0(a2) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(t0, vf19); // qmfc2.ni t0, vf19 - // nop // sll r0, r0, 0 - c->sq(a3, 16, a2); // sq a3, 16(a2) - // nop // sll r0, r0, 0 - c->sw(t7, 28, a2); // sw t7, 28(a2) - bc = ((s64)c->sgpr64(a1)) > 0; // bgtz a1, L46 - c->sq(t6, 48, a2); // sq t6, 48(a2) - if (bc) {goto block_63;} // branch non-likely - - - block_64: - // nop // sll r0, r0, 0 - // Unknown instr: vcallms 292 - vcallms_292(c); - - // nop // sll r0, r0, 0 - c->sw(t3, 60, a2); // sw t3, 60(a2) - // nop // sll r0, r0, 0 - c->sq(t5, 32, a2); // sq t5, 32(a2) - // nop // sll r0, r0, 0 - c->sq(t0, 80, a2); // sq t0, 80(a2) - // nop // sll r0, r0, 0 - c->sw(t4, 92, a2); // sw t4, 92(a2) - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(v1, vf15); // qmfc2.i v1, vf15 - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(a1, vf20); // qmfc2.ni a1, vf20 - // nop // sll r0, r0, 0 - c->mov128_gpr_vf(a3, vf16); // qmfc2.ni a3, vf16 - // nop // sll r0, r0, 0 - c->sq(v1, 64, a2); // sq v1, 64(a2) - // nop // sll r0, r0, 0 - c->sq(a1, 112, a2); // sq a1, 112(a2) - // nop // sll r0, r0, 0 - c->sw(a0, 124, a2); // sw a0, 124(a2) - // nop // sll r0, r0, 0 - c->sq(a3, 96, a2); // sq a3, 96(a2) - - block_65: - c->gprs[v1].du64[0] = 0; // or v1, r0, r0 - c->lw(v1, 644, at); // lw v1, 644(at) - c->lw(a0, 716, at); // lw a0, 716(at) - c->daddiu(a1, at, 12064); // daddiu a1, at, 12064 - c->sw(v1, 64, at); // sw v1, 64(at) - c->sw(a0, 60, at); // sw a0, 60(at) - c->sw(a1, 68, at); // sw a1, 68(at) - c->sw(r0, 72, at); // sw r0, 72(at) - c->lw(v1, 748, at); // lw v1, 748(at) - call_addr = c->gprs[v1].du32[0]; // function call: - // Unknown instr: sllv v0, ra, r0 - // c->jalr(call_addr); // jalr ra, v1 - // generic_prepare_dma_double::execute(c); - c->lw(v1, 752, at); // lw v1, 752(at) - call_addr = c->gprs[v1].du32[0]; // function call: - // Unknown instr: sllv v0, ra, r0 - // c->jalr(call_addr); // jalr ra, v1 - // generic_envmap_dproc::execute(c); - c->lw(v1, 756, at); // lw v1, 756(at) - call_addr = c->gprs[v1].du32[0]; // function call: - // Unknown instr: sllv v0, ra, r0 - // c->jalr(call_addr); // jalr ra, v1 - // generic_interp_dproc::execute(c); - c->lw(v1, 760, at); // lw v1, 760(at) - call_addr = c->gprs[v1].du32[0]; // function call: - // Unknown instr: sllv v0, ra, r0 - // c->jalr(call_addr); // jalr ra, v1 - // generic_no_light_dproc::execute(c); - c->lw(v1, 40, at); // lw v1, 40(at) - c->lw(a0, 56, at); // lw a0, 56(at) - c->mov64(a3, v1); // or a3, v1, r0 - // nop // sll r0, r0, 0 - get_fake_spad_addr(at, cache.fake_scratchpad_data, 0, c);// lui at, 28672 - c->lui(a2, 4096); // lui a2, 4096 - c->lwu(a1, 76, at); // lwu a1, 76(at) - c->ori(a2, a2, 53248); // ori a2, a2, 53248 - // c->lw(t1, 0, a2); // lw t1, 0(a2) - // nop // sll r0, r0, 0 - c->daddiu(t0, at, 108); // daddiu t0, at, 108 - c->andi(a3, a3, 16383); // andi a3, a3, 16383 - c->andi(t1, t1, 256); // andi t1, t1, 256 - // nop // sll r0, r0, 0 - bc = true; // c->sgpr64(t1) == 0; // beq t1, r0, L50 - // nop // sll r0, r0, 0 - if (bc) {goto block_69;} // branch non-likely - - c->mov64(t1, a2); // or t1, a2, r0 - // nop // sll r0, r0, 0 - - block_67: - c->lw(t2, 0, t0); // lw t2, 0(t0) - // nop // sll r0, r0, 0 - c->lw(t3, 0, t1); // lw t3, 0(t1) - // nop // sll r0, r0, 0 - c->andi(t3, t3, 256); // andi t3, t3, 256 - c->daddiu(t2, t2, 1); // daddiu t2, t2, 1 - bc = c->sgpr64(t3) != 0; // bne t3, r0, L49 - c->sw(t2, 0, t0); // sw t2, 0(t0) - if (bc) {goto block_67;} // branch non-likely - - c->gprs[t0].du64[0] = 0; // or t0, r0, r0 - - block_69: - c->dsll(t0, a0, 4); // dsll t0, a0, 4 - // c->sw(a3, 128, a2); // sw a3, 128(a2) - sadr = c->sgpr64(a3); - // nop // sll r0, r0, 0 - // c->sw(a1, 16, a2); // sw a1, 16(a2) - madr = c->sgpr64(a1); - c->addiu(a3, r0, 256); // addiu a3, r0, 256 - // c->sw(a0, 32, a2); // sw a0, 32(a2) - qwc = c->sgpr64(a0); - c->daddu(a0, a1, t0); // daddu a0, a1, t0 - // c->sw(a3, 0, a2); // sw a3, 0(a2) - spad_from_dma_no_sadr_off(cache.fake_scratchpad_data, madr, sadr, qwc); - // nop // sll r0, r0, 0 - c->sw(a0, 76, at); // sw a0, 76(at) - c->gprs[a0].du64[0] = 0; // or a0, r0, r0 - c->xori(v1, v1, 4608); // xori v1, v1, 4608 - c->sw(v1, 40, at); // sw v1, 40(at) - // nop // sll r0, r0, 0 - c->lw(v1, 740, at); // lw v1, 740(at) - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - bc = c->sgpr64(v1) == 0; // beq v1, r0, L9 - // nop // sll r0, r0, 0 - if (bc) {goto block_1;} // branch non-likely - - - block_70: - c->gprs[v0].du64[0] = 0; // or v0, r0, r0 - c->ld(ra, 384, at); // ld ra, 384(at) - c->lq(gp, 496, at); // lq gp, 496(at) - c->lq(s5, 480, at); // lq s5, 480(at) - c->lq(s4, 464, at); // lq s4, 464(at) - c->lq(s3, 448, at); // lq s3, 448(at) - c->lq(s2, 432, at); // lq s2, 432(at) - c->lq(s1, 416, at); // lq s1, 416(at) - c->lq(s0, 400, at); // lq s0, 400(at) - //jr ra // jr ra - c->daddiu(sp, sp, 128); // daddiu sp, sp, 128 - goto end_of_function; // return - - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - // nop // sll r0, r0, 0 - end_of_function: - return c->gprs[v0].du64[0]; -} - -void link() { - cache.fake_scratchpad_data = intern_from_c("*fake-scratchpad-data*").c(); - gLinkedFunctionTable.reg("generic-tie-convert", execute, 256); -} - -} // namespace generic_tie_convert -} // namespace Mips2C - diff --git a/game/mips2c/mips2c_table.cpp b/game/mips2c/mips2c_table.cpp index 35c0893ba4..54f6d145ea 100644 --- a/game/mips2c/mips2c_table.cpp +++ b/game/mips2c/mips2c_table.cpp @@ -99,8 +99,6 @@ namespace shadow_calc_dual_verts { extern void link(); } namespace shadow_xform_verts { extern void link(); } namespace draw_inline_array_instance_tie { extern void link(); } namespace draw_inline_array_prototype_tie_generic_asm { extern void link(); } -namespace generic_tie_dma_to_spad_sync { extern void link(); } -namespace generic_tie_convert { extern void link(); } } // namespace jak1 namespace jak2 { @@ -439,8 +437,7 @@ PerGameVersion>> gMips2C jak1::shadow_xform_verts::link}}, {"tie-methods", {jak1::draw_inline_array_instance_tie::link, - jak1::draw_inline_array_prototype_tie_generic_asm::link}}, - {"generic-tie", {jak1::generic_tie_dma_to_spad_sync::link, jak1::generic_tie_convert::link}}}, + jak1::draw_inline_array_prototype_tie_generic_asm::link}}}, /////////// JAK 2 {{"collide-func", {jak2::collide_do_primitives::link, jak2::moving_sphere_triangle_intersect::link}}, diff --git a/goal_src/jak1/engine/debug/memory-usage-h.gc b/goal_src/jak1/engine/debug/memory-usage-h.gc index f641e02827..f84f5703e0 100644 --- a/goal_src/jak1/engine/debug/memory-usage-h.gc +++ b/goal_src/jak1/engine/debug/memory-usage-h.gc @@ -189,4 +189,16 @@ (+! (-> ,usage data (mem-usage-id ,kind) used) ,bytes) (+! (-> ,usage data (mem-usage-id ,kind) total) (align16 ,bytes)))))) +;; Record DMA bytes in a named category when DMA-memory debugging is enabled. Unlike ordinary +;; allocation accounting, each category's total mirrors its cumulative used byte count. +(defmacro dma-mem-usage-add! (kind count size) + (with-gensyms (usage) + `(let ((,usage *dma-mem-usage*)) + (when (nonzero? ,usage) + (set! (-> ,usage length) (max (+ 1 (mem-usage-id-int ,kind)) (-> ,usage length))) + (set! (-> ,usage data (mem-usage-id ,kind) name) ,(symbol->string kind)) + (+! (-> ,usage data (mem-usage-id ,kind) count) ,count) + (+! (-> ,usage data (mem-usage-id ,kind) used) ,size) + (set! (-> ,usage data (mem-usage-id ,kind) total) (-> ,usage data (mem-usage-id ,kind) used)))))) + (defun-extern mem-size basic symbol mem-usage-flags int) diff --git a/goal_src/jak1/engine/gfx/background/background.gc b/goal_src/jak1/engine/gfx/background/background.gc index 46eaf282e0..ea3ba41805 100644 --- a/goal_src/jak1/engine/gfx/background/background.gc +++ b/goal_src/jak1/engine/gfx/background/background.gc @@ -15,47 +15,45 @@ ;; The recursive draw call through the drawable trees will add drawables to various lists. ;; After this, (finish-background) is called to finalize DMA lists. ;; -;; Which renderers, and how they differ, because the answer is not obvious from any one file: +;; TFRAG -- terrain, smooth cave walls, etc. Not instanced. Detail is a nested +;; vertex array (base points, then level-0 additions, then level-1 additions). +;; To simplify, it moves a vertex to the midpoint of a parent edge, creating a degenerate triangle +;; that can be removed. +;; To avoid t-junctions, morph weights are calculated per _vertex_ meaning that points appearing in +;; multiple fragments are computed the same way in both fragments. +;; Colors come from one time-of-day palette per tree, indexed per fragment. There are +;; several categories: ordinary, translucent, dirt, ice, and low-resolution versions of the first +;; two. These share the same renderer but have different ordering and blending settings. +;; Geometry that reaches the camera goes to TFRAG-NEAR, which clips. There is no generic fallback, +;; the generic field is never populated and there's no renderer. ;; -;; TFRAG -- terrain. Not instanced: every fragment is its own geometry, drawn once. Detail is a nested -;; vertex array (base points, then level-0 additions, then level-1 additions) and the renderer unwinds -;; one subdivision level at a time as a fragment recedes, sliding each added point onto the midpoint of -;; the parent edge it split until it is collinear and contributes nothing. Nothing is ever dropped by the -;; renderer -- a strip's length is fixed before its first vertex -- the point simply becomes redundant and -;; the EE stops uploading that level. The morph weight is evaluated per *vertex*, from that vertex's own -;; transformed w, which is what lets two fragments agree along a shared edge provided the exporter gave -;; them the same two parents; a fragment only gives up a level once its whole bounding sphere is past -;; that level's collapse distance, so a shared vertex has finished collapsing in the neighbour before -;; anyone stops drawing it. Colors come from one time-of-day palette per tree, indexed per fragment. Six tree -;; categories share the code: ordinary, translucent, dirt, ice, and low-resolution versions of the first -;; two. Geometry that reaches the camera goes to TFRAG-NEAR, which clips. There is no GENERIC path -- -;; tfragment has a `generic` field, but generic-tfragment is a four-byte stub and nothing consumes it. -;; -;; TIE -- background models placed repeatedly: crates, huts, pillars, fences. Instanced, and that shapes -;; everything: a prototype's geometry is uploaded to VU1 once and then every placement that chose it -;; streams through as a 64-byte instance record. Detail is four separately authored meshes per prototype -;; rather than a subdivision hierarchy, and the whole instance is drawn at one level, with one morph +;; TIE -- background models placed repeatedly, typically rocks, trees, roof tiles, etc. +;; TIE can handle very small objects placed many times, or very complicated objects placed few times +;; quite well. +;; A prototype's geometry is uploaded to VU1 once and then every placement that chose it +;; streams through as a 64-byte instance record. The whole instance is drawn at one level, with one morph ;; weight for the entire placement -- there is no per-vertex evaluation and no seam problem, because ;; every fragment of a prototype is always at the same level. One time-of-day palette per prototype, -;; shared by all its instances; what an instance owns is a byte of palette *index* per vertex, so the -;; same authored crate can stand in sunlight and in shadow. Instances near the camera go to TIE-NEAR, +;; shared by all its instances; each index has its own palette *index* per vertex, so the +;; same prototype have different lighting per instance. Instances near the camera go to TIE-NEAR, ;; which clips. Prototypes needing an environment map leave the VU1 path entirely and go through -;; GENERIC-TIE. +;; GENERIC-TIE. On PC, we send those to ETIE instead of taking the slower generic path. ;; -;; SHRUB -- small vegetation, instanced like TIE but with no LOD morph, no clipping path and no GENERIC -;; path. The cheapest of the three. +;; SHRUB -- small vegetation, instanced like TIE but with no LOD morph. Turns into billboard or fades +;; out at long distance. Each instance is "lit" by a single time-of-day color. ;; -;; All three share the VU0 program uploaded by background-upload-vu0 below, the scratchpad -;; terrain-context, and the convention that vf16..vf31 hold camera state for the whole background pass. -;; finish-background draws them in the order shrubbery, terrain, TIE, then any GENERIC-TIE fallback. +;; The original design has all background renderers share the VU0 program uploaded by background-upload-vu0 below, +;; the scratchpad terrain-context, and the convention that vf16..vf31 hold camera state for the whole background pass. +;; However, the final versions of these renderers set up their own conventions. (define *background-work* (new 'global 'background-work)) ;; The setup entry stores the camera state in VU0 data memory: ;; 0..3 clipping-plane matrix -;; 4..7 camera rotation -;; 8..11 camera temporary matrix +;; 4..7 camera rotation (includes translation) +;; 8..11 camera temporary matrix (camera with perspective) ;; 12..15 each camera-rotation row's z coefficient broadcast across all lanes +;; used for distance-from-camera testing to select level of detail ;; ;; Entries 16, 25, and 29 reload the clipping planes and either depth or full rotation constants. ;; Entry 33 transforms one point to plane distances and camera depth. Entry 42 additionally applies @@ -344,33 +342,20 @@ ;;;;;;;;;;;;;;;; ;; shrubbery ;;;;;;;;;;;;;;;; - (set! (-> *instance-shrub-work* paused) (paused?)) + (set! (-> *instance-shrub-work* paused) (paused?)) (when (nonzero? (-> *background-work* shrub-tree-count)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) - ;; iterate over all drawable trees + (add-profile-frame! #x40 0 #x40 #x80 'draw) (dotimes (i (-> *background-work* shrub-tree-count)) - ;; update the level index in the scratchpad (set! (-> (scratchpad-object terrain-context) bsp lev-index) (-> *background-work* shrub-levels i index)) (flush-cache 0) (let ((shrub-tree (-> *background-work* shrub-trees i)) (shrub-lev (-> *background-work* shrub-levels i))) - ;; setup colors for shrub (skipping because I don't think we'll need it here...) - ; (if (nonzero? (-> shrub-tree colors-added)) - ; (time-of-day-interp-colors - ; (-> *instance-shrub-work* colors) - ; (-> shrub-tree colors-added) - ; (-> shrub-lev mood) - ; ) - ; ) - ;; and draw! + ;; og:perserve-this - shrub colors computed in C++ now + ;; (if (nonzero? (-> shrub-tree colors-added)) + ;; (time-of-day-interp-colors (-> *instance-shrub-work* colors) (-> shrub-tree colors-added) (-> shrub-lev mood))) (draw-drawable-tree-instance-shrub shrub-tree shrub-lev))) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :g #x60 :b #x80 :a #x80)))) + (add-profile-frame! #x40 #x60 #x80 #x80 'draw)) + (let ((previous-lev (the-as level #f))) (when (or (nonzero? (-> *background-work* tfrag-tree-count)) (nonzero? (-> *background-work* trans-tfrag-tree-count)) @@ -405,10 +390,7 @@ (previous-palette (the-as time-of-day-palette #f)) (tfrag-tree-count (max (max highres-tree-count (-> *background-work* lowres-tfrag-tree-count)) (-> *background-work* lowres-trans-tfrag-tree-count)))) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) + (add-profile-frame! #x40 0 #x40 #x80 'draw) ;; loop over all tfrag trees (dotimes (i tfrag-tree-count) ;;;;;;;;;;;;;; Normal TFRAG @@ -514,19 +496,13 @@ (set! (-> *level* level (-> (scratchpad-object terrain-context) bsp lev-index) closest-object 3) (fmin (-> *level* level (-> (scratchpad-object terrain-context) bsp lev-index) closest-object 3) (-> *tfrag-work* min-dist z))))))) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :g #x80 :b #x60 :a #x80)))) + (add-profile-frame! #x40 #x80 #x60 #x80 'draw)) ;;;;;;;;;; TIE (TFRAG Instance Engine) ;; common setup (set! (-> *instance-tie-work* paused) (paused?)) (when (nonzero? (-> *background-work* tie-tree-count)) (tie-near-make-perspective-matrix (-> *instance-tie-work* tie-near-perspective-matrix)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) + (add-profile-frame! #x40 0 #x40 #x80 'draw) ;;;;;;;;;; Normal TIE (dotimes (i (-> *background-work* tie-tree-count)) (let ((tie-lev (-> *background-work* tie-levels i))) @@ -538,13 +514,9 @@ (set! (-> (scratchpad-object terrain-context) bsp lev-index) (-> tie-lev index)) (set! (-> (scratchpad-object terrain-context) bsp mood) (-> tie-lev mood)) (draw-drawable-tree-instance-tie (-> *background-work* tie-trees i) tie-lev)) - ;; This field holds the 32-bit EE address of a generated generic-prototype DMA chain rather - ;; than a GOAL object. generic-tie-execute treats that address as an opaque chain head. (set! (-> *background-work* tie-generic i) (the-as basic (-> *instance-tie-work* first-generic-prototype)))) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x80 :g #x20 :b #x60 :a #x80))) + (add-profile-frame! #x80 #x20 #x60 #x80 'draw) + ;; TIE Generic (when (not *use-etie*) (dotimes (i (-> *background-work* tie-tree-count)) diff --git a/goal_src/jak1/engine/gfx/depth-cue.gc b/goal_src/jak1/engine/gfx/depth-cue.gc index f77fb809b3..ca7fbb7bba 100644 --- a/goal_src/jak1/engine/gfx/depth-cue.gc +++ b/goal_src/jak1/engine/gfx/depth-cue.gc @@ -117,25 +117,15 @@ (set! (-> direct-packet vif0) (new 'static 'vif-tag)) (set! (-> direct-packet vif1) (new 'static 'vif-tag :imm #x4 :cmd (vif-cmd direct) :msk #x1)) (set! (-> packet-buffer base) (&+ (the-as pointer direct-packet) 16))) - (let* ((tag-buffer dma-buf) - (gif-tag (the-as gs-gif-tag (-> tag-buffer base)))) - (set! (-> gif-tag tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x3)) - (set! (-> gif-tag regs) GIF_REGS_ALL_AD) - (set! (-> tag-buffer base) (&+ (the-as pointer gif-tag) 16))) + (dma-buffer-add-gif-tag dma-buf (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :nreg #x3) GIF_REGS_ALL_AD) (let* ((state-buffer dma-buf) (state-out (-> state-buffer base))) (set! (-> (the-as (pointer gs-xy-offset) state-out) 0) - (new 'static - 'gs-xy-offset - :ofx #x7000 - :ofy (+ (* (-> *video-parms* screen-miny) 16) field-offset))) + (new 'static 'gs-xy-offset :ofx #x7000 :ofy (+ (* (-> *video-parms* screen-miny) 16) field-offset))) (set! (-> (the-as (pointer gs-reg64) state-out) 1) (gs-reg64 xyoffset-1)) - ;; Mask RGB so these sprites establish only the destination-alpha stencil. - (set! (-> (the-as (pointer gs-frame) state-out) 2) - (new 'static 'gs-frame :fbw #x8 :fbmsk #xffffff :fbp framebuffer-page)) + (set! (-> (the-as (pointer gs-frame) state-out) 2) (new 'static 'gs-frame :fbw #x8 :fbmsk #xffffff :fbp framebuffer-page)) (set! (-> (the-as (pointer gs-reg64) state-out) 3) (gs-reg64 frame-1)) - (set! (-> (the-as (pointer gs-test) state-out) 4) - (new 'static 'gs-test :zte #x1 :ztst (gs-ztest greater-equal))) + (set! (-> (the-as (pointer gs-test) state-out) 4) (new 'static 'gs-test :zte #x1 :ztst (gs-ztest greater-equal))) (set! (-> (the-as (pointer gs-reg64) state-out) 5) (gs-reg64 test-1)) (set! (-> state-buffer base) (&+ state-out 48))) (let ((packet-data (the-as (inline-array vector4w) (-> dma-buf base))) @@ -143,19 +133,15 @@ (sprite-width 1024)) (set! (-> packet-data 0 quad) (-> *depth-cue-work* stencil-tmpl dma-vif quad)) (set! (-> packet-data 1 quad) (-> *depth-cue-work* stencil-tmpl quad 1)) - (let ((cursor (the-as (inline-array vector4w) (-> packet-data 2)))) + (let ((cursor (&-> packet-data 2))) (dotimes (i 2) (set! (-> cursor 0 quad) (-> color quad)) - (set! cursor (the-as (inline-array vector4w) (-> cursor 1))) + (set! cursor (&-> cursor 1)) (dotimes (j 5) (set-vector! (-> cursor 0) x (* (-> *video-parms* screen-miny) 16) depth 0) - (set-vector! (-> cursor 1) - (+ x sprite-width) - (* (-> *video-parms* screen-maxy) 16) - depth - #x10000) + (set-vector! (-> cursor 1) (+ x sprite-width) (* (-> *video-parms* screen-maxy) 16) depth #x10000) (+! x sprite-width) - (set! cursor (the-as (inline-array vector4w) (-> cursor 2))))) + (set! cursor (&-> cursor 2)))) (set! (-> dma-buf base) (the-as pointer cursor)) (the-as vector4w cursor)))) diff --git a/goal_src/jak1/engine/gfx/font.gc b/goal_src/jak1/engine/gfx/font.gc index 2e9d553b8d..994eecea80 100644 --- a/goal_src/jak1/engine/gfx/font.gc +++ b/goal_src/jak1/engine/gfx/font.gc @@ -4,16 +4,11 @@ (require "engine/gfx/font-h.gc") (require "engine/ps2/vu1-macros.gc") -;; The font system draws every string in the game, so draw-string is one of the hottest pieces of -;; UI code and it is written as one long assembly function. What follows is what a reader needs -;; before the instruction listings make sense. -;; ;; Glyph tables. *font12-table* and *font24-table* are one vector per glyph. x and y are the ;; texture coordinates of the glyph's top-left corner in the font sheet, z is 1.0, and w is the -;; glyph's advance width in pixels, which is what makes kerning a table lookup instead of a -;; measurement. The table is indexed by character code minus 16, so the byte offset into it is -;; char * 16 - 256; that -256 appears verbatim on the load. Codes 0..15 are commands and have no -;; entry. The extended glyphs reached by a leading byte of 1 land at index (char & 127) + 239. +;; glyph's advance width in pixels. +;; The table is indexed by character code minus 16. Codes 0..15 are commands and have no +;; entry.The extended glyphs reached by a leading byte of 1 are at index (char & 127) + 239. ;; ;; A glyph is a two-triangle strip of four corners. font-work.size1/2/3 hold the cell's corner ;; offsets in pixels for the small and large fonts, and size-st1/2/3 the matching texture-coordinate @@ -33,7 +28,7 @@ ;; character, which is how the wide glyphs get a different TEX0. Fifteen quadwords per glyph in the ;; foreground pass and fifteen in the shadow pass. ;; -;; Inline commands. A tilde introduces an optional + or - sign, an optional decimal argument, and +;; Inline commands start with a tilde, then an optional + or - sign, an optional decimal argument, and ;; one command letter: N selects the font size, L the color, W the shadow, K kerning, J ;; justification, H and V move the cursor horizontally and vertically, and Y and Z save and restore ;; the style and cursor. Either case is accepted. A byte of 1 introduces a two-byte extended @@ -43,8 +38,7 @@ ;; Three passes. The layout pass measures each line and records its justified starting cursor in ;; font-work.justify, one vector per line. The shadow pass and then the foreground pass replay the ;; string from the start and emit packets, reading each line's starting cursor out of that array -;; instead of scanning ahead again. That is why the two drawing passes ignore the justification -;; commands and why a line break in them is just a load from the next justify entry. +;; instead of scanning ahead again. ;; Font asm macros: ;; these could likely be reimplemented within the OpenGOAL compiler and be faster. @@ -632,9 +626,6 @@ (new 'static 'vector :x 0.8476 :y 0.7512 :z 1.0 :w 5.0))) (#when PC_PORT - ;; We have both GOAL and MIPS2C versions of this function. - (define draw-string (the (function string dma-buffer font-context float) (__pc-get-mips2c "draw-string"))) - ;; Use the GOAL version because the MIPS2C version is very slow in debug builds. (defun draw-string ((str string) (buf dma-buffer) (ctxt font-context)) "Draw str with ctxt's transform, origin, colors, size, alignment, shadow, and kerning settings. Interpret embedded font commands while appending glyph packets to buf and return the horizontal @@ -1536,1055 +1527,6 @@ (.mov advance-x cursor) advance-x))) -(#unless PC_PORT - (defun draw-string ((str string) (buf dma-buffer) (ctxt font-context)) - "Draw str with ctxt's transform, origin, colors, size, alignment, shadow, and kerning settings. - Interpret embedded font commands while appending glyph packets to buf and return the horizontal - displacement from the starting origin." - (declare (asm-func float)) - ;; Text is processed in three passes. The layout pass measures each line and records its - ;; justified starting cursor. The shadow and foreground passes then emit packets without - ;; rescanning ahead to find line widths. - ;; The passes share one register allocation. v1 carries four unrelated things in turn -- - ;; the camera, the context's matrix, the video parameters, and finally *font-work* for the - ;; rest of the function -- so it gets four aliases. t4 is the current character byte except - ;; inside prepare-font-colors and main-set-color, where it walks the color table. - ;; a0 is the string cursor during the layout pass and the pass flags afterwards; the shadow and - ;; foreground passes read the string through t3 and walk the layout pass's justify array with - ;; t2. t5 and t8 stay physical: they are short-lived comparison and packing temporaries whose - ;; meaning changes every few instructions. - (rlet ((str :reg a0) - (buf :reg a1 :type dma-buffer) - (dma-out :reg a1) - (ctxt :reg a2 :type font-context) - (glyph-table :reg a3) - (camera :reg v1 :type math-camera) - (mat :reg v1 :type matrix) - (video-parms :reg v1 :type video-parms) - (fw :reg v1 :type font-work) - (out-buf :reg v1 :type dma-buffer) - (lo-tmpl :reg t0) - (hi-tmpl :reg t1) - (flags :reg t2) - (color-index :reg t3) - (color-cursor :reg t4 :type font-work) - (corner0 :reg vf1) - (corner1 :reg vf2) - (corner2 :reg vf3) - (corner3 :reg vf4) - (st0 :reg vf5) - (st1 :reg vf6) - (st2 :reg vf7) - (st3 :reg vf8) - (color0 :reg vf9) - (color1 :reg vf10) - (color2 :reg vf11) - (color3 :reg vf12) - (size1 :reg vf13) - (size2 :reg vf14) - (size3 :reg vf15) - (size-st1 :reg vf16) - (size-st2 :reg vf17) - (size-st3 :reg vf18) - (advance :reg vf19) - (cursor :reg vf23) - (origin :reg vf24) - (limits :reg vf25) - (shadow-offset :reg vf26) - (hvdf-offset :reg vf27) - (mat-row0 :reg vf28) - (mat-row1 :reg vf29) - (mat-row2 :reg vf30) - (mat-row3 :reg vf31)) - (label draw-entry) - (m! camera *math-camera*) - (l.vf shadow-offset (-> camera hvdf-off quad)) - (l.vf hvdf-offset (-> camera hvdf-off quad)) - (vaddw.xy shadow-offset shadow-offset vf0) - (vaddw.x shadow-offset shadow-offset vf0) - (l.w mat (-> ctxt mat)) - (l.vf limits (-> ctxt context-vec quad)) - (l.vf cursor (-> ctxt origin quad)) - (l.vf origin (-> ctxt origin quad)) - (l.vf mat-row0 mat) - (l.vf mat-row1 (-> mat vector 1 quad)) - (l.vf mat-row2 (-> mat vector 2 quad)) - (l.vf mat-row3 (-> mat vector 3 quad)) - (m! video-parms *video-parms*) - (m video-parms video-parms) - (l.vf corner0 (-> video-parms relative-x-scale-reciprical)) - (vmul.xy limits limits corner0) - (vmul.xy cursor cursor corner0) - (vmul.xy origin origin corner0) - (m! fw *font-work*) - (m fw fw) - (s.w buf (-> fw buf)) - (l.w dma-out (-> buf base)) - (s.w str (-> fw str-ptr)) - (l.w flags (-> ctxt flags-signed)) - (vmove.xyzw corner0 vf0) - (vmove.xyzw corner1 vf0) - (vmove.xyzw corner2 vf0) - (vmove.xyzw corner3 vf0) - (s.w flags (-> fw flags)) - (l.vf size-st1 (-> fw size-st1 quad)) - (l.vf size-st2 (-> fw size-st2 quad)) - (l.vf size-st3 (-> fw size-st3 quad)) - (and.i glyph-table flags 32) - (nop!) - (b.nz glyph-table draw-use-large-font :delay (m! glyph-table *font12-table*)) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-small quad)) - (nop!) - (l.vf size2 (-> fw size2-small quad)) - (nop!) - (l.vf size3 (-> fw size3-small quad)) - (nop!) - (l.q lo-tmpl (-> fw small-font-lo-tmpl-qw)) - (b prepare-font-colors :delay (l.q hi-tmpl (-> fw small-font-hi-tmpl-qw))) - (label draw-use-large-font) - (nop!) - (m! glyph-table *font24-table*) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-large quad)) - (nop!) - (l.vf size2 (-> fw size2-large quad)) - (nop!) - (l.vf size3 (-> fw size3-large quad)) - (nop!) - (l.q lo-tmpl (-> fw large-font-lo-tmpl-qw)) - (nop!) - (l.q hi-tmpl (-> fw large-font-hi-tmpl-qw)) - (label prepare-font-colors) - (l.w color-index (-> ctxt color-s32)) - (nop!) - (s.w color-index (-> fw last-color-32)) - (nop!) - (sll color-index color-index 4) - (nop!) - (add color-cursor color-index fw) - (nop!) - (nop!) - (l.wu color-index (-> color-cursor color-table 0 color 0)) - (nop!) - (l.wu t5 (-> color-cursor color-table 0 color 1)) - (pextlb arg-value r0 color-index) - (l.wu color-index (-> color-cursor color-table 0 color 2)) - (pextlh arg-value r0 arg-value) - (l.wu color-cursor (-> color-cursor color-table 0 color 3)) - (pextlb t5 r0 t5) - (s.q arg-value (-> fw current-verts color 0 quad)) - (pextlh t5 r0 t5) - (s.q arg-value (-> fw dest-verts color 0 quad)) - (pextlb color-index r0 color-index) - (s.q t5 (-> fw current-verts color 1 quad)) - (pextlh color-index r0 color-index) - (s.q t5 (-> fw dest-verts color 1 quad)) - (pextlb color-cursor r0 color-cursor) - (s.q color-index (-> fw current-verts color 2 quad)) - (pextlh color-cursor r0 color-cursor) - (s.q color-index (-> fw dest-verts color 2 quad)) - (nop!) - (s.q color-cursor (-> fw current-verts color 3 quad)) - (nop!) - (s.q color-cursor (-> fw dest-verts color 3 quad)) - (m color-index fw) - (nop!) - (label layout-next-character) - ;; Size, kerning, justification, cursor offsets, and save/restore commands affect layout. - ;; Color and shadow commands do not change line width and can be skipped in this pass. - ;; The command letters are N=size, L=color, W=shadow, K=kerning, J=justification, - ;; H/V=horizontal/vertical cursor position, and Y/Z=save/restore style and position. - (l.bu char str 4) - (add.i str str 1) - (b.z char layout-finish :delay (add.i t5 char -1)) - (b.z t5 layout-extended-character :delay (add.i t5 char -126)) - (b.nz t5 layout-character :delay (nop!)) - (l.bu char str 4) - (add.i str str 1) - (add.i t5 r0 0) - (add.i arg-value r0 0) - (b.z char layout-finish :delay (add.i char-test char -43)) - (movz t5 char char-test) - (add.i char-test char -45) - (movz t5 char char-test) - (nop!) - (b.nz t5 layout-format-digits :delay (add.i char-test char -121)) - (b.z char-test layout-save-position :delay (add.i arg-value char -89)) - (b.z arg-value layout-save-position :delay (add.i arg-value char -122)) - (b.z arg-value layout-restore-position :delay (add.i arg-value char -90)) - (b.z arg-value layout-restore-position :delay (add.i arg-value char -48)) - (b.lt arg-value r0 layout-character :delay (add.i arg-value char -57)) - (b.gt arg-value r0 layout-character :delay (add.i arg-value char -48)) - (label layout-format-digits) - (l.bu char str 4) - (add.i str str 1) - (b.z char layout-finish :delay (add.i char-test char -110)) - (b.z char-test layout-use-small-font :delay (add.i char-test char -78)) - (b.z char-test layout-use-small-font :delay (add.i char-test char -108)) - (b.z char-test layout-next-character :delay (add.i char-test char -76)) - (b.z char-test layout-next-character :delay (add.i char-test char -119)) - (b.z char-test layout-next-character :delay (add.i char-test char -87)) - (b.z char-test layout-next-character :delay (add.i char-test char -107)) - (b.z char-test layout-set-kerning :delay (add.i char-test char -75)) - (b.z char-test layout-set-kerning :delay (add.i char-test char -106)) - (b.z char-test layout-set-justification :delay (add.i char-test char -74)) - (b.z char-test layout-set-justification :delay (add.i char-test char -104)) - (b.z char-test layout-horizontal-offset :delay (add.i char-test char -72)) - (b.z char-test layout-horizontal-offset :delay (add.i char-test char -118)) - (b.z char-test layout-vertical-offset :delay (add.i char-test char -86)) - (b.z char-test layout-vertical-offset :delay (add.i char-test char -48)) - (b.lt char-test r0 layout-character :delay (add.i t8 char -57)) - (b.gt t8 r0 layout-character :delay (sll t8 arg-value 2)) - (add char arg-value t8) - (nop!) - (sll char char 1) - (nop!) - (b layout-format-digits :delay (add arg-value char char-test)) - (label layout-use-small-font) - (b.nz arg-value layout-use-large-font :delay (add.i lo-tmpl r0 -33)) - (m! glyph-table *font12-table*) - (m glyph-table glyph-table) - (l.vf size1 (-> fw size1-small quad)) - (nop!) - (l.vf size2 (-> fw size2-small quad)) - (nop!) - (l.vf size3 (-> fw size3-small quad)) - (and flags flags lo-tmpl) - (l.q lo-tmpl (-> fw small-font-lo-tmpl-qw)) - (b layout-next-character :delay (l.q hi-tmpl (-> fw small-font-hi-tmpl-qw))) - (label layout-use-large-font) - (nop!) - (m! glyph-table *font24-table*) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-large quad)) - (nop!) - (l.vf size2 (-> fw size2-large quad)) - (nop!) - (l.vf size3 (-> fw size3-large quad)) - (ori flags flags 32) - (l.q lo-tmpl (-> fw large-font-lo-tmpl-qw)) - (b layout-next-character :delay (l.q hi-tmpl (-> fw large-font-hi-tmpl-qw))) - (label layout-set-kerning) - (add.i char r0 -3) - (nop!) - (b.z arg-value layout-next-character :delay (and flags flags char)) - (b layout-next-character :delay (ori flags flags 2)) - (label layout-set-justification) - (add.i char r0 -21) - (add.i t5 arg-value -2) - (b.z arg-value layout-next-character :delay (and flags flags char)) - (b.z t5 layout-set-right-justification :delay (nop!)) - (b layout-next-character :delay (ori flags flags 16)) - (label layout-set-right-justification) - (b layout-next-character :delay (ori flags flags 4)) - (label layout-horizontal-offset) - (m corner0 arg-value) - (add.i char t5 -45) - (b.z t5 layout-set-horizontal-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char layout-subtract-horizontal-offset :delay (nop!)) - (b layout-next-character :delay (vaddx.x cursor cursor corner0)) - (label layout-subtract-horizontal-offset) - (b layout-next-character :delay (vsubx.x cursor cursor corner0)) - (label layout-set-horizontal-offset) - (b layout-next-character :delay (vaddx.x cursor vf0 corner0)) - (label layout-vertical-offset) - (m corner0 arg-value) - (add.i char t5 -45) - (b.z t5 layout-set-vertical-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char layout-subtract-vertical-offset :delay (nop!)) - (b layout-next-character :delay (vaddx.y cursor cursor corner0)) - (label layout-subtract-vertical-offset) - (b layout-next-character :delay (vsubx.y cursor cursor corner0)) - (label layout-set-vertical-offset) - (b layout-next-character :delay (vaddx.y cursor vf0 corner0)) - (label layout-save-position) - (b layout-next-character :delay (s.vf cursor (-> fw save quad))) - (label layout-restore-position) - (b layout-next-character :delay (l.vf cursor (-> fw save quad))) - (label layout-extended-character) - (l.bu char str 4) - (add.i str str 1) - (vadd.xyz corner3 cursor size3) - (and.i char char 127) - (vsub.xyzw corner0 limits cursor) - (add.i char char 255) - (b layout-glyph :delay (sll t5 char 4)) - (label layout-character) - (vadd.xyz corner3 cursor size3) - (sll t5 char 4) - (vsub.xyzw corner0 limits cursor) - (add.i arg-value char -10) - (b.z arg-value layout-line-break :delay (add.i char char -13)) - (b.nz char layout-glyph :delay (nop!)) - (label layout-line-break) - (vsub.xyzw corner0 cursor origin) - (and.i char flags 16) - (b.nz char layout-right-align-line :delay (and.i char flags 4)) - (b.nz char layout-center-line :delay (nop!)) - (vaddx.x cursor vf0 origin) - (nop!) - (s.vf cursor color-index 944) - (vaddw.y cursor cursor size3) - (b layout-next-character :delay (add.i color-index color-index 16)) - (label layout-right-align-line) - (vsub.x cursor origin corner0) - (s.vf cursor color-index 944) - (vaddx.x cursor vf0 origin) - (vaddw.y cursor cursor size3) - (b layout-next-character :delay (add.i color-index color-index 16)) - (label layout-center-line) - (vmulw.x corner0 corner0 size-st1) - (nop!) - (vsub.x cursor origin corner0) - (s.vf cursor color-index 944) - (vaddx.x cursor vf0 origin) - (vaddw.y cursor cursor size3) - (b layout-next-character :delay (add.i color-index color-index 16)) - (label layout-glyph) - (add char t5 glyph-table) - (nop!) - (l.vf st0 char -256) - (m char corner0) - (b.lt char r0 layout-finish :delay (sra char char 31)) - (mul.vf advance st0 size1) - (and.i char flags 2) - (b.z char layout-advance-fixed :delay (nop!)) - (b layout-continue :delay (vaddw.x cursor cursor advance)) - (label layout-advance-fixed) - (nop!) - (vaddw.x cursor cursor size2) - (label layout-continue) - (b layout-next-character :delay (nop!)) - (label layout-finish) - (vsub.xyzw corner0 cursor origin) - (and.i str flags 16) - (b.nz str layout-right-align-final-line :delay (and.i str flags 4)) - (b.nz str layout-center-final-line :delay (nop!)) - (vaddx.x cursor vf0 origin) - (nop!) - (b shadow-pass-setup :delay (s.vf cursor color-index 944)) - (label layout-right-align-final-line) - (vsub.x cursor origin corner0) - (nop!) - (b shadow-pass-setup :delay (s.vf cursor color-index 944)) - (label layout-center-final-line) - (vmulw.x corner0 corner0 size-st1) - (nop!) - (vsub.x cursor origin corner0) - (s.vf cursor color-index 944) - (label shadow-pass-setup) - ;; Restart at the saved string pointer and emit the offset shadow quads when shadow drawing is - ;; enabled. Line breaks select the next starting cursor produced by the layout pass. - (l.w pass-flags (-> fw flags)) - (m justify-cursor fw) - (l.w str-cursor (-> fw str-ptr)) - (l.vf cursor (-> justify-cursor justify 0 quad)) - (label shadow-next-character) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (b.z char main-pass-setup :delay (add.i t5 char -1)) - (b.z t5 shadow-extended-character :delay (add.i t5 char -126)) - (b.nz t5 shadow-character :delay (nop!)) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (add.i t5 r0 0) - (add.i arg-value r0 0) - (b.z char main-pass-setup :delay (add.i char-test char -43)) - (movz t5 char char-test) - (add.i char-test char -45) - (movz t5 char char-test) - (nop!) - (b.nz t5 shadow-format-digits :delay (add.i char-test char -121)) - (b.z char-test shadow-save-position :delay (add.i arg-value char -89)) - (b.z arg-value shadow-save-position :delay (add.i arg-value char -122)) - (b.z arg-value shadow-restore-position :delay (add.i arg-value char -90)) - (b.z arg-value shadow-restore-position :delay (add.i arg-value char -48)) - (b.lt arg-value r0 shadow-character :delay (add.i arg-value char -57)) - (b.gt arg-value r0 shadow-character :delay (add.i arg-value char -48)) - (label shadow-format-digits) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (b.z char main-pass-setup :delay (add.i char-test char -110)) - (b.z char-test shadow-use-small-font :delay (add.i char-test char -78)) - (b.z char-test shadow-use-small-font :delay (add.i char-test char -108)) - (b.z char-test shadow-next-character :delay (add.i char-test char -76)) - (b.z char-test shadow-next-character :delay (add.i char-test char -119)) - (b.z char-test shadow-set-enabled :delay (add.i char-test char -87)) - (b.z char-test shadow-set-enabled :delay (add.i char-test char -107)) - (b.z char-test shadow-set-kerning :delay (add.i char-test char -75)) - (b.z char-test shadow-set-kerning :delay (add.i char-test char -106)) - (b.z char-test shadow-next-character :delay (add.i char-test char -74)) - (b.z char-test shadow-next-character :delay (add.i char-test char -104)) - (b.z char-test shadow-horizontal-offset :delay (add.i char-test char -72)) - (b.z char-test shadow-horizontal-offset :delay (add.i char-test char -118)) - (b.z char-test shadow-vertical-offset :delay (add.i char-test char -86)) - (b.z char-test shadow-vertical-offset :delay (add.i char-test char -48)) - (b.lt char-test r0 shadow-character :delay (add.i t8 char -57)) - (b.gt t8 r0 shadow-character :delay (sll t8 arg-value 2)) - (add char arg-value t8) - (nop!) - (sll char char 1) - (nop!) - (b shadow-format-digits :delay (add arg-value char char-test)) - (label shadow-use-small-font) - (b.nz arg-value shadow-use-large-font :delay (m! glyph-table *font12-table*)) - (m glyph-table glyph-table) - (add.i lo-tmpl r0 -33) - (l.vf size1 (-> fw size1-small quad)) - (nop!) - (l.vf size2 (-> fw size2-small quad)) - (nop!) - (l.vf size3 (-> fw size3-small quad)) - (and pass-flags pass-flags lo-tmpl) - (l.q lo-tmpl (-> fw small-font-lo-tmpl-qw)) - (b shadow-next-character :delay (l.q hi-tmpl (-> fw small-font-hi-tmpl-qw))) - (label shadow-use-large-font) - (nop!) - (m! glyph-table *font24-table*) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-large quad)) - (nop!) - (l.vf size2 (-> fw size2-large quad)) - (nop!) - (l.vf size3 (-> fw size3-large quad)) - (ori pass-flags pass-flags 32) - (l.q lo-tmpl (-> fw large-font-lo-tmpl-qw)) - (b shadow-next-character :delay (l.q hi-tmpl (-> fw large-font-hi-tmpl-qw))) - (label shadow-set-enabled) - (add.i char r0 -2) - (nop!) - (b.z arg-value shadow-next-character :delay (and pass-flags pass-flags char)) - (b shadow-next-character :delay (ori pass-flags pass-flags 1)) - (label shadow-set-kerning) - (add.i char r0 -3) - (nop!) - (b.z arg-value shadow-next-character :delay (and pass-flags pass-flags char)) - (b shadow-next-character :delay (ori pass-flags pass-flags 2)) - (label shadow-horizontal-offset) - (m corner0 arg-value) - (add.i char t5 -45) - (b.z t5 shadow-set-horizontal-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char shadow-subtract-horizontal-offset :delay (nop!)) - (b shadow-next-character :delay (vaddx.x cursor cursor corner0)) - (label shadow-subtract-horizontal-offset) - (b shadow-next-character :delay (vsubx.x cursor cursor corner0)) - (label shadow-set-horizontal-offset) - (b shadow-next-character :delay (vaddx.x cursor vf0 corner0)) - (label shadow-vertical-offset) - (m corner0 arg-value) - (add.i char t5 -45) - (b.z t5 shadow-set-vertical-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char shadow-subtract-vertical-offset :delay (nop!)) - (b shadow-next-character :delay (vaddx.y cursor cursor corner0)) - (label shadow-subtract-vertical-offset) - (b shadow-next-character :delay (vsubx.y cursor cursor corner0)) - (label shadow-set-vertical-offset) - (b shadow-next-character :delay (vaddx.y cursor vf0 corner0)) - (label shadow-save-position) - (b shadow-next-character :delay (s.vf cursor (-> fw save quad))) - (label shadow-restore-position) - (b shadow-next-character :delay (l.vf cursor (-> fw save quad))) - (label shadow-extended-character) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (vadd.xyz corner3 cursor size3) - (and.i t5 char 127) - (vsub.xyzw corner0 limits cursor) - (add.i t5 t5 255) - (b emit-shadow-glyph :delay (sll t5 t5 4)) - (label shadow-character) - (vadd.xyz corner3 cursor size3) - (sll t5 char 4) - (vsub.xyzw corner0 limits cursor) - (add.i arg-value char -10) - (b.z arg-value shadow-line-break :delay (add.i arg-value char -13)) - (b.nz arg-value emit-shadow-glyph :delay (and.i arg-value pass-flags 1)) - (label shadow-line-break) - (add.i justify-cursor justify-cursor 16) - (nop!) - (b shadow-next-character :delay (l.vf cursor (-> justify-cursor justify 0 quad))) - (label emit-shadow-glyph) - ;; Build four texture coordinates and four positions, transform them by the font matrix, - ;; perspective-divide each corner, and append the shadow-colored GIF packet. - (add t5 t5 glyph-table) - (nop!) - (l.vf st0 t5 -256) - (m t5 corner0) - (b.lt t5 r0 main-pass-setup :delay (vadd.xyz st1 st0 size-st1)) - (sra t5 t5 31) - (vadd.xyz st2 st0 size-st2) - (b.lt t5 r0 shadow-next-character :delay (vadd.xyz st3 st0 size-st3)) - (vadd.xyz corner0 cursor vf0) - (nop!) - (vadd.xyz corner1 cursor size1) - (s.vf st0 (-> fw current-verts tex-st 0 quad)) - (vadd.xyz corner2 cursor size2) - (s.vf st1 (-> fw current-verts tex-st 1 quad)) - (mul.vf advance st0 size1) - (s.vf st2 (-> fw current-verts tex-st 2 quad)) - (nop!) - (s.vf st3 (-> fw current-verts tex-st 3 quad)) - (nop!) - (s.vf corner0 (-> fw current-verts pos 0 quad)) - (nop!) - (s.vf corner1 (-> fw current-verts pos 1 quad)) - (nop!) - (s.vf corner2 (-> fw current-verts pos 2 quad)) - (nop!) - (s.vf corner3 (-> fw current-verts pos 3 quad)) - (l.vf corner0 (-> fw current-verts pos 0 quad)) - (nop!) - (l.vf corner1 (-> fw current-verts pos 1 quad)) - (vmulaw.xyzw acc mat-row3 vf0) - (l.vf corner2 (-> fw current-verts pos 2 quad)) - (vmaddax.xyzw acc mat-row0 corner0) - (l.vf corner3 (-> fw current-verts pos 3 quad)) - (vmadday.xyzw acc mat-row1 corner0) - (nop!) - (vmaddz.xyzw corner0 mat-row2 corner0) - (nop!) - (vmulaw.xyzw acc mat-row3 vf0) - (nop!) - (vmaddax.xyzw acc mat-row0 corner1) - (nop!) - (vmadday.xyzw acc mat-row1 corner1) - (nop!) - (vmaddz.xyzw corner1 mat-row2 corner1) - (nop!) - (vmulaw.xyzw acc mat-row3 vf0) - (nop!) - (vmaddax.xyzw acc mat-row0 corner2) - (nop!) - (vmadday.xyzw acc mat-row1 corner2) - (nop!) - (vmaddz.xyzw corner2 mat-row2 corner2) - (nop!) - (vmulaw.xyzw acc mat-row3 vf0) - (nop!) - (vmaddax.xyzw acc mat-row0 corner3) - (nop!) - (vmadday.xyzw acc mat-row1 corner3) - (nop!) - (vmaddz.xyzw corner3 mat-row2 corner3) - (vdiv Q limits.z corner0.w) - (l.q t5 (-> fw char-tmpl dma-vif quad)) - (nop!) - (l.q arg-value (-> fw char-tmpl quad 1)) - (nop!) - (s.q t5 dma-out) - (nop!) - (s.q arg-value dma-out 16) - (l.vf st0 (-> fw current-verts tex-st 0 quad)) - (m.q t5 lo-tmpl) - (l.vf st1 (-> fw current-verts tex-st 1 quad)) - (and.i char char 128) - (l.vf st2 (-> fw current-verts tex-st 2 quad)) - (movn t5 hi-tmpl char) - (vmulq.xyz corner0 corner0 Q) - (s.q t5 dma-out 32) - (vmulq.xyz st0 st0 Q) - (nop!) - (vnop) - (nop!) - (vnop) - (nop!) - (vdiv Q limits.z corner1.w) - (nop!) - (l.vf st3 (-> fw current-verts tex-st 3 quad)) - (nop!) - (add.vf corner0 corner0 shadow-offset) - (nop!) - (l.vf color0 (-> fw color-shadow quad)) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (vftoi4.xyzw corner0 corner0) - (nop!) - (vmulq.xyz corner1 corner1 Q) - (nop!) - (vmulq.xyz st1 st1 Q) - (s.vf st0 dma-out 48) - (vnop) - (nop!) - (vnop) - (nop!) - (vdiv Q limits.z corner2.w) - (s.vf color0 dma-out 64) - (nop!) - (s.vf corner0 dma-out 80) - (add.vf corner1 corner1 shadow-offset) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (vftoi4.xyzw corner1 corner1) - (nop!) - (vmulq.xyz corner2 corner2 Q) - (nop!) - (vmulq.xyz st2 st2 Q) - (s.vf st1 dma-out 96) - (vnop) - (nop!) - (vnop) - (nop!) - (vdiv Q limits.z corner3.w) - (s.vf color0 dma-out 112) - (nop!) - (s.vf corner1 dma-out 128) - (add.vf corner2 corner2 shadow-offset) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (vftoi4.xyzw corner2 corner2) - (nop!) - (vmulq.xyz corner3 corner3 Q) - (nop!) - (vmulq.xyz st3 st3 Q) - (s.vf st2 dma-out 144) - (nop!) - (s.vf color0 dma-out 160) - (add.vf corner3 corner3 shadow-offset) - (s.vf corner2 dma-out 176) - (and.i char pass-flags 2) - (nop!) - (b.z char shadow-advance-fixed :delay (nop!)) - (b finish-shadow-glyph :delay (vaddw.x cursor cursor advance)) - (label shadow-advance-fixed) - (nop!) - (vaddw.x cursor cursor size2) - (label finish-shadow-glyph) - (vftoi4.xyzw corner3 corner3) - (nop!) - (nop!) - (nop!) - (nop!) - (s.vf st3 dma-out 192) - (nop!) - (s.vf color0 dma-out 208) - (nop!) - (s.vf corner3 dma-out 224) - (b shadow-next-character :delay (add.i dma-out dma-out 240)) - (label main-pass-setup) - ;; Restart once more for foreground glyphs. This pass also applies inline color changes and - ;; saves or restores the color set together with the cursor. - (l.w pass-flags (-> fw flags)) - (m justify-cursor fw) - (l.w str-cursor (-> fw str-ptr)) - (l.vf cursor (-> justify-cursor justify 0 quad)) - (label main-next-character) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (b.z char draw-finish :delay (add.i t5 char -1)) - (b.z t5 main-extended-character :delay (add.i t5 char -126)) - (b.nz t5 main-character :delay (nop!)) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (add.i t5 r0 0) - (add.i arg-value r0 0) - (b.z char draw-finish :delay (add.i char-test char -43)) - (movz t5 char char-test) - (add.i char-test char -45) - (movz t5 char char-test) - (nop!) - (b.nz t5 main-format-digits :delay (add.i char-test char -121)) - (b.z char-test main-save-style :delay (add.i arg-value char -89)) - (b.z arg-value main-save-style :delay (add.i arg-value char -122)) - (b.z arg-value main-restore-style :delay (add.i arg-value char -90)) - (b.z arg-value main-restore-style :delay (add.i arg-value char -48)) - (b.lt arg-value r0 main-character :delay (add.i arg-value char -57)) - (b.gt arg-value r0 main-character :delay (add.i arg-value char -48)) - (label main-format-digits) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (b.z char draw-finish :delay (add.i char-test char -110)) - (b.z char-test main-use-small-font :delay (add.i char-test char -78)) - (b.z char-test main-use-small-font :delay (add.i char-test char -108)) - (b.z char-test main-set-color :delay (add.i char-test char -76)) - (b.z char-test main-set-color :delay (add.i char-test char -119)) - (b.z char-test main-next-character :delay (add.i char-test char -87)) - (b.z char-test main-next-character :delay (add.i char-test char -107)) - (b.z char-test main-set-kerning :delay (add.i char-test char -75)) - (b.z char-test main-set-kerning :delay (add.i char-test char -106)) - (b.z char-test main-next-character :delay (add.i char-test char -74)) - (b.z char-test main-next-character :delay (add.i char-test char -104)) - (b.z char-test main-horizontal-offset :delay (add.i char-test char -72)) - (b.z char-test main-horizontal-offset :delay (add.i char-test char -118)) - (b.z char-test main-vertical-offset :delay (add.i char-test char -86)) - (b.z char-test main-vertical-offset :delay (add.i char-test char -48)) - (b.lt char-test r0 main-character :delay (add.i t8 char -57)) - (b.gt t8 r0 main-character :delay (sll t8 arg-value 2)) - (add char arg-value t8) - (nop!) - (sll char char 1) - (nop!) - (b main-format-digits :delay (add arg-value char char-test)) - (label main-use-small-font) - (b.nz arg-value main-use-large-font :delay (m! glyph-table *font12-table*)) - (m glyph-table glyph-table) - (add.i lo-tmpl r0 -33) - (l.vf size1 (-> fw size1-small quad)) - (nop!) - (l.vf size2 (-> fw size2-small quad)) - (nop!) - (l.vf size3 (-> fw size3-small quad)) - (and pass-flags pass-flags lo-tmpl) - (l.q lo-tmpl (-> fw small-font-lo-tmpl-qw)) - (b main-next-character :delay (l.q hi-tmpl (-> fw small-font-hi-tmpl-qw))) - (label main-use-large-font) - (nop!) - (m! glyph-table *font24-table*) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-large quad)) - (nop!) - (l.vf size2 (-> fw size2-large quad)) - (nop!) - (l.vf size3 (-> fw size3-large quad)) - (ori pass-flags pass-flags 32) - (l.q lo-tmpl (-> fw large-font-lo-tmpl-qw)) - (b main-next-character :delay (l.q hi-tmpl (-> fw large-font-hi-tmpl-qw))) - (label main-set-color) - ;; A color command selects one of the four-color gradients. Expand each packed RGBA word to - ;; the GS vertex format and retain both current and destination colors for interpolation. - (nop!) - (s.w arg-value (-> fw last-color-32)) - (sll color-cursor arg-value 4) - (l.q t5 (-> fw current-verts color 0 quad)) - (add color-cursor color-cursor fw) - (l.q arg-value (-> fw current-verts color 1 quad)) - (nop!) - (s.q t5 (-> fw src-verts color 0 quad)) - (nop!) - (s.q arg-value (-> fw src-verts color 1 quad)) - (nop!) - (l.q t5 (-> fw current-verts color 2 quad)) - (nop!) - (l.q arg-value (-> fw current-verts color 3 quad)) - (nop!) - (s.q t5 (-> fw src-verts color 2 quad)) - (nop!) - (s.q arg-value (-> fw src-verts color 3 quad)) - (nop!) - (l.wu arg-value (-> color-cursor color-table 0 color 0)) - (nop!) - (l.wu t5 (-> color-cursor color-table 0 color 1)) - (pextlb arg-value r0 arg-value) - (mmi-nop!) - (pextlh arg-value r0 arg-value) - (mmi-nop!) - (pextlb t5 r0 t5) - (s.q arg-value (-> fw current-verts color 0 quad)) - (pextlh t5 r0 t5) - (s.q arg-value (-> fw dest-verts color 0 quad)) - (nop!) - (s.q t5 (-> fw current-verts color 1 quad)) - (nop!) - (s.q t5 (-> fw dest-verts color 1 quad)) - (nop!) - (l.wu t5 (-> color-cursor color-table 0 color 2)) - (nop!) - (l.wu color-cursor (-> color-cursor color-table 0 color 3)) - (pextlb t5 r0 t5) - (mmi-nop!) - (pextlh t5 r0 t5) - (mmi-nop!) - (pextlb color-cursor r0 color-cursor) - (s.q t5 (-> fw current-verts color 2 quad)) - (pextlh color-cursor r0 color-cursor) - (s.q t5 (-> fw dest-verts color 2 quad)) - (nop!) - (s.q color-cursor (-> fw current-verts color 3 quad)) - (b main-next-character :delay (s.q color-cursor (-> fw dest-verts color 3 quad))) - (label main-set-kerning) - (add.i char r0 -3) - (nop!) - (b.z arg-value main-next-character :delay (and pass-flags pass-flags char)) - (b main-next-character :delay (ori pass-flags pass-flags 2)) - (label main-horizontal-offset) - (m corner0 arg-value) - (add.i char t5 -45) - (b.z t5 main-set-horizontal-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char main-subtract-horizontal-offset :delay (nop!)) - (b main-next-character :delay (vaddx.x cursor cursor corner0)) - (label main-subtract-horizontal-offset) - (b main-next-character :delay (vsubx.x cursor cursor corner0)) - (label main-set-horizontal-offset) - (b main-next-character :delay (vaddx.x cursor vf0 corner0)) - (label main-vertical-offset) - (m corner0 arg-value) - (add.i char t5 -45) - (b.z t5 main-set-vertical-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char main-subtract-vertical-offset :delay (nop!)) - (b main-next-character :delay (vaddx.y cursor cursor corner0)) - (label main-subtract-vertical-offset) - (b main-next-character :delay (vsubx.y cursor cursor corner0)) - (label main-set-vertical-offset) - (b main-next-character :delay (vaddx.y cursor vf0 corner0)) - (label main-save-style) - (nop!) - (l.w char (-> fw last-color-32)) - (nop!) - (l.vf color0 (-> fw current-verts color 0 quad)) - (nop!) - (l.vf color1 (-> fw current-verts color 1 quad)) - (nop!) - (l.vf color2 (-> fw current-verts color 2 quad)) - (nop!) - (l.vf color3 (-> fw current-verts color 3 quad)) - (nop!) - (s.w char (-> fw save-last-color-32)) - (nop!) - (s.vf color0 (-> fw save-color 0 quad)) - (nop!) - (s.vf color1 (-> fw save-color 1 quad)) - (nop!) - (s.vf color2 (-> fw save-color 2 quad)) - (nop!) - (s.vf color3 (-> fw save-color 3 quad)) - (b main-next-character :delay (s.vf cursor (-> fw save quad))) - (label main-restore-style) - (nop!) - (l.w char (-> fw save-last-color-32)) - (nop!) - (l.vf color0 (-> fw save-color 0 quad)) - (nop!) - (l.vf color1 (-> fw save-color 1 quad)) - (nop!) - (l.vf color2 (-> fw save-color 2 quad)) - (nop!) - (l.vf color3 (-> fw save-color 3 quad)) - (nop!) - (s.w char (-> fw last-color-32)) - (nop!) - (s.vf color0 (-> fw current-verts color 0 quad)) - (nop!) - (s.vf color0 (-> fw dest-verts color 0 quad)) - (nop!) - (s.vf color1 (-> fw current-verts color 1 quad)) - (nop!) - (s.vf color1 (-> fw dest-verts color 1 quad)) - (nop!) - (s.vf color2 (-> fw current-verts color 2 quad)) - (nop!) - (s.vf color2 (-> fw dest-verts color 2 quad)) - (nop!) - (s.vf color3 (-> fw current-verts color 3 quad)) - (nop!) - (s.vf color3 (-> fw dest-verts color 3 quad)) - (b main-next-character :delay (l.vf cursor (-> fw save quad))) - (label main-extended-character) - (l.bu char str-cursor 4) - (add.i str-cursor str-cursor 1) - (vadd.xyz corner3 cursor size3) - (and.i t5 char 127) - (vsub.xyzw corner0 limits cursor) - (add.i t5 t5 255) - (b emit-main-glyph :delay (sll t5 t5 4)) - (label main-character) - (vadd.xyz corner3 cursor size3) - (sll t5 char 4) - (vsub.xyzw corner0 limits cursor) - (add.i arg-value char -10) - (b.z arg-value main-line-break :delay (add.i arg-value char -13)) - (b.nz arg-value emit-main-glyph :delay (nop!)) - (label main-line-break) - (add.i justify-cursor justify-cursor 16) - (nop!) - (b main-next-character :delay (l.vf cursor (-> justify-cursor justify 0 quad))) - (label emit-main-glyph) - ;; The font tables supply atlas s,t and glyph advance in w. Construct and transform the four - ;; corners, reject glyphs behind the camera, then write texture, color, and XYZ2 triplets. - (add t5 t5 glyph-table) - (nop!) - (l.vf st0 t5 -256) - (m t5 corner0) - (b.lt t5 r0 draw-finish :delay (vadd.xyz st1 st0 size-st1)) - (sra t5 t5 31) - (vadd.xyz st2 st0 size-st2) - (b.lt t5 r0 main-next-character :delay (vadd.xyz st3 st0 size-st3)) - (vadd.xyz corner0 cursor vf0) - (nop!) - (vadd.xyz corner1 cursor size1) - (nop!) - (vadd.xyz corner2 cursor size2) - (s.vf st0 (-> fw current-verts tex-st 0 quad)) - (vadd.xyz corner3 cursor size3) - (s.vf st1 (-> fw current-verts tex-st 1 quad)) - (mul.vf advance st0 size1) - (s.vf st2 (-> fw current-verts tex-st 2 quad)) - (nop!) - (s.vf st3 (-> fw current-verts tex-st 3 quad)) - (nop!) - (s.vf corner0 (-> fw current-verts pos 0 quad)) - (nop!) - (s.vf corner1 (-> fw current-verts pos 1 quad)) - (nop!) - (s.vf corner2 (-> fw current-verts pos 2 quad)) - (nop!) - (s.vf corner3 (-> fw current-verts pos 3 quad)) - (l.vf corner0 (-> fw current-verts pos 0 quad)) - (nop!) - (l.vf corner1 (-> fw current-verts pos 1 quad)) - (vmulaw.xyzw acc mat-row3 vf0) - (l.vf corner2 (-> fw current-verts pos 2 quad)) - (vmaddax.xyzw acc mat-row0 corner0) - (l.vf corner3 (-> fw current-verts pos 3 quad)) - (vmadday.xyzw acc mat-row1 corner0) - (nop!) - (vmaddz.xyzw corner0 mat-row2 corner0) - (nop!) - (vmulaw.xyzw acc mat-row3 vf0) - (nop!) - (vmaddax.xyzw acc mat-row0 corner1) - (nop!) - (vmadday.xyzw acc mat-row1 corner1) - (nop!) - (vmaddz.xyzw corner1 mat-row2 corner1) - (nop!) - (vmulaw.xyzw acc mat-row3 vf0) - (nop!) - (vmaddax.xyzw acc mat-row0 corner2) - (nop!) - (vmadday.xyzw acc mat-row1 corner2) - (nop!) - (vmaddz.xyzw corner2 mat-row2 corner2) - (nop!) - (vmulaw.xyzw acc mat-row3 vf0) - (nop!) - (vmaddax.xyzw acc mat-row0 corner3) - (nop!) - (vmadday.xyzw acc mat-row1 corner3) - (nop!) - (vmaddz.xyzw corner3 mat-row2 corner3) - (vdiv Q limits.z corner0.w) - (l.q t5 (-> fw char-tmpl dma-vif quad)) - (nop!) - (l.q arg-value (-> fw char-tmpl quad 1)) - (nop!) - (s.q t5 dma-out) - (nop!) - (s.q arg-value dma-out 16) - (l.vf st0 (-> fw current-verts tex-st 0 quad)) - (m.q t5 lo-tmpl) - (l.vf st1 (-> fw current-verts tex-st 1 quad)) - (and.i char char 128) - (l.vf st2 (-> fw current-verts tex-st 2 quad)) - (movn t5 hi-tmpl char) - (vmulq.xyz corner0 corner0 Q) - (s.q t5 dma-out 32) - (vmulq.xyz st0 st0 Q) - (nop!) - (vnop) - (nop!) - (vnop) - (nop!) - (vdiv Q limits.z corner1.w) - (nop!) - (l.vf st3 (-> fw current-verts tex-st 3 quad)) - (nop!) - (add.vf corner0 corner0 hvdf-offset) - (nop!) - (l.vf color0 (-> fw current-verts color 0 quad)) - (nop!) - (l.vf color1 (-> fw current-verts color 1 quad)) - (nop!) - (l.vf color2 (-> fw current-verts color 2 quad)) - (nop!) - (vftoi4.xyzw corner0 corner0) - (nop!) - (vmulq.xyz corner1 corner1 Q) - (nop!) - (vmulq.xyz st1 st1 Q) - (s.vf st0 dma-out 48) - (vnop) - (nop!) - (vnop) - (nop!) - (vdiv Q limits.z corner2.w) - (s.vf color0 dma-out 64) - (l.vf color3 (-> fw current-verts color 3 quad)) - (s.vf corner0 dma-out 80) - (add.vf corner1 corner1 hvdf-offset) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (vftoi4.xyzw corner1 corner1) - (nop!) - (vmulq.xyz corner2 corner2 Q) - (nop!) - (vmulq.xyz st2 st2 Q) - (s.vf st1 dma-out 96) - (vnop) - (nop!) - (vnop) - (nop!) - (vdiv Q limits.z corner3.w) - (s.vf color1 dma-out 112) - (nop!) - (s.vf corner1 dma-out 128) - (add.vf corner2 corner2 hvdf-offset) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (vftoi4.xyzw corner2 corner2) - (nop!) - (vmulq.xyz corner3 corner3 Q) - (nop!) - (vmulq.xyz st3 st3 Q) - (s.vf st2 dma-out 144) - (nop!) - (s.vf color2 dma-out 160) - (add.vf corner3 corner3 hvdf-offset) - (s.vf corner2 dma-out 176) - (and.i char pass-flags 2) - (nop!) - (b.z char main-advance-fixed :delay (nop!)) - (b finish-main-glyph :delay (vaddw.x cursor cursor advance)) - (label main-advance-fixed) - (nop!) - (vaddw.x cursor cursor size2) - (label finish-main-glyph) - (vftoi4.xyzw corner3 corner3) - (nop!) - (nop!) - (nop!) - (nop!) - (s.vf st3 dma-out 192) - (nop!) - (s.vf color3 dma-out 208) - (nop!) - (s.vf corner3 dma-out 224) - (b main-next-character :delay (add.i dma-out dma-out 240)) - (label draw-finish) - (l.w out-buf (-> fw buf)) - (s.w dma-out (-> out-buf base)) - (l.vf origin (-> ctxt origin quad)) - (vsub.xyzw cursor cursor origin) - (m v0 cursor) - (jr ra :delay (add sp sp r0))))) - (defun draw-string-adv ((str string) (buf dma-buffer) (ctxt font-context)) "Draw str into buf with ctxt, then advance ctxt's origin x by the returned width." (+! (-> ctxt origin x) (draw-string str buf ctxt)) @@ -2694,185 +1636,6 @@ (.mov advance-x cursor) advance-x))) -(#unless PC_PORT - (defun get-string-length ((str string) (ctxt font-context)) - "Interpret str with ctxt's size, alignment, and kerning settings without drawing, and return the - horizontal displacement from the starting origin." - (declare (asm-func float)) - ;; Apply the same display scaling and inline-command parser as draw-string, but only advance - ;; the cursor. This keeps measurement and drawing identical for kerning, size changes, saved - ;; positions, and explicit horizontal offsets. - ;; a1 is the context, then the video parameters, then *font-work*. - (rlet ((str :reg a0) - (ctxt :reg a1 :type font-context) - (video-parms :reg a1 :type video-parms) - (fw :reg a1 :type font-work) - (glyph-table :reg a2) - (char :reg a3) - (flags :reg v1) - (sign-char :reg t0) - (glyph-offset :reg t0) - (arg-value :reg t1) - (char-test :reg t2) - (corner0 :reg vf1) - (st0 :reg vf5) - (size1 :reg vf13) - (size2 :reg vf14) - (advance :reg vf19) - (cursor :reg vf23) - (origin :reg vf24) - (limits :reg vf25)) - (label measure-entry) - (l.vf limits (-> ctxt context-vec quad)) - (l.vf cursor (-> ctxt origin quad)) - (l.vf origin (-> ctxt origin quad)) - (l.w v1 (-> ctxt flags-signed)) - (m! video-parms *video-parms*) - (m video-parms video-parms) - (l.vf corner0 (-> video-parms relative-x-scale-reciprical)) - (vmul.xy limits limits corner0) - (vmul.xy cursor cursor corner0) - (vmul.xy origin origin corner0) - (m! fw *font-work*) - (m fw fw) - (s.w str (-> fw str-ptr)) - (s.w flags (-> fw flags)) - (vmove.xyzw corner0 vf0) - (and.i glyph-table flags 32) - (nop!) - (b.nz glyph-table measure-initial-large-font :delay (m! glyph-table *font12-table*)) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-small quad)) - (b measure-next-character :delay (l.vf size2 (-> fw size2-small quad))) - (label measure-initial-large-font) - (nop!) - (m! glyph-table *font24-table*) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-large quad)) - (nop!) - (l.vf size2 (-> fw size2-large quad)) - (label measure-next-character) - ;; A byte value of 1 introduces a two-byte extended glyph. A tilde introduces an optional - ;; sign and decimal argument followed by a command letter; all other bytes are glyphs. - (l.bu char str 4) - (add.i str str 1) - (b.z char measure-finish :delay (add.i sign-char char -1)) - (b.z sign-char measure-extended-character :delay (add.i sign-char char -126)) - (b.nz sign-char measure-character :delay (nop!)) - (l.bu char str 4) - (add.i str str 1) - (add.i sign-char r0 0) - (add.i arg-value r0 0) - (b.z char measure-finish :delay (add.i char-test char -43)) - (movz sign-char char char-test) - (add.i char-test char -45) - (movz sign-char char char-test) - (nop!) - (b.nz sign-char measure-format-digits :delay (add.i char-test char -121)) - (b.z char-test measure-save-position :delay (add.i arg-value char -89)) - (b.z arg-value measure-save-position :delay (add.i arg-value char -122)) - (b.z arg-value measure-restore-position :delay (add.i arg-value char -90)) - (b.z arg-value measure-restore-position :delay (add.i arg-value char -48)) - (b.lt arg-value r0 measure-character :delay (add.i arg-value char -57)) - (b.gt arg-value r0 measure-character :delay (add.i arg-value char -48)) - (label measure-format-digits) - (l.bu char str 4) - (add.i str str 1) - (b.z char measure-finish :delay (add.i char-test char -110)) - (b.z char-test measure-set-font-size :delay (add.i char-test char -78)) - (b.z char-test measure-set-font-size :delay (add.i char-test char -108)) - (b.z char-test measure-next-character :delay (add.i char-test char -76)) - (b.z char-test measure-next-character :delay (add.i char-test char -119)) - (b.z char-test measure-next-character :delay (add.i char-test char -87)) - (b.z char-test measure-next-character :delay (add.i char-test char -107)) - (b.z char-test measure-set-kerning :delay (add.i char-test char -75)) - (b.z char-test measure-set-kerning :delay (add.i char-test char -106)) - (b.z char-test measure-next-character :delay (add.i char-test char -74)) - (b.z char-test measure-next-character :delay (add.i char-test char -104)) - (b.z char-test measure-horizontal-offset :delay (add.i char-test char -72)) - (b.z char-test measure-horizontal-offset :delay (add.i char-test char -118)) - (b.z char-test measure-next-character :delay (add.i char-test char -86)) - (b.z char-test measure-next-character :delay (add.i char-test char -48)) - (b.lt char-test r0 measure-character :delay (add.i t3 char -57)) - (b.gt t3 r0 measure-character :delay (sll t3 arg-value 2)) - (add char arg-value t3) - (nop!) - (sll char char 1) - (nop!) - (b measure-format-digits :delay (add arg-value char char-test)) - (label measure-set-font-size) - (b.nz arg-value measure-select-large-font :delay (m! glyph-table *font12-table*)) - (m glyph-table glyph-table) - (add.i char r0 -33) - (l.vf size1 (-> fw size1-small quad)) - (nop!) - (l.vf size2 (-> fw size2-small quad)) - (b measure-next-character :delay (and flags flags char)) - (label measure-select-large-font) - (nop!) - (m! glyph-table *font24-table*) - (m glyph-table glyph-table) - (nop!) - (l.vf size1 (-> fw size1-large quad)) - (nop!) - (l.vf size2 (-> fw size2-large quad)) - (b measure-next-character :delay (ori flags flags 32)) - (label measure-set-kerning) - (add.i char r0 -3) - (nop!) - (b.z arg-value measure-next-character :delay (and flags flags char)) - (b measure-next-character :delay (ori flags flags 2)) - (label measure-horizontal-offset) - (m corner0 arg-value) - (add.i char sign-char -45) - (b.z sign-char measure-set-horizontal-offset :delay (vitof0.xyzw corner0 corner0)) - (b.z char measure-subtract-horizontal-offset :delay (nop!)) - (b measure-next-character :delay (vaddx.x cursor cursor corner0)) - (label measure-subtract-horizontal-offset) - (b measure-next-character :delay (vsubx.x cursor cursor corner0)) - (label measure-set-horizontal-offset) - (b measure-next-character :delay (vaddx.x cursor vf0 corner0)) - (label measure-save-position) - (b measure-next-character :delay (s.vf cursor (-> fw save quad))) - (label measure-restore-position) - (b measure-next-character :delay (l.vf cursor (-> fw save quad))) - (label measure-extended-character) - (l.bu char str 4) - (add.i str str 1) - (nop!) - (and.i char char 127) - (nop!) - (add.i char char 255) - (b measure-glyph :delay (sll glyph-offset char 4)) - (label measure-character) - (nop!) - (sll glyph-offset char 4) - (nop!) - (add.i arg-value char -10) - (b.z arg-value measure-line-break :delay (add.i char char -13)) - (b.nz char measure-glyph :delay (nop!)) - (label measure-line-break) - (b measure-next-character :delay (vaddx.x cursor vf0 origin)) - (label measure-glyph) - (add char glyph-offset glyph-table) - (nop!) - (l.vf st0 char -256) - (nop!) - (mul.vf advance st0 size1) - (and.i char flags 2) - (b.z char measure-advance-fixed :delay (nop!)) - (b measure-next-character :delay (vaddw.x cursor cursor advance)) - (label measure-advance-fixed) - (b measure-next-character :delay (vaddw.x cursor cursor size2)) - (label measure-finish) - (vsub.xyzw cursor cursor origin) - (m v0 cursor) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!)))) - (defun draw-string-xy ((str string) (buf dma-buffer) (x int) (y int) (color font-color) (flags font-flags)) "Draw str at x,y with a temporary context initialized from the default font matrix, color, and flags, append its glyph packets to buf, and return the horizontal advance." diff --git a/goal_src/jak1/engine/gfx/foreground/bones-h.gc b/goal_src/jak1/engine/gfx/foreground/bones-h.gc index 19f8d1e67c..7510f68e61 100644 --- a/goal_src/jak1/engine/gfx/foreground/bones-h.gc +++ b/goal_src/jak1/engine/gfx/foreground/bones-h.gc @@ -51,7 +51,7 @@ (dma-list dma-packet :inline :overlay-at (-> buffer 0 joint 0 control-bits 0)))) (#unless PC_PORT - (defun invalidate-cache-line ((address pointer)) + (defun invalidate-cache-line ((address object)) "Write back and invalidate both ways of an EE data-cache line." (declare (asm-func int)) (rlet ((address-reg :reg a0) @@ -65,7 +65,7 @@ (jr ra :delay (add sp sp r0))))) (#when PC_PORT - (defun invalidate-cache-line ((address pointer)) + (defun invalidate-cache-line ((address object)) "Write back and invalidate both ways of an EE data-cache line." 0)) diff --git a/goal_src/jak1/engine/gfx/generic/generic-effect-ee-asm.gc b/goal_src/jak1/engine/gfx/generic/generic-effect-ee-asm.gc new file mode 100644 index 0000000000..9d0cf724f0 --- /dev/null +++ b/goal_src/jak1/engine/gfx/generic/generic-effect-ee-asm.gc @@ -0,0 +1,4638 @@ +(defun generic-upload-vu0 () + "Build and start an asynchronous VIF0 DMA chain which uploads the Generic VU0 program. This + entry does not wait for completion, and its one-off chain construction is less optimized than + upload-vu0-program." + (let ((dma-buf *vu0-dma-list*)) + (set! (-> dma-buf base) (-> dma-buf data)) + (set! (-> dma-buf end) (&-> dma-buf data-buffer (-> dma-buf allocated-length))) + (dma-buffer-add-vu-function dma-buf generic-vu0-block 0) + (let ((end-tag (the-as (pointer uint64) (-> dma-buf base)))) + (set! (-> end-tag 0) #x70000000) + (set! (-> end-tag 1) 0) + (set! (-> dma-buf base) (&+ (the-as pointer end-tag) 16))) + (sync.l) + (dma-buffer-send-chain (the-as dma-bank-source #x10008000) dma-buf)) + (none)) + + + ;; Generic work begins 16 bytes into scratchpad. The assembly keeps the scratchpad page in at, so + ;; offsets 20 through 108 name ptr-vtxs through from-spr-waits in generic-saves. The converters + ;; update those cursors directly because their inner loops share them with the DMA packet builders. + ;; Local vectors used by generic-debug-light-proc. The assembly addresses these labels relative to fp. + (asm-data + (label generic-debug-light-normal-range) + (word #xbf800000 #x3f800000 0 0) + (label generic-debug-light-color-center) + (word 0 0 0 #x43000000) + (label generic-debug-light-color-scale) + (word #x437f0000 #x437f0000 #x437f0000 0)) + + (defun generic-debug-light-proc () + "Replace Generic vertex colors with packed, clamped normal components while preserving the other + expanded vertex attributes." + (declare (asm-func none) (allow-saved-regs)) + ;; A debug processor: it replaces every vertex color with the vertex's own normal, so a surface is + ;; tinted by which way it faces. Clamp xyz into [-1, 1], scale by 127 and bias by 128 so that -1 + ;; becomes 1 and +1 becomes 255, then pack the three floats down to three bytes with ftoi0, ppach + ;; and ppacb. A wrong result looks like flat color, or like color that does not change as the + ;; object turns. + ;; + ;; Everything else about the output is the ordinary layout, which is the point: drop this in place + ;; of generic-light-proc and the geometry is unchanged, only shaded differently. Nothing installs + ;; it in the shipped game. + (asm-block build-range-constants + (label generic-debug-light-proc-entry) + (add.i sp sp -80) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.d fp at (+ (generic-work-offset fx-buf work storage2 data 0) 8)) + (m fp t9) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 4)) + (add.i v1 fp generic-debug-light-color-scale) + (add.i a0 fp generic-debug-light-color-center) + (add.i a1 fp generic-debug-light-normal-range) + (l.vf vf5 v1) + (l.vf vf6 a0) + (l.vf vf7 a1) + (m v1 vf7) + (lui at #x7000) + (nop!) + (nop!) + (rlet ((gsf-buf :reg a0 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a3 at (generic-work-offset saves num-dps)) + (nop!) + (l.w v1 (-> gsf-buf info ptr-iks)) + (nop!) + (l.w t1 (-> gsf-buf info ptr-verts))) + (nop!) + (l.w a0 at (generic-work-offset saves ptr-vtxs)) + (nop!) + (l.w a1 at (generic-work-offset saves ptr-clrs)) + (add.i t2 r0 255) + (l.w a2 at (generic-work-offset saves ptr-texs)) + (add.i t3 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (lui t0 -2) + (add.i t4 a3 -4) + (m a3 a3) + (nop!) + (ori t0 t0 #xfffe) + (mmi-nop!) + (pextlw t0 t0 t0) + (mmi-nop!) + (pextlw t0 t0 t0) + (mmi-nop!) + (pextlw t1 t1 t1) + (mmi-nop!) + (pextlw t1 t1 t1) + (mmi-nop!) + (pcpyh t2 t2) + (mmi-nop!) + (pcpyld t2 t2 t2) + (mmi-nop!) + (pcpyh t3 t3) + (mmi-nop!) + (pcpyld t3 t3 t3)) + ;; Four vertices a pass. The clamp, scale and bias happen in VU0 registers because vf5-vf7 already + ;; hold the three constants; only the pack down to bytes is EE work. + (asm-block vertex-loop + (label generic-debug-light-proc-vertex-loop) + (add.i a3 a3 -4) + (l.dr t4 v1) + (add.i a0 a0 48) + (l.dl t4 v1 7) + (add.i a2 a2 16) + (add.i v1 v1 8) + (pextlh t4 r0 t4) + (mmi-nop!) + (and.q t5 t4 t2) + (mmi-nop!) + (sll.w t5 t5 5) + (mmi-nop!) + (add.w s4 t5 t1) + (mmi-nop!) + (srl32 s5 s4 0) + (l.q t6 s4) + (pcpyud gp s4 r0) + (l.q t7 s5) + (srl32 t9 gp 0) + (l.q t5 gp) + (and.q ra t4 t3) + (l.q t8 t9) + (sra.w ra ra 8) + (l.vf vf1 s4 16) + (pextuw s4 t7 t6) + (l.vf vf2 s5 16) + (pextuw s5 t8 t5) + (l.vf vf3 gp 16) + (pcpyud gp s4 s5) + (l.vf vf4 t9 16) + (and.q t9 gp t0) + (nop!) + (or.q t9 t9 ra) + (nop!) + (nop!) + (max.x.vf.xyz vf1 vf1 vf7) + (nop!) + (max.x.vf.xyz vf2 vf2 vf7) + (nop!) + (max.x.vf.xyz vf3 vf3 vf7) + (nop!) + (max.x.vf.xyz vf4 vf4 vf7) + (nop!) + (min.y.vf.xyz vf1 vf1 vf7) + (nop!) + (min.y.vf.xyz vf2 vf2 vf7) + (nop!) + (min.y.vf.xyz vf3 vf3 vf7) + (nop!) + (min.y.vf.xyz vf4 vf4 vf7) + (nop!) + (mul.vf vf1 vf1 vf5) + (nop!) + (mul.vf vf2 vf2 vf5) + (nop!) + (mul.vf vf3 vf3 vf5) + (nop!) + (mul.vf vf4 vf4 vf5) + (nop!) + (add.vf vf1 vf1 vf6) + (nop!) + (add.vf vf2 vf2 vf6) + (nop!) + (add.vf vf3 vf3 vf6) + (nop!) + (add.vf vf4 vf4 vf6) + (nop!) + (ftoi.vf vf1 vf1) + (nop!) + (ftoi.vf vf2 vf2) + (nop!) + (ftoi.vf vf3 vf3) + (nop!) + (ftoi.vf vf4 vf4) + (nop!) + (m gp vf1) + (nop!) + (m s5 vf2) + (nop!) + (m s4 vf3) + (nop!) + (m ra vf4) + (ppach gp r0 gp) + (mmi-nop!) + (ppach s5 r0 s5) + (mmi-nop!) + (ppach s4 r0 s4) + (mmi-nop!) + (ppach ra r0 ra) + (mmi-nop!) + (ppacb gp r0 gp) + (mmi-nop!) + (ppacb s3 r0 s5) + (mmi-nop!) + (ppacb s5 r0 s4) + (mmi-nop!) + (ppacb ra r0 ra) + (mmi-nop!) + (pextlw gp s3 gp) + (nop!) + (add.i a1 a1 16) + (add.i s4 a3 -4) + (pextlw ra ra s5) + (mmi-nop!) + (pcpyld ra ra gp) + (s.q t9 a2 -16) + (prot3w t8 t8) + (s.q ra a1 -16) + (prot3w t7 t7) + (mmi-nop!) + (pextuw t9 t7 t6) + (mmi-nop!) + (pcpyld t7 t5 t7) + (mmi-nop!) + (pcpyld t6 t9 t6) + (mmi-nop!) + (pextuw t5 t8 t5) + (s.q t6 a0 -48) + (pcpyld t5 t8 t5) + (s.q t7 a0 -32) + (b.gt a3 r0 generic-debug-light-proc-vertex-loop :delay (s.q t5 a0 -16)) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.d fp at (+ (generic-work-offset fx-buf work storage2 data 0) 8)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 80)) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-none-dma-wait () + "Wait until the scratchpad-to-memory DMA channel is idle without processing vertices." + (declare (asm-func none)) + ;; The other do-nothing processor, for the case where there is no work but the previous packet is + ;; still being copied out. Waiting here rather than at the top of the next pass keeps the wait out + ;; of the common path. + ;; + ;; The sixteen-instruction no-op pad is the shape of a poll written for a channel expected to be + ;; busy: a loaded word is not usable for two more instructions, and re-reading a hardware register + ;; faster than this buys nothing. Unlike the other waits in this file it charges no counter. + (asm-block address-the-channel + (label generic-none-dma-wait-entry) + (lui v1 #x1000) + (ori v1 v1 #xd400)) + ;; Sixteen instructions a pass, almost all of them empty. The channel is expected to be busy. + (asm-block poll-dma + (label generic-none-dma-wait-poll-dma) + (l.w a0 v1) + (nop!) + (nop!) + (nop!) + (and.i a0 a0 DMA-CHCR-STR) + (nop!) + (b.z a0 generic-none-dma-wait-dma-idle :delay (nop!)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (b generic-none-dma-wait-poll-dma :delay (nop!))) + ;; Return zero. There was never anything to convert. + ;; Return zero. There was never anything to convert. + (asm-block dma-idle + (label generic-none-dma-wait-dma-idle) + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!)) + ) + + (defun generic-copy-vtx-dclr-dtex () + "Expand the GSF vertex stream while applying its packed delta-color and delta-texture attributes." + (declare (asm-func none) (allow-saved-regs)) + ;; The plainest of the processors: it fills all three streams and applies the draw-point attribute + ;; lanes, and does no shading at all. A vertex's color is its own dclr added to its own clr, and + ;; its coordinate its dtex added to its own tex; nothing is looked up and no VU0 call happens. + ;; + ;; This is what generic-no-light-dproc does with the delta pass folded in, and nothing installs + ;; either of them in Jak 1 - the shipped no-light path goes through generic-no-light-dproc. Kept + ;; because it is the shortest complete statement of what a processor has to produce. + (asm-block load-stream-cursors + (label generic-copy-vtx-dclr-dtex-entry) + (add.i sp sp -96) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 5)) + (lui at #x7000) + (nop!) + (nop!) + (rlet ((gsf-buf :reg a0 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a1 at (generic-work-offset saves num-dps)) + (nop!) + (l.w v1 (-> gsf-buf info ptr-iks)) + (add.i a1 a1 3) + (l.w a2 (-> gsf-buf info ptr-verts))) + (sra a0 a1 2) + (nop!) + (sll a0 a0 3) + (add.i a3 r0 255) + (lui a1 -2) + (add.i t1 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (ori a1 a1 #xfffe) + (add a0 v1 a0) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a2 a2 a2) + (l.w t2 at (generic-work-offset saves ptr-vtxs)) + (pextlw a2 a2 a2) + (l.w t3 at (generic-work-offset saves ptr-clrs)) + (pcpyh a3 a3) + (l.w t4 at (generic-work-offset saves ptr-texs)) + (pcpyld a3 a3 a3) + (l.q t0 at (generic-work-offset fx-buf work consts texture-offset)) + (pcpyh t1 t1) + (l.d t5 v1) + (pcpyld t1 t1 t1) + (mmi-nop!) + ;; Prime the indexed lookup before entering the four-vertex loop. Each source halfword selects + ;; one 32-byte GSF vertex record; the four selected records are gathered through EE packed loads. + (pextlh t6 r0 t5) + (mmi-nop!) + (and.q t5 t6 a3) + (mmi-nop!) + (sll.w t5 t5 5) + (mmi-nop!) + (add.i t2 t2 -48) + (add.i t3 t3 -16) + (b generic-copy-vtx-dclr-dtex-prime-vertex-loop :delay (add.i t4 t4 -16))) + (asm-block vertex-loop + (label generic-copy-vtx-dclr-dtex-vertex-loop) + ;; Drain the previous group while the next four packed indices are decoded. + (pextlh t6 r0 ra) + (s.q t5 t2) + (and.q t5 t6 a3) + (s.q t9 t2 16) + (sll.w t5 t5 5) + (s.q t7 t2 32)) + ;; The loop is rotated: this is the body, entered directly from the setup above, and the short block + ;; before it stores the group this pass gathered. So the stores at the top of a pass belong to the + ;; previous four vertices, and the block above is reached once more than this one. + (asm-block prime-vertex-loop + (label generic-copy-vtx-dclr-dtex-prime-vertex-loop) + ;; The low five index bits select a byte offset within the GSF work area. The upper bits carry + ;; the signed color/texture deltas which are merged after the source records have been gathered. + (add.w s4 t5 a2) + (mmi-nop!) + (srl32 gp s4 0) + (add.i t2 t2 48) + (pcpyud ra s4 r0) + (l.q t5 s4) + (srl32 t7 ra 0) + (add.i t3 t3 16) + (and.q t6 t6 t1) + (l.w t9 s4 16) + (sra.w t8 t6 8) + (l.q s2 gp 16) + (add.i t4 t4 16) + (l.q s5 gp 16) + (add.i v1 v1 8) + (l.q s3 gp 16) + (nop!) + (l.q t6 ra) + (pextlw s2 s2 t9) + (l.q t9 gp) + (pextlw s3 s3 s5) + (l.q s5 t7) + (pcpyld s3 s3 s2) + (l.w s4 s4 20) + (nop!) + (l.w gp gp 20) + (add.h s3 s3 t0) + (l.w ra ra 20) + (and.q s3 s3 a1) + (l.w t7 t7 20) + (or.q s3 s3 t8) + (s.w s4 t3) + (prot3w t8 s5) + (s.q s3 t4) + (prot3w t9 t9) + (s.w gp t3 4) + (pextuw gp t9 t5) + (s.w ra t3 8) + (pcpyld t9 t6 t9) + (l.d ra v1) + (pcpyld t5 gp t5) + (mmi-nop!) + (pextuw t6 t8 t6) + (s.w t7 t3 12) + (b.ne v1 a0 generic-copy-vtx-dclr-dtex-vertex-loop :delay (pcpyld t7 t8 t6)) + (nop!) + (s.q t5 t2) + (nop!) + (s.q t9 t2 16) + (nop!) + (s.q t7 t2 32) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 96)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-light ((gsf-buf gsf-buffer) (shaders pointer)) + "Build one lit Generic pass using shaders, then start the scratchpad-to-memory DMA." + (declare (asm-func none)) + ;; The whole calling convention in twenty instructions, which is the reason to read it: store the + ;; two arguments into generic-saves, clear is-envmap so the base GIF template is used, call the + ;; single-pass builder, call the lighting processor, hand the buffer to fromSPR. Any caller that + ;; wants a different set of effects substitutes different calls in the middle and changes nothing + ;; else. + ;; + ;; Nothing installs this in Jak 1 - mercneric and generic-TIE both do the same three steps by hand + ;; so they can choose per fragment - but it is the readable version of what they do. + (asm-block build-vertex-packet + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-light-entry) + (add.i sp sp -16) + (s.d ra at (generic-work-offset fx-buf work storage data 0)) + (lui at #x7000) + (s.w a1 at (generic-work-offset saves ptr-shaders)) + (s.w a0 at (generic-work-offset saves gsf-buf)) + (s.w r0 at (generic-work-offset saves is-envmap)) + (m! t9 generic-prepare-dma-single) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 generic-light-proc) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (lui at #x7000) + (lui from-spr #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori from-spr from-spr #xd000) + (l.w t1 (-> from-spr chcr)) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 generic-light-start-dma :delay (nop!)) + (m t1 from-spr) + (nop!))) + (asm-block wait-for-dma + (label generic-light-wait-for-dma) + (l.w t2 t0) + (nop!) + (l.w t3 t1) + (nop!) + (and.i t3 t3 DMA-CHCR-STR) + (add.i t2 t2 1) + (b.nz t3 generic-light-wait-for-dma :delay (s.w t2 t0)) + (m t0 r0)) + ;; Program the channel, advance saves.basep by exactly what it will deliver, and switch to the + ;; other output buffer. Nothing waits for this transfer - the wait above is what makes that safe. + (asm-block start-dma + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-light-start-dma) + (sll t0 a0 4) + (s.w a3 (-> from-spr sadr)) + (nop!) + (s.w a1 (-> from-spr madr)) + (add.i a3 r0 DMA-CHCR-STR) + (s.w a0 (-> from-spr qwc)) + (add a0 a1 t0) + (s.w a3 (-> from-spr chcr)) + (nop!) + (s.w a0 at (generic-work-offset saves basep)) + (m a0 r0) + (xor.i v1 v1 GENERIC-OUTBUF-FLIP) + (s.w v1 at (generic-work-offset saves cur-outbuf)) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage data 0)) + (jr ra :delay (add.i sp sp 16)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!))) + ) + + (defun generic-envmap-only-proc () + "Build the environment-map-only Generic vertex stream, including reflected texture coordinates, + environment colors, strip metadata, and its final DMA packet." + (declare (asm-func none) (allow-saved-regs)) + ;; The complete single-pass converter for "the reflection is the only thing drawn": it fills all three + ;; base cursors itself - positions, the flat environment tint as the color, and the reflected + ;; coordinate - so the object is drawn once, as pure reflection, with no base texture underneath. + ;; + ;; It is dead, and so is its other half. saves.is-envmap is cleared everywhere in the game and set + ;; nowhere, and its only reader is generic-prepare-dma-single - so the envmap branch of that builder, + ;; which swaps in the unfogged GIF tag and repeats the one shared environment shader per strip, is + ;; exactly the packet this processor wants and is never reached either. Together they are a coherent + ;; path that was never wired up. + ;; The reflection is a sphere map, and it is worth reading the VU0 side once because the EE half is + ;; unreadable without it. Per vertex, entry 48 reflects the eye direction about the vertex normal + ;; (dot the two, scale the normal by it, add), normalizes the result, multiplies by the 0.5 in + ;; consts.envmap.consts.z and adds the 0.5 in .w - so a reflection pointing straight at the camera + ;; lands at the centre of the texture and one pointing away lands at the edge - then converts x and y + ;; to the GS's 12-bit fixed point with ftoi12. The texture is clamped in both directions, which is + ;; what keeps the edge of the sphere from wrapping. + ;; + ;; So the output is one coordinate pair per draw point that slides across the environment texture as + ;; the object turns. If it were wrong the symptom is unmistakable: the reflection either stops moving + ;; with the object, or smears radially because the normalize went wrong, or clamps to a single edge + ;; color because the bias is off. + ;; + ;; Four vertices per pass, and entry 48 has the same one-group lag as the lighting entry - it copies + ;; its previous results forward at the top - so the coordinates the EE reads out after a call belong + ;; to the group submitted by the call before it. Everything in the loop is one group behind and the + ;; tail publishes the last one. + ;; + ;; Its conversion loop is unrolled three times, and the three tails are the three phases of that + ;; unroll rather than three vertex counts - the names are misleading. The pipeline is three groups + ;; deep and parks its live state in saves.envmap.verts and .kicks at *constant* displacements, so + ;; rotating which slot triple a pass uses is register renaming the ISA cannot do against fixed + ;; offsets. Hence three copies of the body, each hard-coding its phase, and three exits, each + ;; draining the slots its phase left the data in. All three write a full group of four; nothing is + ;; lane-masked anywhere. Which one you land in is ((ceil(dps / 4) - 1) mod 3). + ;; + ;; The state has to live in memory rather than registers because this processor also emits positions, + ;; so it must keep whole quadwords alive across the VU0 call - twelve of them plus three kick masks. + ;; The fourth kicks slot is declared and never touched. + (asm-block load-stream-cursors + (label generic-envmap-only-proc-entry) + (add.i sp sp -128) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (lui at #x7000) + (nop!) + (rlet ((gsf-buf :reg a0 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w v1 at (generic-work-offset saves num-dps)) + (nop!) + (l.w t1 (-> gsf-buf info ptr-iks)) + (nop!) + (l.w a0 (-> gsf-buf info ptr-verts))) + (nop!) + (l.w a3 at (generic-work-offset saves ptr-vtxs)) + (nop!) + (l.w t4 at (generic-work-offset saves ptr-clrs)) + (nop!) + (l.w a2 at (generic-work-offset saves ptr-texs)) + (nop!) + (add.i t2 r0 255) + (add.i a1 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (lui t3 -2) + (lui t0 #x3f80) + (ori t3 t3 #xfffe) + (m f0 t0) + (add.i t0 v1 3) + (sra t6 t0 2) + (l.q t0 at (generic-work-offset fx-buf work consts envmap colors)) + (sra t5 t6 2) + (and.i t6 t6 3) + (b.z t5 generic-envmap-only-proc-clear-tail :delay (nop!))) + (asm-block clear-four-loop + (label generic-envmap-only-proc-clear-four-loop) + ;; Environment colors default to the per-draw value. Fill four destinations at a time, then + ;; handle the remaining one to three vertices without overrunning the allocated stream. + (add.i t4 t4 64) + (s.q t0 t4 -64) + (nop!) + (s.q t0 t4 -48) + (add.i t5 t5 -1) + (s.q t0 t4 -32) + (b.gt t5 r0 generic-envmap-only-proc-clear-four-loop :delay (s.q t0 t4 -16))) + (asm-block clear-tail + (label generic-envmap-only-proc-clear-tail) + (b.z t6 generic-envmap-only-proc-prime-conversion :delay (add.i t5 t6 -1)) + (b.z t5 generic-envmap-only-proc-prime-conversion :delay (s.q t0 t4)) + (add.i t4 t4 16) + (add.i t5 t5 -1) + (b.z t5 generic-envmap-only-proc-prime-conversion :delay (s.q t0 t4)) + (add.i t4 t4 16) + (add.i t5 t5 -1) + (nop!) + (s.q t0 t4)) + (asm-block prime-conversion + (label generic-envmap-only-proc-prime-conversion) + ;; Load the camera matrix, environment constants, packed index masks, and four source records. + ;; The first block is primed here so the loop can overlap EE packing with the next VU0 call. + (add.i t0 v1 -4) + (l.vf vf31 at (generic-work-offset fx-buf work consts envmap consts)) + (pextlw v1 t3 t3) + (mmi-nop!) + (pextlw v1 v1 v1) + (mmi-nop!) + (pextlw a0 a0 a0) + (mmi-nop!) + (pextlw a0 a0 a0) + (mmi-nop!) + (pcpyh t3 t2) + (l.dr t2 t1) + (pcpyld t3 t3 t3) + (l.dl t2 t1 7) + (pcpyh a1 a1) + (mmi-nop!) + (pcpyld a1 a1 a1) + (mmi-nop!) + (add.i t1 t1 8) + (s.q t3 at (generic-work-offset saves envmap index-mask)) + (pextlh t2 r0 t2) + (mmi-nop!) + (and.q t3 t2 t3) + (mmi-nop!) + (sll.w t3 t3 5) + (mmi-nop!) + (add.w t7 t3 a0) + (mmi-nop!) + (srl32 t8 t7 0) + (l.s f4 t7 24) + (pcpyud t9 t7 r0) + (l.s f3 t8 24) + (srl32 ra t9 0) + (l.s f2 t9 24) + (and.q t3 t2 a1) + (l.s f1 ra 24) + (sra.w gp t3 8) + (l.q t3 t7 16) + (sub.s f4 f4 f0) + (nop!) + (div.s f4 f0 f4) + (l.q t4 t8 16) + (nop!) + (l.q t5 t9 16) + (nop!) + (l.q t6 ra 16) + (nop!) + (l.q t7 t7) + (nop!) + (l.q t8 t8) + (nop!) + (l.q t9 t9) + (nop!) + (l.q ra ra) + (mul.s f4 f4 f0) + (s.q t7 at (generic-work-offset saves envmap verts 0)) + (sub.s f3 f3 f0) + (nop!) + (div.s f3 f0 f3) + (s.q t8 at (generic-work-offset saves envmap verts 1)) + (nop!) + (s.q t9 at (generic-work-offset saves envmap verts 2)) + (nop!) + (s.q ra at (generic-work-offset saves envmap verts 3)) + (nop!) + (m s5 f4) + (nop!) + (s.q gp at (generic-work-offset saves envmap kicks 0)) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (sub.s f2 f2 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m gp f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (sub.s f1 f1 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw gp gp s5) + (nop!) + (nop!) + (nop!) + (nop!) + (m s5 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m s4 f1) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (pextlw s5 s4 s5) + (nop!) + (pcpyld gp s5 gp) + (nop!) + (nop!) + (m.ni vf21 t3) + (nop!) + (m.ni vf22 t4) + (nop!) + (m.ni vf23 t5) + (nop!) + (m.ni vf24 t6) + (nop!) + (m.ni vf9 t7) + (nop!) + (m.ni vf10 t8) + (nop!) + (m.ni vf11 t9) + (nop!) + (m.ni vf12 ra) + (nop!) + (m.ni vf27 gp) + (l.q t3 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (nop!) + (l.dr t2 t1) + (nop!) + (l.dl t2 t1 7) + (nop!) + (add.i t1 t1 8) + (pextlh t2 r0 t2) + (mmi-nop!) + (and.q t3 t2 t3) + (mmi-nop!) + (sll.w t3 t3 5) + (mmi-nop!) + (add.w t7 t3 a0) + (mmi-nop!) + (srl32 t8 t7 0) + (l.s f4 t7 24) + (pcpyud ra t7 r0) + (l.s f3 t8 24) + (srl32 gp ra 0) + (l.s f2 ra 24) + (and.q t3 t2 a1) + (l.s f1 gp 24) + (sra.w t9 t3 8) + (l.q t3 t7 16) + (sub.s f4 f4 f0) + (nop!) + (sub.s f3 f3 f0) + (nop!) + (sub.s f2 f2 f0) + (nop!) + (sub.s f1 f1 f0) + (nop!) + (div.s f4 f0 f4) + (l.q t4 t8 16) + (nop!) + (l.q t5 ra 16) + (nop!) + (l.q t6 gp 16) + (nop!) + (l.q t7 t7) + (nop!) + (l.q t8 t8) + (nop!) + (l.q ra ra) + (nop!) + (l.q gp gp) + (mul.s f4 f4 f0) + (s.q t7 at (generic-work-offset saves envmap verts 4)) + (div.s f3 f0 f3) + (s.q t8 at (generic-work-offset saves envmap verts 5)) + (nop!) + (s.q ra at (generic-work-offset saves envmap verts 6)) + (nop!) + (s.q gp at (generic-work-offset saves envmap verts 7)) + (nop!) + (m s5 f4) + (nop!) + (s.q t9 at (generic-work-offset saves envmap kicks 1)) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw t9 t9 s5) + (nop!) + (nop!) + (m s5 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m s4 f1) + (nop!) + (nop!) + (nop!) + (m.ni vf21 t3) + (nop!) + (m.ni vf9 t7) + (nop!) + (m.ni vf10 t8) + (nop!) + (m.ni vf11 ra) + (nop!) + (m.ni vf12 gp) + (pextlw t3 s4 s5) + (nop!) + (pextlw t3 t3 t9) + (nop!) + (nop!) + (m.ni vf22 t4) + (nop!) + (m.ni vf23 t5) + (nop!) + (m.ni vf24 t6) + (nop!) + (m.ni vf27 t3) + (l.q t3 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (nop!) + (l.dr t2 t1) + (nop!) + (l.dl t2 t1 7) + (nop!) + (add.i t1 t1 8) + (pextlh t2 r0 t2) + (mmi-nop!) + (and.q t3 t2 t3) + (mmi-nop!) + (sll.w t3 t3 5) + (mmi-nop!) + (add.w t5 t3 a0) + (mmi-nop!) + (srl32 t6 t5 0) + (l.s f4 t5 24) + (pcpyud t7 t5 r0) + (l.s f3 t6 24) + (srl32 t8 t7 0) + (l.s f2 t7 24) + (and.q t3 t2 a1) + (l.s f1 t8 24) + (sra.w t3 t3 8) + (l.q t4 t5 16) + (sub.s f4 f4 f0) + (nop!) + (sub.s f3 f3 f0) + (nop!) + (sub.s f2 f2 f0) + (nop!) + (sub.s f1 f1 f0) + (nop!) + (div.s f4 f0 f4) + (l.q t9 t6 16) + (nop!) + (l.q ra t7 16) + (nop!) + (l.q gp t8 16) + (nop!) + (l.q t5 t5) + (nop!) + (l.q t6 t6) + (nop!) + (l.q t7 t7) + (nop!) + (l.q t8 t8) + (mul.s f4 f4 f0) + (s.q t5 at (generic-work-offset saves envmap verts 8)) + (div.s f3 f0 f3) + (s.q t6 at (generic-work-offset saves envmap verts 9)) + (nop!) + (s.q t7 at (generic-work-offset saves envmap verts 10)) + (nop!) + (s.q t8 at (generic-work-offset saves envmap verts 11)) + (nop!) + (m s5 f4) + (nop!) + (s.q t3 at (generic-work-offset saves envmap kicks 2)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m s4 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (l.q t3 at (generic-work-offset saves envmap kicks 0)) + (pextlw s5 s4 s5) + (l.q s4 at (generic-work-offset saves envmap verts 1)) + (nop!) + (l.q s2 at (generic-work-offset saves envmap verts 3)) + (nop!) + (m s3 f2) + (nop!) + (m.ni vf21 t4) + (nop!) + (m.ni vf22 t9) + (nop!) + (m.ni vf23 ra) + (nop!) + (m.ni vf24 gp) + (nop!) + (l.q t4 at (generic-work-offset saves envmap verts 2)) + (nop!) + (m s1 f1) + (prot3w ra s2) + (l.q t9 at (generic-work-offset saves envmap verts 0)) + (prot3w gp s4) + (m.ni vf9 t5) + (pextlw t5 s1 s3) + (m.ni vf10 t6) + (pcpyld t5 t5 s5) + (m.ni vf11 t7) + (pextuw t7 gp t9) + (m.ni vf12 t8) + (pcpyld t6 t4 gp) + (m.ni vf27 t5) + (pcpyld s5 t7 t9) + (m.ni t9 vf17) + (pextuw t4 ra t4) + (m.ni t7 vf18) + (pcpyld ra ra t4) + (m.ni gp vf19) + (b.le t0 r0 generic-envmap-only-proc-finish-four-tail :delay (m.ni t8 vf20))) + (asm-block convert-four-loop + (label generic-envmap-only-proc-convert-four-loop) + ;; Entry 48 transforms four normals and eye vectors into reflected ST coordinates. While VU0 + ;; works, the EE writes the preceding group's position, color, texture, and strip metadata. + (l.q s4 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (add.i t0 t0 -4) + (l.dr t2 t1) + (add.i t4 a3 48) + (l.dl t2 t1 7) + (add.i a3 a2 16) + (add.i t5 t1 8) + (pextlh t2 r0 t2) + (s.q s5 t4 -48) + (and.q a2 t2 s4) + (s.q t6 t4 -32) + (sll.w a2 a2 5) + (s.q ra t4 -16) + (add.w s2 a2 a0) + (mmi-nop!) + (srl32 s3 s2 0) + (l.s f1 s2 24) + (pcpyud s4 s2 r0) + (l.s f4 s3 24) + (sub.s f3 f1 f0) + (nop!) + (srl32 s5 s4 0) + (l.s f2 s4 24) + (and.q a2 t2 a1) + (l.s f1 s5 24) + (sra.w a2 a2 8) + (l.q t1 s2 16) + (div.s f3 f0 f3) + (l.q t6 s3 16) + (sub.s f4 f4 f0) + (nop!) + (ppach s1 r0 t9) + (l.q t9 s4 16) + (ppach v0 r0 t7) + (l.q ra s5 16) + (ppach s0 r0 gp) + (l.q t7 s2) + (ppach s2 r0 t8) + (l.q t8 s3) + (pextlw s3 v0 s1) + (l.q gp s4) + (pextlw s4 s2 s0) + (l.q s5 s5) + (mul.s f5 f3 f0) + (s.q t7 at (generic-work-offset saves envmap verts 0)) + (div.s f3 f0 f4) + (s.q t8 at (generic-work-offset saves envmap verts 1)) + (pcpyld s4 s4 s3) + (s.q gp at (generic-work-offset saves envmap verts 2)) + (and.q s4 s4 v1) + (s.q s5 at (generic-work-offset saves envmap verts 3)) + (or.q s4 s4 t3) + (m t3 f5) + (sub.s f2 f2 f0) + (s.q a2 at (generic-work-offset saves envmap kicks 0)) + (nop!) + (s.q s4 a3 -16) + (sub.s f1 f1 f0) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (nop!) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (m a2 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (pextlw t3 a2 t3) + (l.q a2 at (generic-work-offset saves envmap kicks 1)) + (nop!) + (l.q s4 at (generic-work-offset saves envmap verts 5)) + (div.s f1 f0 f1) + (l.q s2 at (generic-work-offset saves envmap verts 7)) + (nop!) + (m s3 f2) + (nop!) + (m.ni vf21 t1) + (nop!) + (m.ni vf22 t6) + (nop!) + (m.ni vf23 t9) + (nop!) + (m.ni vf24 ra) + (nop!) + (l.q ra at (generic-work-offset saves envmap verts 6)) + (nop!) + (l.q t9 at (generic-work-offset saves envmap verts 4)) + (prot3w t1 s2) + (m s2 f1) + (prot3w t6 s4) + (m.ni vf9 t7) + (pextlw t7 s2 s3) + (m.ni vf10 t8) + (pcpyld t3 t7 t3) + (m.ni vf11 gp) + (pextuw t7 t6 t9) + (m.ni vf12 s5) + (pcpyld t6 ra t6) + (m.ni vf27 t3) + (pcpyld gp t7 t9) + (m.ni t9 vf17) + (pextuw t3 t1 ra) + (m.ni t8 vf18) + (pcpyld ra t1 t3) + (m.ni t7 vf19) + (b.le t0 r0 generic-envmap-only-proc-finish-three-tail :delay (m.ni t3 vf20)) + (l.q s5 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (add.i t0 t0 -4) + (l.dr t2 t5) + (add.i t4 t4 48) + (l.dl t2 t5 7) + (add.i t1 a3 16) + (add.i t5 t5 8) + (pextlh t2 r0 t2) + (s.q gp t4 -48) + (and.q a3 t2 s5) + (s.q t6 t4 -32) + (sll.w a3 a3 5) + (s.q ra t4 -16) + (add.w s2 a3 a0) + (mmi-nop!) + (srl32 s3 s2 0) + (l.s f1 s2 24) + (pcpyud gp s2 r0) + (l.s f4 s3 24) + (sub.s f3 f1 f0) + (nop!) + (srl32 s5 gp 0) + (l.s f2 gp 24) + (and.q a3 t2 a1) + (l.s f1 s5 24) + (sra.w s4 a3 8) + (l.q a3 s2 16) + (div.s f3 f0 f3) + (l.q t6 s3 16) + (sub.s f4 f4 f0) + (nop!) + (ppach s1 r0 t9) + (l.q t9 gp 16) + (ppach v0 r0 t8) + (l.q ra s5 16) + (ppach s0 r0 t7) + (l.q t7 s2) + (ppach s2 r0 t3) + (l.q t8 s3) + (pextlw t3 v0 s1) + (l.q gp gp) + (pextlw s3 s2 s0) + (l.q s5 s5) + (mul.s f5 f3 f0) + (s.q t7 at (generic-work-offset saves envmap verts 4)) + (div.s f3 f0 f4) + (s.q t8 at (generic-work-offset saves envmap verts 5)) + (pcpyld t3 s3 t3) + (s.q gp at (generic-work-offset saves envmap verts 6)) + (and.q t3 t3 v1) + (s.q s5 at (generic-work-offset saves envmap verts 7)) + (or.q t3 t3 a2) + (m a2 f5) + (sub.s f2 f2 f0) + (s.q s4 at (generic-work-offset saves envmap kicks 1)) + (nop!) + (s.q t3 t1 -16) + (sub.s f1 f1 f0) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (nop!) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (m t3 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (pextlw a2 t3 a2) + (l.q t3 at (generic-work-offset saves envmap kicks 2)) + (nop!) + (l.q s4 at (generic-work-offset saves envmap verts 9)) + (div.s f1 f0 f1) + (l.q s2 at (generic-work-offset saves envmap verts 11)) + (nop!) + (m s3 f2) + (nop!) + (m.ni vf21 a3) + (nop!) + (m.ni vf22 t6) + (nop!) + (m.ni vf23 t9) + (nop!) + (m.ni vf24 ra) + (nop!) + (l.q t6 at (generic-work-offset saves envmap verts 10)) + (nop!) + (l.q t9 at (generic-work-offset saves envmap verts 8)) + (prot3w a3 s2) + (m s2 f1) + (prot3w ra s4) + (m.ni vf9 t7) + (pextlw t7 s2 s3) + (m.ni vf10 t8) + (pcpyld a2 t7 a2) + (m.ni vf11 gp) + (pextuw t7 ra t9) + (m.ni vf12 s5) + (pcpyld ra t6 ra) + (m.ni vf27 a2) + (pcpyld s5 t7 t9) + (m.ni t8 vf17) + (pextuw a2 a3 t6) + (m.ni t9 vf18) + (pcpyld gp a3 a2) + (m.ni t6 vf19) + (b.le t0 r0 generic-envmap-only-proc-finish-two-tail :delay (m.ni t7 vf20)) + (l.q s4 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (add.i t0 t0 -4) + (l.dr t2 t5) + (add.i a3 t4 48) + (l.dl t2 t5 7) + (add.i a2 t1 16) + (add.i t1 t5 8) + (pextlh t2 r0 t2) + (s.q s5 a3 -48) + (and.q t4 t2 s4) + (s.q ra a3 -32) + (sll.w t4 t4 5) + (s.q gp a3 -16) + (add.w s3 t4 a0) + (mmi-nop!) + (srl32 s4 s3 0) + (l.s f1 s3 24) + (pcpyud ra s3 r0) + (l.s f4 s4 24) + (sub.s f3 f1 f0) + (nop!) + (srl32 gp ra 0) + (l.s f2 ra 24) + (and.q t4 t2 a1) + (l.s f1 gp 24) + (sra.w s5 t4 8) + (l.q t4 s3 16) + (div.s f3 f0 f3) + (l.q t5 s4 16) + (sub.s f4 f4 f0) + (nop!) + (ppach s2 r0 t8) + (l.q t8 ra 16) + (ppach s0 r0 t9) + (l.q t9 gp 16) + (ppach s1 r0 t6) + (l.q t6 s3) + (ppach s3 r0 t7) + (l.q t7 s4) + (pextlw s4 s0 s2) + (l.q ra ra) + (pextlw s3 s3 s1) + (l.q gp gp) + (mul.s f5 f3 f0) + (s.q t6 at (generic-work-offset saves envmap verts 8)) + (div.s f3 f0 f4) + (s.q t7 at (generic-work-offset saves envmap verts 9)) + (pcpyld s4 s3 s4) + (s.q ra at (generic-work-offset saves envmap verts 10)) + (and.q s4 s4 v1) + (s.q gp at (generic-work-offset saves envmap verts 11)) + (or.q s4 s4 t3) + (m t3 f5) + (sub.s f2 f2 f0) + (s.q s5 at (generic-work-offset saves envmap kicks 2)) + (nop!) + (s.q s4 a2 -16) + (sub.s f1 f1 f0) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (nop!) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (m s5 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (pextlw s5 s5 t3) + (l.q t3 at (generic-work-offset saves envmap kicks 0)) + (nop!) + (l.q s4 at (generic-work-offset saves envmap verts 1)) + (div.s f1 f0 f1) + (l.q s2 at (generic-work-offset saves envmap verts 3)) + (nop!) + (m s3 f2) + (nop!) + (m.ni vf21 t4) + (nop!) + (m.ni vf22 t5) + (nop!) + (m.ni vf23 t8) + (nop!) + (m.ni vf24 t9) + (nop!) + (l.q t5 at (generic-work-offset saves envmap verts 2)) + (nop!) + (l.q t8 at (generic-work-offset saves envmap verts 0)) + (prot3w t4 s2) + (m s2 f1) + (prot3w t9 s4) + (m.ni vf9 t6) + (pextlw t6 s2 s3) + (m.ni vf10 t7) + (pcpyld t7 t6 s5) + (m.ni vf11 ra) + (pextuw ra t9 t8) + (m.ni vf12 gp) + (pcpyld t6 t5 t9) + (m.ni vf27 t7) + (pcpyld s5 ra t8) + (m.ni t9 vf17) + (pextuw t5 t4 t5) + (m.ni t7 vf18) + (pcpyld ra t4 t5) + (m.ni gp vf19) + (b.gt t0 r0 generic-envmap-only-proc-convert-four-loop :delay (m.ni t8 vf20))) + (asm-block finish-four-tail + (label generic-envmap-only-proc-finish-four-tail) + ;; Complete the group already returned by VU0. The three exits below select the valid lanes of + ;; the partially filled group and retain the same packed stream boundaries as a full group. + (add.i a1 a3 48) + (s.q s5 a1 -48) + (add.i a0 a2 16) + (s.q t6 a1 -32) + (ppach a2 r0 t9) + (s.q ra a1 -16) + (ppach a1 r0 t7) + (mmi-nop!) + (ppach a3 r0 gp) + (mmi-nop!) + (ppach t0 r0 t8) + (mmi-nop!) + (pextlw a1 a1 a2) + (mmi-nop!) + (pextlw a2 t0 a3) + (mmi-nop!) + (pcpyld a1 a2 a1) + (mmi-nop!) + (and.q v1 a1 v1) + (mmi-nop!) + (or.q v1 v1 t3) + (mmi-nop!) + (nop!) + (mmi-nop!) + (b generic-envmap-only-proc-finish :delay (s.q v1 a0 -16))) + (asm-block finish-three-tail + (label generic-envmap-only-proc-finish-three-tail) + (add.i a1 t4 48) + (s.q gp a1 -48) + (add.i a0 a3 16) + (s.q t6 a1 -32) + (ppach a3 r0 t9) + (s.q ra a1 -16) + (ppach a1 r0 t8) + (mmi-nop!) + (ppach t0 r0 t7) + (mmi-nop!) + (ppach t1 r0 t3) + (mmi-nop!) + (pextlw a1 a1 a3) + (mmi-nop!) + (pextlw a3 t1 t0) + (mmi-nop!) + (pcpyld a1 a3 a1) + (mmi-nop!) + (and.q v1 a1 v1) + (mmi-nop!) + (or.q v1 v1 a2) + (mmi-nop!) + (nop!) + (mmi-nop!) + (b generic-envmap-only-proc-finish :delay (s.q v1 a0 -16))) + (asm-block finish-two-tail + (label generic-envmap-only-proc-finish-two-tail) + (add.i a1 t4 48) + (s.q s5 a1 -48) + (add.i a0 t1 16) + (s.q ra a1 -32) + (ppach a2 r0 t8) + (s.q gp a1 -16) + (ppach a1 r0 t9) + (mmi-nop!) + (ppach a3 r0 t6) + (mmi-nop!) + (ppach t0 r0 t7) + (mmi-nop!) + (pextlw a1 a1 a2) + (mmi-nop!) + (pextlw a2 t0 a3) + (mmi-nop!) + (pcpyld a1 a2 a1) + (mmi-nop!) + (and.q v1 a1 v1) + (mmi-nop!) + (or.q v1 v1 t3) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (s.q v1 a0 -16)) + (asm-block finish + (label generic-envmap-only-proc-finish) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 128)) + (nop!) + (nop!)) + ) + + ;; The same wrapper with the lighting processor swapped for the unlit one, so a model's authored + ;; vertex colors survive to the GS untouched. + (defun generic-no-light ((gsf-buf gsf-buffer) (shaders pointer)) + "Build one Generic pass without lighting using shaders, then start the scratchpad-to-memory DMA." + (declare (asm-func none)) + ;; Install the GSF and shader pointers, prepare one packet, expand source colors without running VU + ;; lighting, and submit the result through the scratchpad DMA channel. + (asm-block build-vertex-packet + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-no-light-entry) + (add.i sp sp -16) + (s.d ra at (generic-work-offset fx-buf work storage data 0)) + (lui at #x7000) + (s.w a1 at (generic-work-offset saves ptr-shaders)) + (s.w a0 at (generic-work-offset saves gsf-buf)) + (s.w r0 at (generic-work-offset saves is-envmap)) + (m! t9 generic-prepare-dma-single) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 generic-no-light-proc) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (lui at #x7000) + (lui from-spr #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori from-spr from-spr #xd000) + (l.w t1 (-> from-spr chcr)) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 generic-no-light-start-dma :delay (nop!)) + (m t1 from-spr) + (nop!))) + ;; The previous packet may still be on its way out of scratchpad. Every busy poll is charged to + ;; saves.from-spr-waits so a frame bound on scratchpad bandwidth says so. + (asm-block wait-for-dma + (label generic-no-light-wait-for-dma) + (l.w t2 t0) + (nop!) + (l.w t3 t1) + (nop!) + (and.i t3 t3 DMA-CHCR-STR) + (add.i t2 t2 1) + (b.nz t3 generic-no-light-wait-for-dma :delay (s.w t2 t0)) + (m t0 r0)) + ;; Program the channel, advance saves.basep by exactly what it will deliver, and switch to the + ;; other output buffer. Nothing waits for this transfer - the wait above is what makes that safe. + (asm-block start-dma + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-no-light-start-dma) + (sll t0 a0 4) + (s.w a3 (-> from-spr sadr)) + (nop!) + (s.w a1 (-> from-spr madr)) + (add.i a3 r0 DMA-CHCR-STR) + (s.w a0 (-> from-spr qwc)) + (add a0 a1 t0) + (s.w a3 (-> from-spr chcr)) + (nop!) + (s.w a0 at (generic-work-offset saves basep)) + (m a0 r0) + (xor.i v1 v1 GENERIC-OUTBUF-FLIP) + (s.w v1 at (generic-work-offset saves cur-outbuf)) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage data 0)) + (jr ra :delay (add.i sp sp 16)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!))) + ) + + ;; And the two-pass version: the double builder reserves a second set of stream cursors and a second + ;; packet header, the envmap processor fills them with reflected coordinates and the environment + ;; tint, and the GS lays that pass over the unlit one. This is the shape generic-TIE uses, spelled + ;; out in one place. + (defun generic-no-light+envmap ((gsf-buf gsf-buffer) (shaders pointer)) + "Build both ordinary and environment-map Generic passes without lighting, apply interpolation and + delta attributes using shaders, then start the scratchpad-to-memory DMA." + (declare (asm-func none)) + ;; Prepare the paired base and environment-map packets. The base pass keeps source lighting, while + ;; interpolation, deltas, and reflected texture coordinates are added before the shared scratch output + ;; is submitted. + (asm-block build-two-pass-packet + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-no-light+envmap-entry) + (add.i sp sp -16) + (s.d ra at (generic-work-offset fx-buf work storage data 0)) + (lui at #x7000) + (s.w a1 at (generic-work-offset saves ptr-shaders)) + (s.w a0 at (generic-work-offset saves gsf-buf)) + (m! t9 generic-prepare-dma-double) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 generic-envmap-dproc) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 generic-interp-dproc) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 generic-no-light-dproc) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (nop!) + (lui at #x7000) + (lui from-spr #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori from-spr from-spr #xd000) + (l.w t1 (-> from-spr chcr)) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 generic-no-light+envmap-start-dma :delay (nop!)) + (m t1 from-spr) + (nop!))) + ;; The previous packet may still be on its way out of scratchpad. Every busy poll is charged to + ;; saves.from-spr-waits so a frame bound on scratchpad bandwidth says so. + (asm-block wait-for-dma + (label generic-no-light+envmap-wait-for-dma) + (l.w t2 t0) + (nop!) + (l.w t3 t1) + (nop!) + (and.i t3 t3 DMA-CHCR-STR) + (add.i t2 t2 1) + (b.nz t3 generic-no-light+envmap-wait-for-dma :delay (s.w t2 t0)) + (m t0 r0)) + ;; Program the channel, advance saves.basep by exactly what it will deliver, and switch to the + ;; other output buffer. Nothing waits for this transfer - the wait above is what makes that safe. + (asm-block start-dma + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-no-light+envmap-start-dma) + (sll t0 a0 4) + (s.w a3 (-> from-spr sadr)) + (nop!) + (s.w a1 (-> from-spr madr)) + (add.i a3 r0 DMA-CHCR-STR) + (s.w a0 (-> from-spr qwc)) + (add a0 a1 t0) + (s.w a3 (-> from-spr chcr)) + (nop!) + (s.w a0 at (generic-work-offset saves basep)) + (m a0 r0) + (xor.i v1 v1 GENERIC-OUTBUF-FLIP) + (s.w v1 at (generic-work-offset saves cur-outbuf)) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage data 0)) + (jr ra :delay (add.i sp sp 16)) + (nop!) + (nop!))) + ) + + (defun generic-no-light-dproc () + "Expand GSF vertices without lighting while applying their delta color and texture attributes." + (declare (asm-func none) (allow-saved-regs)) + ;; The unlit processor the shipped game installs, and the one that fans generic-TIE's whole packet out + ;; to draw points. It emits five streams from each vertex record, and they are five separate streams + ;; rather than anything combined: + ;; + ;; ptr-vtxs the position, three quadwords per four vertices + ;; ptr-clrs clr, the vertex's own color + ;; ptr-texs tex plus consts.texture-offset, with the kick flag in bit 0 of S + ;; ptr-env-clrs dclr, which generic-envmap-dproc filled with the environment tint + ;; ptr-env-texs dtex, the reflected coordinate, likewise, with the kick flag added + ;; + ;; Normals are not copied - nothing downstream wants them, which is also what lets envmap-dproc + ;; overwrite two of their lanes. So this is not "no lighting" in the sense of no shading: it is the + ;; processor that publishes whatever the passes before it deposited in the vertex records, which for + ;; TIE means the time-of-day palette color, the subdivision blend, and the reflection. + ;; + ;; If it were wrong the symptom would be right geometry with wrong colors - flat, or carrying the + ;; previous object's palette, or a reflection that does not line up with the base pass. + (asm-block load-stream-cursors + (label generic-no-light-dproc-entry) + (add.i sp sp -128) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (lui at #x7000) + (nop!) + (rlet ((gsf-buf :reg a1 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a0 at (generic-work-offset saves num-dps)) + (nop!) + (l.w v1 (-> gsf-buf info ptr-iks)) + (nop!) + (l.w a2 (-> gsf-buf info ptr-verts))) + (add.i a0 a0 3) + (nop!) + (sra a0 a0 2) + (nop!) + (sll a0 a0 3) + (add.i a3 r0 255) + (lui a1 -2) + (add.i t1 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (ori a1 a1 #xfffe) + (add a0 v1 a0) + (pextlw a1 a1 a1) + (l.w t2 at (generic-work-offset saves ptr-vtxs)) + (pextlw a1 a1 a1) + (l.w t3 at (generic-work-offset saves ptr-clrs)) + (pextlw a2 a2 a2) + (l.w t5 at (generic-work-offset saves ptr-texs)) + (pextlw a2 a2 a2) + (l.w t4 at (generic-work-offset saves ptr-env-clrs)) + (pcpyh a3 a3) + (l.w t6 at (generic-work-offset saves ptr-env-texs)) + (pcpyld a3 a3 a3) + (l.q t0 at (generic-work-offset fx-buf work consts texture-offset)) + (pcpyh t1 t1) + (l.d t7 v1) + (pcpyld t1 t1 t1) + (mmi-nop!) + (pextlh t8 r0 t7) + (mmi-nop!) + (and.q t7 t8 a3) + (mmi-nop!) + (sll.w t7 t7 5) + (mmi-nop!) + (add.i t2 t2 -48) + (add.i t3 t3 -16) + (add.i t4 t4 -16) + (add.i t5 t5 -16) + (b generic-no-light-dproc-prime-vertex-loop :delay (add.i t6 t6 -16)) + (nop!)) + (asm-block vertex-loop + (label generic-no-light-dproc-vertex-loop) + ;; Store the preceding group while decoding the next four packed vertex references. + (pextlh t8 r0 gp) + (s.q t7 t2) + (and.q t7 t8 a3) + (s.q t9 t2 16) + (sll.w t7 t7 5) + (s.q ra t2 32)) + ;; The loop is rotated: this is the body, entered directly from the setup above, and the short block + ;; before it stores the group this pass gathered. So the stores at the top of a pass belong to the + ;; previous four vertices, and the block above is reached once more than this one. + (asm-block prime-vertex-loop + (label generic-no-light-dproc-prime-vertex-loop) + ;; Gather source position, normal, color, and texture data, then add the signed delta fields + ;; carried in the indexed stream before writing the expanded records. + (add.w s3 t7 a2) + (mmi-nop!) + (srl32 s2 s3 0) + (add.i t2 t2 48) + (pcpyud s5 s3 r0) + (l.q t7 s3) + (srl32 s4 s5 0) + (add.i t3 t3 16) + (and.q t8 t8 t1) + (l.q t9 s2) + (sra.w gp t8 8) + (l.q t8 s5) + (pextuw s1 t9 t7) + (l.q ra s4) + (add.i t5 t5 16) + (add.i v1 v1 8) + (add.i t4 t4 16) + (add.i t6 t6 16) + (pextuw s0 ra t8) + (l.q s3 s3 16) + (pcpyud s1 s1 s0) + (l.q s2 s2 16) + (add.h s0 s1 t0) + (l.q s1 s5 16) + (and.q s5 s0 a1) + (l.q s0 s4 16) + (pextlw s4 s2 s3) + (mmi-nop!) + (pextuw s3 s2 s3) + (mmi-nop!) + (pextlw s2 s0 s1) + (mmi-nop!) + (pextuw s0 s0 s1) + (mmi-nop!) + (pcpyld s1 s2 s4) + (mmi-nop!) + (pcpyud s4 s4 s2) + (mmi-nop!) + (pcpyud s3 s3 s0) + (s.q s4 t4) + (and.q s4 s1 a1) + (s.q s3 t3) + (or.q s4 s4 gp) + (mmi-nop!) + (or.q gp s5 gp) + (s.q s4 t6) + (prot3w ra ra) + (s.q gp t5) + (prot3w t9 t9) + (mmi-nop!) + (pextuw s5 t9 t7) + (mmi-nop!) + (pcpyld t9 t8 t9) + (l.d gp v1) + (pcpyld t7 s5 t7) + (mmi-nop!) + (pextuw t8 ra t8) + (mmi-nop!) + (b.ne v1 a0 generic-no-light-dproc-vertex-loop :delay (pcpyld ra ra t8)) + (nop!) + (s.q t7 t2) + (nop!) + (s.q t9 t2 16) + (nop!) + (s.q ra t2 32) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 128)) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-no-light-dproc-only () + "Apply delta color and texture data to expanded GSF vertices without producing the ordinary position + and normal streams." + (declare (asm-func none) (allow-saved-regs)) + ;; The same color and coordinate work with the position and normal copies left out, and reading + ;; the *environment* stream cursors rather than the base ones. It is the second pass of a two-pass + ;; draw: the geometry has already been written by the first pass and only needs a second set of + ;; attributes laid over it, so copying the positions again would be pure cost. + ;; + ;; Nothing installs it in Jak 1. Where the same job is needed, generic-envmap-dproc does it and + ;; computes the coordinates as well. + (asm-block load-stream-cursors + (label generic-no-light-dproc-only-entry) + (add.i sp sp -32) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 1)) + (lui at #x7000) + (rlet ((gsf-buf :reg a0 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a1 at (generic-work-offset saves num-dps)) + (nop!) + (l.w v1 (-> gsf-buf info ptr-iks)) + (add.i a1 a1 3) + (l.w a2 (-> gsf-buf info ptr-verts))) + (nop!) + (sra a0 a1 2) + (sll a0 a0 3) + (add.i a3 r0 255) + (lui a1 -2) + (add.i t1 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (ori a1 a1 #xfffe) + (add a0 v1 a0) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a2 a2 a2) + (mmi-nop!) + (pextlw a2 a2 a2) + (l.w t2 at (generic-work-offset saves ptr-env-clrs)) + (pcpyh a3 a3) + (l.w t3 at (generic-work-offset saves ptr-env-texs)) + (pcpyld a3 a3 a3) + (l.q t0 at (generic-work-offset fx-buf work consts texture-offset)) + (pcpyh t1 t1) + (l.d t4 v1) + (pcpyld t1 t1 t1) + (mmi-nop!) + (pextlh t5 r0 t4) + (mmi-nop!) + (and.q t4 t5 a3) + (mmi-nop!) + (sll.w t4 t4 5) + (mmi-nop!) + (nop!) + (add.i t2 t2 -16) + (b generic-no-light-dproc-only-prime-vertex-loop :delay (add.i t3 t3 -16))) + (asm-block vertex-loop + (label generic-no-light-dproc-only-vertex-loop) + ;; This shortened path drains only the delta color and texture pair from the preceding group. + (pextlh t5 r0 t5) + (s.d t7 t2) + (and.q t6 t5 a3) + (s.d t4 t2 8) + (sll.w t4 t6 5) + (mmi-nop!)) + ;; The loop is rotated: this is the body, entered directly from the setup above, and the short block + ;; before it stores the group this pass gathered. So the stores at the top of a pass belong to the + ;; previous four vertices, and the block above is reached once more than this one. + (asm-block prime-vertex-loop + (label generic-no-light-dproc-only-prime-vertex-loop) + ;; Resolve four source records and merge their delta fields; position and normal cursors are + ;; deliberately untouched. + (add.w t8 t4 a2) + (mmi-nop!) + (srl32 t7 t8 0) + (mmi-nop!) + (pcpyud t6 t8 r0) + (l.wu t9 t8 16) + (srl32 t4 t6 0) + (add.i t2 t2 16) + (and.q t5 t5 t1) + (l.wu gp t7 16) + (sra.w t5 t5 8) + (l.wu ra t6 16) + (pextlw t9 gp t9) + (l.wu gp t4 16) + (add.i t3 t3 16) + (add.i v1 v1 8) + (pextlw ra gp ra) + (l.wu t8 t8 20) + (pcpyld t9 ra t9) + (l.wu t7 t7 20) + (add.h t9 t9 t0) + (l.wu t6 t6 20) + (and.q t9 t9 a1) + (l.wu t4 t4 20) + (or.q t9 t9 t5) + (l.d t5 v1) + (pextlw t7 t7 t8) + (s.q t9 t3) + (b.ne v1 a0 generic-no-light-dproc-only-vertex-loop :delay (pextlw t4 t4 t6)) + (nop!) + (s.d t7 t2) + (nop!) + (s.d t4 t2 8) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 32)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-no-light-proc () + "Expand indexed GSF vertices without lighting, preserving source colors while reconstructing the + position, normal, and texture streams." + (declare (asm-func none) (allow-saved-regs)) + ;; Fills all three streams and passes the authored colors through untouched - no lighting, and no + ;; draw-point attribute lanes either. The difference from generic-no-light-dproc is exactly that: + ;; this one ignores dclr and dtex, so a model drawn with it looks the same whatever the earlier + ;; passes computed. + ;; + ;; Nothing installs it. It is the base case the two dproc variants are built from. + (asm-block load-stream-cursors + (label generic-no-light-proc-entry) + (add.i sp sp -96) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 5)) + (lui at #x7000) + (nop!) + (nop!) + (rlet ((gsf-buf :reg a0 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a1 at (generic-work-offset saves num-dps)) + (nop!) + (l.w v1 (-> gsf-buf info ptr-iks)) + (add.i a1 a1 3) + (l.w a2 (-> gsf-buf info ptr-verts))) + (sra a0 a1 2) + (nop!) + (sll a0 a0 3) + (add.i a3 r0 255) + (lui a1 -2) + (add.i t1 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (ori a1 a1 #xfffe) + (add a0 v1 a0) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a2 a2 a2) + (l.w t2 at (generic-work-offset saves ptr-vtxs)) + (pextlw a2 a2 a2) + (l.w t3 at (generic-work-offset saves ptr-clrs)) + (pcpyh a3 a3) + (l.w t4 at (generic-work-offset saves ptr-texs)) + (pcpyld a3 a3 a3) + (l.q t0 at (generic-work-offset fx-buf work consts texture-offset)) + (pcpyh t1 t1) + (l.d t5 v1) + (pcpyld t1 t1 t1) + (mmi-nop!) + (pextlh t6 r0 t5) + (mmi-nop!) + (and.q t5 t6 a3) + (mmi-nop!) + (sll.w t5 t5 5) + (mmi-nop!) + (add.i t2 t2 -48) + (add.i t3 t3 -16) + (b generic-no-light-proc-prime-vertex-loop :delay (add.i t4 t4 -16))) + (asm-block vertex-loop + (label generic-no-light-proc-vertex-loop) + ;; Store the previous four expanded vertices as the next group is gathered. + (pextlh t6 r0 ra) + (s.q t5 t2) + (and.q t5 t6 a3) + (s.q t7 t2 16) + (sll.w t5 t5 5) + (s.q t8 t2 32)) + ;; The loop is rotated: this is the body, entered directly from the setup above, and the short block + ;; before it stores the group this pass gathered. So the stores at the top of a pass belong to the + ;; previous four vertices, and the block above is reached once more than this one. + (asm-block prime-vertex-loop + (label generic-no-light-proc-prime-vertex-loop) + ;; Strip the packed index/control bits, gather four source records, preserve their colors, and + ;; rebuild the 48-byte position/normal stream plus the 16-byte texture stream. + (add.w gp t5 a2) + (mmi-nop!) + (srl32 s4 gp 0) + (add.i t2 t2 48) + (pcpyud s5 gp r0) + (l.q t5 gp) + (srl32 t8 s5 0) + (add.i t3 t3 16) + (and.q t6 t6 t1) + (l.q t7 s4) + (sra.w ra t6 8) + (l.q t6 s5) + (pextuw s3 t7 t5) + (l.q t9 t8) + (add.i t4 t4 16) + (add.i v1 v1 8) + (pextuw s2 t9 t6) + (l.w gp gp 28) + (pcpyud s3 s3 s2) + (l.w s4 s4 28) + (add.h s3 s3 t0) + (l.w s5 s5 28) + (and.q s3 s3 a1) + (l.w t8 t8 28) + (or.q ra s3 ra) + (s.w gp t3) + (prot3w t9 t9) + (s.q ra t4) + (prot3w t7 t7) + (s.w s4 t3 4) + (pextuw gp t7 t5) + (s.w s5 t3 8) + (pcpyld t7 t6 t7) + (l.d ra v1) + (pcpyld t5 gp t5) + (mmi-nop!) + (pextuw t6 t9 t6) + (s.w t8 t3 12) + (b.ne v1 a0 generic-no-light-proc-vertex-loop :delay (pcpyld t8 t9 t6)) + (nop!) + (s.q t5 t2) + (nop!) + (s.q t7 t2 16) + (nop!) + (s.q t8 t2 32) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 96)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-interp-dproc () + "Apply the pending Generic interpolation job to a range of expanded vertex delta attributes." + (declare (asm-func none)) + ;; The level-of-detail blend for the *environment coordinate*, and the only processor driven by a job + ;; record rather than by the whole vertex list. saves.ptr-interp-job names a range - num vertices + ;; starting at first - and a second source; for each vertex in that range the dtex that + ;; generic-envmap-dproc computed becomes its own weighted by morph-z plus the source's weighted by + ;; morph-w. Only byte 16 of each record is read and written; colors are not touched here, because + ;; TIE's color blend was already done by the converter when it looked the palette up. + ;; + ;; Eight halfword lanes is four (S, T) pairs, so one pmulth and one pmaddh cover four vertices at a + ;; time. The two weights sum to 256 rather than to 1.0, so what follows them is a shift, not a divide. + ;; + ;; Three early exits, all normal rather than error paths: no job at all, a job-type this build does + ;; not implement, or a weight already at zero because the object sits at the near edge of its LOD band + ;; and nothing is collapsing. Without this pass a subdividing TIE surface would pop its reflection + ;; every time it crossed an LOD boundary - the geometry eases across, and the reflection has to ease + ;; with it. + (asm-block read-interpolation-job + (label generic-interp-dproc-entry) + (lui at #x7000) + (nop!) + (l.w v1 at (generic-work-offset saves ptr-interp-job)) + (nop!) + (rlet ((gsf-buf :reg a0 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (b.z v1 generic-interp-dproc-finish :delay (nop!)) + (nop!) + (l.w t0 v1 8) + (nop!) + (l.w a2 (-> gsf-buf info ptr-verts))) + (nop!) + (l.h a0 v1) + (nop!) + (l.h a1 v1 2) + (b.nz a0 generic-interp-dproc-finish :delay (l.h a0 v1 4)) + (sll t1 a0 5) + (l.h a0 v1 12) + (add.i a3 a1 7) + (l.h a1 v1 14) + (b.z a1 generic-interp-dproc-finish :delay (add v1 t1 a2)) + (pextlh a0 a0 a0) + (mmi-nop!) + (pextlw a0 a0 a0) + (mmi-nop!) + (pcpyld a0 a0 a0) + (mmi-nop!) + (pextlh a1 a1 a1) + (mmi-nop!) + (pextlw a1 a1 a1) + (mmi-nop!) + (pcpyld a1 a1 a1) + (mmi-nop!) + (pextlw a2 a2 a2) + (mmi-nop!) + (pcpyld a2 a2 a2) + (mmi-nop!) + (sra a3 a3 3) + (nop!) + (sll a3 a3 4) + (l.d t1 t0) + (add a3 t0 a3) + (add.i t0 t0 8) + (pextlb t1 r0 t1) + (mmi-nop!) + (sll.h t2 t1 5) + (mmi-nop!) + (pextuh t1 r0 t2) + (mmi-nop!) + (pextlh t2 r0 t2) + (mmi-nop!) + (add.w t2 t2 a2) + (mmi-nop!) + (b generic-interp-dproc-prime-vertex-loop :delay (pcpyud t5 t2 r0))) + (asm-block vertex-loop + (label generic-interp-dproc-vertex-loop) + ;; Four records are in flight. Rotate the packed products, write one word into each 32-byte + ;; attribute record, and advance to the next eight source indices. + (srl32 t5 t6 0) + (srl32 t4 t3 0) + (pextuh t1 r0 t2) + (s.w t6 v1 16) + (pextlh t2 r0 t2) + (s.w t5 v1 48) + (add.w t2 t2 a2) + (s.w t3 v1 80) + (pcpyud t5 t2 r0) + (s.w t4 v1 112) + (add.i t0 t0 8) + (add.i v1 v1 128)) + ;; The loop is rotated: this is the body, entered directly from the setup above, and the short block + ;; before it stores the group this pass gathered. So the stores at the top of a pass belong to the + ;; previous four vertices, and the block above is reached once more than this one. + (asm-block prime-vertex-loop + (label generic-interp-dproc-prime-vertex-loop) + ;; Multiply the two signed source attributes by the complementary morph weights, add the + ;; products through the multimedia accumulator, and gather the four low words for storage. + (add.w t1 t1 a2) + (l.wu t3 t2 16) + (pcpyud t6 t1 r0) + (l.wu t4 t5 16) + (srl32 t8 t2 0) + (l.wu t2 t1 16) + (srl32 t9 t5 0) + (l.wu t5 t6 16) + (srl32 t7 t1 0) + (l.wu t1 t8 16) + (srl32 t8 t6 0) + (l.wu t6 t9 16) + (pextlw t4 t4 t3) + (l.wu t3 t7 16) + (pextlw t2 t5 t2) + (l.wu t5 t8 16) + (pcpyld t2 t2 t4) + (l.wu t4 v1 16) + (pextlw t6 t6 t1) + (l.wu t1 v1 48) + (pextlw t3 t5 t3) + (mmi-nop!) + (pcpyld t3 t3 t6) + (l.wu t5 v1 80) + (pextlw t1 t1 t4) + (l.wu t4 v1 112) + (pmulth r0 t2 a1) + (mmi-nop!) + (pextlw t2 t4 t5) + (mmi-nop!) + (pmaddh r0 t3 a1) + (mmi-nop!) + (pcpyld t1 t2 t1) + (l.d t2 t0) + (pmaddh r0 t1 a0) + (mmi-nop!) + (pextlb t1 r0 t2) + (mmi-nop!) + (sll.h t2 t1 5) + (mmi-nop!) + (pmfhl.lw t3) + (mmi-nop!) + (pmfhl.uw t1) + (mmi-nop!) + (sra.w t3 t3 8) + (mmi-nop!) + (sra.w t1 t1 8) + (mmi-nop!) + (pinteh t6 t1 t3) + (mmi-nop!) + (b.ne t0 a3 generic-interp-dproc-vertex-loop :delay (pcpyud t3 t6 r0)) + (srl32 a0 t6 0) + (s.w t6 v1 16) + (srl32 a1 t3 0) + (s.w a0 v1 48) + (nop!) + (s.w t3 v1 80) + (nop!) + (s.w a1 v1 112)) + ;; Nothing to blend: no job, a job type this build does not implement, or a weight already at zero. + (asm-block finish + (label generic-interp-dproc-finish) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-envmap-dproc () + "Generate environment-map texture deltas for the expanded GSF vertices using the current transform + and Generic VU0 entry 48." + (declare (asm-func none)) + ;; The environment-map processor generic-TIE installs, and a genuinely different operation from + ;; generic-envmap-proc rather than the same one with a flag. It writes no VIF stream at all: it walks + ;; the vertex array *linearly*, gsf-header.num-vtxs of them four at a time, and deposits the reflected + ;; coordinate into each vertex's own dtex and the environment tint into its dclr. It never looks at + ;; the index list and never sees num-dps. + ;; + ;; Three reasons it has to work that way. The coordinate has to exist as a per-vertex record before + ;; generic-interp-dproc runs, because that is what interp-dproc blends - a stream cannot be + ;; interpolated, and without the blend a subdividing TIE surface would pop its reflection at every + ;; LOD boundary. TIE fragments share vertices heavily, so num-vtxs is well below num-dps and doing + ;; the VU0 work per vertex is strictly less work; generic-no-light-dproc fans the result out to draw + ;; points afterwards for nothing. And it is a leaf - no saved registers, no stack - because all of + ;; its state fits in the temporaries. + ;; + ;; It destroys the normal it is reading: dtex and dclr are bytes 16 to 23, which are nrm.x and nrm.y. + ;; That is safe only because the write cursor trails the load cursor by exactly three groups, nrm.z + ;; at byte 24 is never written, and nothing downstream reads normals. It also writes up to three + ;; vertices past the end of the array and reads up to three groups past it, which is part of what the + ;; GSF buffer's spare kilobyte is for. + ;; The environment map is a normal-based sphere map, and the EE half is unreadable without knowing + ;; the VU0 half. consts.matrix is the projection matrix alone, so pos and nrm in a gsf-vertex are + ;; already in camera space with +z forward. Writing p for the position, n for the normal and + ;; k = (0, 0, 1) for the camera axis, entry 48 computes + ;; + ;; m = n - k + ;; r = p + m * (m.p) / m.z + ;; ST = 0.5 * r.xy / |r| + 0.5 + ;; + ;; which is the reflection of the eye vector in the plane perpendicular to m: the one that swaps the + ;; camera axis for the normal. It comes out that cheaply because |n| = 1 makes |m|^2 = -2 m.z + ;; exactly, so the 2/|m|^2 a Householder reflection needs is already sitting in m.z and the only + ;; division in the whole thing is the EE-side 1/(n.z - 1) that the FPU chain below computes, four per + ;; group, crossed in together as vf27. + ;; + ;; Two things follow. Unit-length normals are a hard precondition - nothing normalizes n. And for a + ;; vertex on the optical axis the answer is just n, so what lands at the centre of the texture is a + ;; *normal* facing the camera, not a reflection; a true mirror lookup would swing at twice the rate. + ;; The 0.5 scale and 0.5 bias are consts.envmap.consts z and w, and the shader clamps in both + ;; directions, which is what keeps the rim of the sphere from wrapping. n.z = +1 would divide by + ;; zero, but that is a normal pointing directly away from the camera, and front-facing geometry has + ;; n.z in [-1, 0). + ;; + ;; ftoi12 is a transport format rather than a GS coordinate: VU1 converts it back to float before + ;; emitting ST, so the resolution is 1/4096 - 1/2048 in S once its low bit is taken for the kick flag. + ;; + ;; A wrong result is unmistakable. Sign flip on the reciprocal and the reflection slides the wrong + ;; way, mirrored through the texture centre; non-unit normals and it drifts as the model scales; + ;; missing bias and half of every object collapses to one clamped edge color; missing normalize and + ;; distant geometry smears radially and pins at the edge. + ;; + ;; Four vertices per pass, and entry 48 runs one group behind the caller like the lighting entry does + ;; - but structurally rather than by copying its outputs aside. Its last two pairs start the fourth + ;; vertex and the first thirteen pairs of the *next* call finish it, with vf05-vf08, vf29 and vf30 + ;; carrying the pipeline across the call boundary. So the first call's output is garbage, which is + ;; why every caller calls entry 48 twice before reading anything, and why no other VU0 entry may be + ;; used between two of these calls. + (asm-block load-stream-cursors + (label generic-envmap-dproc-entry) + (nop!) + (lui at #x7000) + (lui v1 #x3f80) + (m f0 v1) + (rlet ((gsf-buf :reg a1 :type gsf-buffer)) + (l.wu gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w v1 at (generic-work-offset fx-buf work consts envmap colors)) + (nop!) + (l.w a2 (-> gsf-buf info ptr-verts))) + (m a0 a2) + (l.hu a1 a1 20) + (nop!) + (l.s f4 a2 24) + (add.i a1 a1 -4) + (l.s f3 a2 56) + (nop!) + (l.s f2 a2 88) + (nop!) + (l.s f1 a2 120) + (nop!) + (l.q t2 a2 16) + (sub.s f4 f4 f0) + (l.q t3 a2 48) + (div.s f4 f0 f4) + (l.q t4 a2 80) + (nop!) + (l.q t5 a2 112) + (nop!) + (l.vf vf31 at (generic-work-offset fx-buf work consts envmap consts)) + (nop!) + (l.q t6 a2) + (nop!) + (l.q a3 a2 32) + (nop!) + (l.q t0 a2 64) + (nop!) + (l.q t1 a2 96) + (mul.s f4 f4 f0) + (m vf21 t2) + (sub.s f3 f3 f0) + (m.ni vf22 t3) + (div.s f3 f0 f3) + (m.ni vf23 t4) + (nop!) + (m.ni vf24 t5) + (nop!) + (m.ni vf9 t6) + (sub.s f2 f2 f0) + (m t2 f4) + (sub.s f1 f1 f0) + (m.ni vf10 a3) + (nop!) + (m.ni vf11 t0) + (nop!) + (m.ni vf12 t1) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m a3 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw a3 a3 t2) + (mmi-nop!) + (nop!) + (nop!) + (nop!) + (m t0 f2) + (nop!) + (nop!) + (nop!) + (add.i a2 a2 128) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t1 f1) + (pextlw t0 t1 t0) + (mmi-nop!) + (pcpyld a3 t0 a3) + (mmi-nop!) + (nop!) + (m.ni vf27 a3) + (nop!) + (callms GENERIC-VU0-ENVMAP) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (l.s f4 a2 24) + (nop!) + (l.s f3 a2 56) + (nop!) + (l.s f2 a2 88) + (nop!) + (l.s f1 a2 120) + (nop!) + (l.q a3 a2 16) + (sub.s f4 f4 f0) + (nop!) + (sub.s f3 f3 f0) + (nop!) + (sub.s f2 f2 f0) + (nop!) + (sub.s f1 f1 f0) + (nop!) + (div.s f4 f0 f4) + (l.q t0 a2 48) + (nop!) + (l.q t1 a2 80) + (nop!) + (l.q t2 a2 112) + (nop!) + (l.q t3 a2) + (nop!) + (l.q t4 a2 32) + (nop!) + (l.q t5 a2 64) + (nop!) + (l.q t6 a2 96) + (mul.s f4 f4 f0) + (nop!) + (div.s f3 f0 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t7 f4) + (nop!) + (add.i a2 a2 128) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t8 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw t7 t8 t7) + (nop!) + (nop!) + (m t8 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f1) + (nop!) + (nop!) + (nop!) + (m.ni vf21 a3) + (nop!) + (m.ni vf9 t3) + (nop!) + (m.ni vf10 t4) + (nop!) + (m.ni vf11 t5) + (nop!) + (m.ni vf12 t6) + (pextlw a3 t9 t8) + (nop!) + (pcpyld a3 a3 t7) + (nop!) + (nop!) + (m.ni vf22 t0) + (nop!) + (m.ni vf23 t1) + (nop!) + (m.ni vf24 t2) + (nop!) + (m.ni vf27 a3) + (nop!) + (callms GENERIC-VU0-ENVMAP) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (l.s f1 a2 24) + (sub.s f1 f1 f0) + (l.s f2 a2 56) + (div.s f3 f0 f1) + (l.s f5 a2 88) + (nop!) + (l.s f1 a2 120) + (nop!) + (l.q a3 a2 16) + (nop!) + (nop!) + (sub.s f4 f2 f0) + (nop!) + (sub.s f2 f5 f0) + (nop!) + (sub.s f1 f1 f0) + (nop!) + (mul.s f3 f3 f0) + (l.q t0 a2 48) + (div.s f4 f0 f4) + (l.q t1 a2 80) + (nop!) + (l.q t2 a2 112) + (nop!) + (l.q t3 a2) + (nop!) + (l.q t4 a2 32) + (nop!) + (l.q t5 a2 64) + (nop!) + (l.q t6 a2 96) + (nop!) + (m t7 f3) + (mul.s f3 f4 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (add.i a2 a2 128) + (nop!) + (m t8 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (pextlw t7 t8 t7) + (nop!) + (nop!) + (nop!) + (nop!) + (m t8 f1) + (nop!) + (nop!) + (nop!) + (nop!) + (pextlw t8 t8 t9) + (nop!) + (pcpyld t7 t8 t7) + (nop!) + (nop!) + (nop!) + (nop!) + (m.ni vf21 a3) + (nop!) + (m.ni vf22 t0) + (nop!) + (m.ni vf23 t1) + (nop!) + (m.ni vf24 t2) + (nop!) + (m.ni vf9 t3) + (nop!) + (m.ni vf10 t4) + (nop!) + (m.ni vf11 t5) + (nop!) + (m.ni vf12 t6) + (nop!) + (m.ni vf27 t7) + (nop!) + (m.ni t1 vf17) + (nop!) + (m.ni t2 vf18) + (nop!) + (m.ni t0 vf19) + (b.le a1 r0 generic-envmap-dproc-finish-tail :delay (m.ni a3 vf20))) + (asm-block vertex-loop + (label generic-envmap-dproc-vertex-loop) + ;; VU0 entry 48 converts the next four normal/eye pairs while the EE packs and writes the + ;; preceding reflected texture coordinates. + (ppach t1 r0 t1) + (callms GENERIC-VU0-ENVMAP) + (ppach t2 r0 t2) + (mmi-nop!) + (ppach t0 r0 t0) + (mmi-nop!) + (ppach a3 r0 a3) + (mmi-nop!) + (nop!) + (s.w t1 a0 16) + (nop!) + (s.w t2 a0 48) + (nop!) + (s.w t0 a0 80) + (nop!) + (s.w a3 a0 112) + (nop!) + (l.s f4 a2 24) + (nop!) + (l.s f3 a2 56) + (nop!) + (l.s f2 a2 88) + (nop!) + (l.s f1 a2 120) + (nop!) + (l.q a3 a2 16) + (sub.s f4 f4 f0) + (s.w v1 a0 20) + (sub.s f3 f3 f0) + (s.w v1 a0 52) + (sub.s f2 f2 f0) + (s.w v1 a0 84) + (sub.s f1 f1 f0) + (s.w v1 a0 116) + (div.s f4 f0 f4) + (l.q t3 a2 48) + (nop!) + (l.q t4 a2 80) + (nop!) + (l.q t5 a2 112) + (nop!) + (l.q t6 a2) + (nop!) + (l.q t2 a2 32) + (nop!) + (l.q t0 a2 64) + (nop!) + (l.q t1 a2 96) + (add.i a1 a1 -4) + (add.i a0 a0 128) + (mul.s f4 f4 f0) + (nop!) + (div.s f3 f0 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t7 f4) + (nop!) + (add.i a2 a2 128) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t8 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw t7 t8 t7) + (nop!) + (nop!) + (m t8 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f1) + (nop!) + (m.ni vf21 a3) + (nop!) + (m.ni vf22 t3) + (nop!) + (m.ni vf23 t4) + (nop!) + (m.ni vf24 t5) + (nop!) + (m.ni vf9 t6) + (pextlw a3 t9 t8) + (m.ni vf10 t2) + (pcpyld a3 a3 t7) + (m.ni vf11 t0) + (nop!) + (m.ni vf12 t1) + (nop!) + (m.ni vf27 a3) + (nop!) + (m.ni t1 vf17) + (nop!) + (m.ni t2 vf18) + (nop!) + (m.ni t0 vf19) + (b.gt a1 r0 generic-envmap-dproc-vertex-loop :delay (m.ni a3 vf20))) + (asm-block finish-tail + (label generic-envmap-dproc-finish-tail) + ;; Drain the final VU0 result and write only the valid lanes in the last group. + (ppach a1 r0 t1) + (s.w v1 a0 20) + (ppach a2 r0 t2) + (s.w v1 a0 52) + (ppach t0 r0 t0) + (s.w a1 a0 16) + (ppach a1 r0 a3) + (s.w a2 a0 48) + (nop!) + (s.w t0 a0 80) + (nop!) + (s.w a1 a0 112) + (nop!) + (s.w v1 a0 84) + (nop!) + (s.w v1 a0 116) + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-prepare-dma-single () + "Build one Generic DMA packet layout, choose the rotating VU1 input and GIF buffers, and initialize + its output header before vertex conversion." + (declare (asm-func none) (allow-saved-regs)) + ;; Builds the packet the processors are about to fill, and it is worth knowing the shape exactly + ;; because everything downstream is measured against it. + ;; + ;; Eight quadwords of header first, which is a generic-texbuf: the DMA tag, then the four rows of the + ;; camera matrix, the triangle-strip GIF tag, and two A+D quadwords. Those two are GS no-ops whose + ;; unused upper words carry the draw-point count and the offset of the first kick, so the counts ride + ;; inside a packet the GS is willing to accept. + ;; + ;; Then one five-quadword adgif shader per strip, each with two software-owned words patched: +28 + ;; takes that strip's vertex count out of gsf-header.strip-table, and +12 takes the running VU-memory + ;; offset at which the strip will be kicked. That offset accumulates three quadwords per vertex plus + ;; nine per strip - the nine being the prologue VU1 writes into the input bank ahead of each strip's + ;; vertices. The last shader's count gets bit 15 set as the terminator. Total upload is + ;; 7 + 5 * num-strips quadwords, which is what the VIF tag is patched with at the end. + ;; + ;; Finally it flips gifbuf-adr to the other header buffer, derives the three stream cursors the + ;; processors write into, and records the quadword count the caller will hand to fromSPR. + ;; + ;; is-envmap chooses between an authored shader per strip and one shared shader repeated - the + ;; environment map is one texture for the whole level - and between the base GIF tag and the envmap + ;; one. That is the only difference between the two paths. + (asm-block select-buffers + (label generic-prepare-dma-single-entry) + (add.i sp sp -32) + (s.q gp at (generic-work-offset fx-buf work storage2 data 1)) + (lui at #x7000) + (nop!) + (l.w t1 at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w t8 at (generic-work-offset saves ptr-shaders)) + (nop!) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (nop!) + (l.w t3 at (generic-work-offset saves is-envmap)) + (nop!) + (l.h a1 t1 18) + (m a0 v1) + (l.bu a2 t1 16) + (b.z t8 generic-prepare-dma-single-empty-packet :delay (l.q t4 at (generic-work-offset fx-buf work consts dma-header))) + (nop!) + (l.q t9 at (generic-work-offset fx-buf work consts matrix vector 0)) + (nop!) + (l.q gp at (generic-work-offset fx-buf work consts matrix vector 1)) + (m a3 a2) + (l.q t7 at (generic-work-offset fx-buf work consts matrix vector 2)) + (b.nz t3 generic-prepare-dma-single-select-second-gif-buffer :delay (l.q t2 at (generic-work-offset fx-buf work consts matrix vector 3))) + (nop!) + (l.q t0 at (generic-work-offset fx-buf work consts base-strgif)) + (nop!) + (l.q t5 at (generic-work-offset fx-buf work consts adcmds 0)) + (b generic-prepare-dma-single-write-header :delay (l.q t6 at (generic-work-offset fx-buf work consts adcmds 3)))) + ;; The environment-map pass takes its own GIF tag and uses the same A+D quadword twice. Nothing + ;; about a GIF buffer is selected here despite what the label has always said. + (asm-block select-envmap-templates + (label generic-prepare-dma-single-select-second-gif-buffer) + (nop!) + (l.q t0 at (generic-work-offset fx-buf work consts envmap strgif)) + (nop!) + (l.q t5 at (generic-work-offset fx-buf work consts adcmds 3)) + (nop!) + (l.q t6 at (generic-work-offset fx-buf work consts adcmds 3))) + (asm-block write-header + (label generic-prepare-dma-single-write-header) + ;; Write the fixed VIF/GIF prefix. The selected shader path below appends either one record per + ;; strip or repeated copies of the shared environment-map shader. + (nop!) + (s.q t4 a0) + (nop!) + (s.q t9 a0 16) + (nop!) + (s.q gp a0 32) + (m t4 t8) + (s.q t7 a0 48) + (add.i t1 t1 22) + (s.q t2 a0 64) + (add.i t2 r0 0) + (s.q t0 a0 80) + (add.i t0 r0 128) + (s.q t5 a0 96) + (b.nz t3 generic-prepare-dma-single-copy-env-shader :delay (s.q t6 a0 112))) + (asm-block copy-shader-loop + (label generic-prepare-dma-single-copy-shader-loop) + ;; Each strip byte selects an authored shader. Copy its five qwords and patch the strip-specific + ;; AD and GIF words into the packet. + (add a0 a0 t0) + (l.q t0 t4) + (add.i a3 a3 -1) + (l.bu t3 t1) + (nop!) + (l.q t5 t4 16) + (add t7 t3 t3) + (l.q t6 t4 32) + (add t8 t7 t3) + (l.q t7 t4 48) + (add.i t9 t8 9) + (l.q t8 t4 64) + (add.i t4 t4 80) + (s.q t0 a0) + (nop!) + (s.w t2 a0 12) + (add.i t1 t1 1) + (s.q t5 a0 16) + (add t2 t2 t9) + (s.w t3 a0 28) + (nop!) + (s.q t6 a0 32) + (add.i t0 r0 80) + (s.q t7 a0 48) + (b.gt a3 r0 generic-prepare-dma-single-copy-shader-loop :delay (s.q t8 a0 64)) + (b generic-prepare-dma-single-finish-shaders :delay (nop!))) + (asm-block copy-env-shader + (label generic-prepare-dma-single-copy-env-shader) + (nop!) + (l.w t3 at (generic-work-offset saves ptr-env-shader)) + (nop!) + (l.q t4 t3) + (nop!) + (l.q t5 t3 16) + (nop!) + (l.q t6 t3 32) + (nop!) + (l.q t7 t3 48) + (nop!) + (l.q t8 t3 64)) + (asm-block copy-env-shader-loop + (label generic-prepare-dma-single-copy-env-shader-loop) + ;; Environment-map strips all share one five-qword shader record; only the strip count and + ;; packet cursor change. + (add a0 a0 t0) + (l.bu t3 t1) + (add.i a3 a3 -1) + (s.q t4 a0) + (add t0 t3 t3) + (s.w t2 a0 12) + (add t0 t0 t3) + (s.q t5 a0 16) + (add.i t0 t0 9) + (s.w t3 a0 28) + (add.i t1 t1 1) + (nop!) + (add t2 t2 t0) + (s.q t6 a0 32) + (add.i t0 r0 80) + (s.q t7 a0 48) + (b.gt a3 r0 generic-prepare-dma-single-copy-env-shader-loop :delay (s.q t8 a0 64))) + (asm-block finish-shaders + (label generic-prepare-dma-single-finish-shaders) + (ori a3 t3 #x8000) + (s.w a1 at (generic-work-offset saves num-dps)) + (nop!) + (s.w a3 a0 28) + (nop!) + (s.w a2 a0 92) + (nop!) + (s.w a1 v1 108) + (b generic-prepare-dma-single-finish-packet :delay (s.w r0 v1 124))) + ;; No shaders means nothing to draw. Write the DMA tag alone and still publish the draw-point count + ;; and the stream cursors, so the caller's send tail and the processors after it stay valid rather + ;; than needing a special case. + (asm-block empty-packet + (label generic-prepare-dma-single-empty-packet) + (sll a3 a2 2) + (s.q t4 a0) + (add a3 a3 a2) + (s.w a1 v1 108) + (sll a3 a3 4) + (nop!) + (add.i t0 a3 128) + (s.w a1 at (generic-work-offset saves num-dps))) + (asm-block finish-packet + (label generic-prepare-dma-single-finish-packet) + ;; Finish the VIF unpack tags, derive all output-stream cursors, and rotate the input-bank + ;; selector for the next call. + (sll t1 a2 2) + (l.w a3 v1 12) + (add a2 t1 a2) + (l.w t1 at (generic-work-offset saves gifbuf-adr)) + (add.i a2 a2 7) + (nop!) + (or t2 a3 t1) + (nop!) + (sll t3 a2 16) + (xor.i a3 t1 GENERIC-VU1-HEADER-FLIP) + (or t1 t2 t3) + (add.i a2 a2 1) + (nop!) + (s.w t1 v1 12) + (nop!) + (s.w a3 at (generic-work-offset saves gifbuf-adr)) + (add.i a1 a1 3) + (add a0 a0 t0) + (sra a1 a1 2) + (add.i a2 a0 32) + (sll t0 a1 2) + (nop!) + (add a3 t0 t0) + (sll a1 t0 2) + (add a3 a3 t0) + (add.i a1 a1 15) + (sll a3 a3 2) + (sra a1 a1 4) + (add.i a3 a3 15) + (sll t1 a1 4) + (sra a3 a3 4) + (l.w a1 at (generic-work-offset saves inbuf-adr)) + (sll a3 a3 4) + (nop!) + (add a3 a2 a3) + (l.w t2 at (generic-work-offset fx-buf work consts stcycle-tag)) + (add a2 a3 t1) + (s.q r0 a3) + (add.i a2 a2 16) + (s.q r0 a3 -16) + (add t1 a2 t1) + (s.q r0 a3 -32) + (add.i t1 t1 16) + (s.q r0 a2) + (sub t3 t1 v1) + (s.q r0 a2 -16) + (sra t3 t3 4) + (s.q r0 a0) + (nop!) + (s.h t3 v1) + (add.i v1 t3 1) + (s.q r0 t1) + (nop!) + (s.q r0 t1 -16) + (nop!) + (add.i t5 a1 1) + (add.i t4 a1 2) + (l.w t3 at (generic-work-offset fx-buf work consts flush-tag)) + (nop!) + (l.w t7 at (generic-work-offset fx-buf work consts unpack-vtx-tag)) + (sll t0 t0 16) + (l.w t6 at (generic-work-offset fx-buf work consts unpack-clr-tag)) + (or t4 t7 t4) + (l.w t7 at (generic-work-offset fx-buf work consts unpack-tex-tag)) + (or t5 t6 t5) + (l.w t6 at (generic-work-offset fx-buf work consts mscal-tag)) + (nop!) + (l.w t8 at (generic-work-offset fx-buf work consts reset-cycle-tag)) + (m t9 a1) + (s.w t2 a0 8) + (or t2 t7 t9) + (s.w t8 t1) + (add.i a0 a0 16) + (s.w r0 t1 4) + (or t4 t4 t0) + (s.w t6 t1 8) + (add.i a3 a3 16) + (s.w t3 t1 12) + (or t1 t5 t0) + (s.w t4 a0 -4) + (or t0 t2 t0) + (s.w t1 a3 -4) + (add.i a2 a2 16) + (s.w t0 a2 -4) + (add.i t0 r0 GENERIC-VU1-INBUF-LAST) + (s.w v1 at (generic-work-offset saves qwc)) + (b.ne a1 t0 generic-prepare-dma-single-select-next-input :delay (add.i v1 a1 GENERIC-VU1-INBUF-STEP)) + (nop!) + (add.i v1 r0 9)) + (asm-block select-next-input + (label generic-prepare-dma-single-select-next-input) + (nop!) + (s.w v1 at (generic-work-offset saves inbuf-adr)) + (nop!) + (s.w a0 at (generic-work-offset saves ptr-vtxs)) + (nop!) + (s.w a3 at (generic-work-offset saves ptr-clrs)) + (nop!) + (s.w a2 at (generic-work-offset saves ptr-texs)) + (m v0 r0) + (l.q gp at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 32)) + (nop!) + (nop!)) + ) + + (defun generic-prepare-dma-double () + "Build the paired ordinary and environment-map Generic DMA packet layouts, choose rotating VU1 input + and GIF buffers, and initialize both output headers before vertex conversion." + (declare (asm-func none) (allow-saved-regs)) + ;; The same packet twice over, because an environment-mapped object is drawn as two GS passes over the + ;; same geometry: the base color, then the reflection blended on top. So this lays down two + ;; generic-texbuf headers, two shader lists - authored shaders for the base pass, the shared + ;; environment shader repeated for the second - and two sets of stream cursors: the base three in + ;; saves.ptr-vtxs/-clrs/-texs and the second pair in saves.ptr-env-clrs/-env-texs. + ;; + ;; Both selectors therefore advance twice, two header buffers consumed and two input banks, because + ;; each pass is an independent VU1 invocation with its own matrix, shaders and kick. That is the reason + ;; the input banks are triple buffered rather than double: a two-pass draw needs two banks in flight + ;; while the GIF is still reading out a third. + ;; + ;; The second pass needs no copy of the positions, and the mechanism is worth knowing: instead of a + ;; second twelve-bytes-per-vertex block in scratchpad, it emits a DMA ref tag pointing at + ;; basep + the first pass's position offset - where those same bytes will already be sitting in main + ;; memory once this buffer has been copied out. So the second pass uploads four bytes of color and + ;; four of coordinate per draw point and nothing else, which is what makes it cheap enough to be + ;; worth having. It is also why its final MSCAL needs a cnt tag of its own rather than riding in a + ;; bare VIF quadword the way the single-pass builder's does. + ;; + ;; Two MSCALs, not one: consts.vif-header is patched into a kick-and-switch quadword whose first word + ;; fires the base pass the moment its streams are in, and whose last word unpacks the second header + ;; into the other VU1 header buffer. And since two flips of gifbuf-adr are the identity, this builder + ;; never writes that cursor back at all - only the input bank advances, twice. + (asm-block select-buffers + (label generic-prepare-dma-double-entry) + (add.i sp sp -128) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (lui at #x7000) + (nop!) + (nop!) + (l.w a3 at (generic-work-offset saves gsf-buf)) + (nop!) + (l.bu v1 a3 16) + (nop!) + (l.h a0 a3 18) + (nop!) + (l.w t7 at (generic-work-offset saves cur-outbuf)) + (sll a2 v1 2) + (add.i a1 a0 3) + (add a2 a2 v1) + (add.i t0 r0 -4) + (and t6 a1 t0) + (sll a1 a2 4) + (add a2 t6 t6) + (add.i a1 a1 112) + (add a2 a2 t6) + (sll t0 t6 2) + (nop!) + (add.i t0 t0 15) + (sll a2 a2 2) + (sra t0 t0 4) + (add.i a2 a2 15) + (sll t0 t0 4) + (sra t9 a2 4) + (sll t2 t9 4) + (m a2 t7) + (sra t8 a1 4) + (add t1 a2 a1) + (nop!) + (add gp t1 t2) + (nop!) + (add t2 gp t0) + (nop!) + (add t3 t2 t0) + (nop!) + (add ra t3 a1) + (nop!) + (add t4 ra t0) + (nop!) + (add t5 t4 t0) + (add.i t0 t1 32) + (add.i t1 gp 64) + (add.i t2 t2 80) + (add.i a1 t3 80) + (add.i t3 ra 112) + (add.i t4 t4 128) + (add.i t5 t5 128) + (s.q r0 t0 -16) + (s.q r0 t1 -32) + (s.q r0 t1 -16) + (s.q r0 t2 -32) + (s.q r0 t2 -16) + (s.q r0 a1 -16) + (s.q r0 t3 -16) + (s.q r0 t4 -32) + (s.q r0 t4 -16) + (s.q r0 t5 -16) + (l.q ra at (generic-work-offset fx-buf work consts dma-header)) + (l.q gp at (generic-work-offset fx-buf work consts vif-header)) + (l.q s5 at (generic-work-offset fx-buf work consts dma-ref-vtxs)) + (l.q s4 at (generic-work-offset fx-buf work consts dma-cnt-call)) + (l.w s3 at (generic-work-offset fx-buf work consts mscal-tag)) + (s.q ra a2) + (s.q gp a1) + (s.q s5 t5) + (s.h t9 t5) + (s.q s4 t5 16) + (s.w s3 t5 24) + (sub t9 t5 t7) + (sra t9 t9 4) + (add.i t9 t9 -1) + (s.h t9 t7) + (add.i t7 t9 3) + (s.w t7 at (generic-work-offset saves qwc)) + (l.w t7 at (generic-work-offset saves basep)) + (sub t9 t0 a2) + (add t7 t7 t9) + (nop!) + (l.w t9 a2 12) + (sll ra t8 16) + (l.w t8 at (generic-work-offset saves gifbuf-adr)) + (nop!) + (l.w gp a1 12) + (or s4 t9 t8) + (l.w t9 at (generic-work-offset saves inbuf-adr)) + (xor.i s5 t8 GENERIC-VU1-HEADER-FLIP) + (nop!) + (or s4 s4 ra) + (l.w t8 at (generic-work-offset fx-buf work consts stcycle-tag)) + (nop!) + (l.w s3 at (generic-work-offset fx-buf work consts flush-tag)) + (or gp gp s5) + (s.w s4 a2 12) + (or s5 gp ra) + (l.w ra at (generic-work-offset fx-buf work consts unpack-vtx-tag)) + (add.i gp t9 1) + (s.w s5 a1 12) + (add.i s4 t9 2) + (l.w s3 at (generic-work-offset fx-buf work consts unpack-clr-tag)) + (sll t6 t6 16) + (l.w s5 at (generic-work-offset fx-buf work consts unpack-tex-tag)) + (or s4 ra s4) + (l.w ra at (generic-work-offset fx-buf work consts mscal-tag)) + (or s3 s3 gp) + (l.w gp at (generic-work-offset fx-buf work consts reset-cycle-tag)) + (m s2 t9) + (s.w t8 t0 -8) + (or s5 s5 s2) + (s.w gp a1 4) + (or s4 s4 t6) + (s.w ra a1) + (or s3 s3 t6) + (s.w s4 t0 -4) + (or s5 s5 t6) + (s.w s3 t1 -4) + (add.i s4 r0 GENERIC-VU1-INBUF-LAST) + (s.w s5 t2 -4) + (b.ne t9 s4 generic-prepare-dma-double-select-next-gif-buffer :delay (add.i t9 t9 GENERIC-VU1-INBUF-STEP)) + (nop!) + (add.i t9 r0 9)) + (asm-block select-next-gif-buffer + (label generic-prepare-dma-double-select-next-gif-buffer) + (add.i s1 t9 1) + (l.w s0 at (generic-work-offset fx-buf work consts unpack-clr-tag)) + (m s3 t9) + (l.w s2 at (generic-work-offset fx-buf work consts unpack-tex-tag)) + (add.i s5 t9 2) + (l.w s4 at (generic-work-offset fx-buf work consts unpack-vtx-tag)) + (or s1 s0 s1) + (s.w t8 t3 -8) + (or t8 s2 s3) + (s.w gp t5 24) + (or gp s4 s5) + (nop!) + (or s5 s1 t6) + (s.w ra t5 28) + (or t8 t8 t6) + (s.w s5 t3 -4) + (or ra gp t6) + (s.w t8 t4 -4) + (add.i t6 r0 GENERIC-VU1-INBUF-LAST) + (s.w t7 t5 4) + (nop!) + (s.w ra t5 12) + (b.ne t9 t6 generic-prepare-dma-double-select-next-input :delay (add.i t5 t9 GENERIC-VU1-INBUF-STEP)) + (nop!) + (add.i t5 r0 9)) + (asm-block select-next-input + (label generic-prepare-dma-double-select-next-input) + (nop!) + (s.w t5 at (generic-work-offset saves inbuf-adr)) + (nop!) + (s.w t0 at (generic-work-offset saves ptr-vtxs)) + (nop!) + (s.w t1 at (generic-work-offset saves ptr-clrs)) + (nop!) + (s.w t2 at (generic-work-offset saves ptr-texs)) + (nop!) + (s.w t3 at (generic-work-offset saves ptr-env-clrs)) + (nop!) + (s.w t4 at (generic-work-offset saves ptr-env-texs)) + (nop!) + (l.w t0 at (generic-work-offset saves ptr-shaders)) + (nop!) + (l.q t1 at (generic-work-offset fx-buf work consts matrix vector 0)) + (b.z t0 generic-prepare-dma-double-empty-packet :delay (l.q t2 at (generic-work-offset fx-buf work consts matrix vector 1))) + (nop!) + (l.q t3 at (generic-work-offset fx-buf work consts matrix vector 2)) + (nop!) + (l.q t4 at (generic-work-offset fx-buf work consts matrix vector 3)) + (nop!) + (s.q t1 a2 16) + (nop!) + (s.q t2 a2 32) + (nop!) + (s.q t3 a2 48) + (nop!) + (s.q t4 a2 64) + (nop!) + (s.q t1 a1 16) + (nop!) + (s.q t2 a1 32) + (nop!) + (s.q t3 a1 48) + (nop!) + (s.q t4 a1 64) + (nop!) + (l.q t1 at (generic-work-offset fx-buf work consts base-strgif)) + (nop!) + (l.q t2 at (generic-work-offset fx-buf work consts adcmds 0)) + (nop!) + (l.q t3 at (generic-work-offset fx-buf work consts adcmds 3)) + (nop!) + (l.q t4 at (generic-work-offset fx-buf work consts envmap strgif)) + (nop!) + (s.q t1 a2 80) + (nop!) + (s.q t2 a2 96) + (nop!) + (s.q t3 a2 112) + (nop!) + (s.q t4 a1 80) + (nop!) + (s.q t3 a1 96) + (nop!) + (s.q t3 a1 112) + (add.i t2 a3 22) + (add.i t3 r0 0) + (m t4 v1) + (add.i t6 r0 128) + (m t1 a2) + (nop!)) + (asm-block copy-shader-loop + (label generic-prepare-dma-double-copy-shader-loop) + ;; Build the ordinary pass first, copying the selected five-qword shader for each strip. + (add t1 t1 t6) + (l.q t6 t0) + (add.i t4 t4 -1) + (l.bu t5 t2) + (nop!) + (l.q t7 t0 16) + (add t9 t5 t5) + (l.q t8 t0 32) + (add ra t9 t5) + (l.q t9 t0 48) + (add.i gp ra 9) + (l.q ra t0 64) + (add.i t0 t0 80) + (s.q t6 t1) + (nop!) + (s.w t3 t1 12) + (add.i t2 t2 1) + (s.q t7 t1 16) + (add t3 t3 gp) + (s.w t5 t1 28) + (nop!) + (s.q t8 t1 32) + (add.i t6 r0 80) + (s.q t9 t1 48) + (b.gt t4 r0 generic-prepare-dma-double-copy-shader-loop :delay (s.q ra t1 64)) + (ori t0 t5 #x8000) + (s.w a0 at (generic-work-offset saves num-dps)) + (nop!) + (s.w t0 t1 28) + (nop!) + (s.w v1 a2 92) + (nop!) + (s.w a0 a2 108) + (nop!) + (s.w r0 a2 124) + (add.i a3 a3 22) + (add.i t0 r0 0) + (m t1 v1) + (l.w t6 at (generic-work-offset saves ptr-env-shader)) + (m a2 a1) + (add.i t8 r0 128) + (nop!) + (l.q t2 t6) + (nop!) + (l.q t3 t6 16) + (nop!) + (l.q t4 t6 32) + (nop!) + (l.q t5 t6 48) + (nop!) + (l.q t6 t6 64)) + (asm-block copy-env-shader-loop + (label generic-prepare-dma-double-copy-env-shader-loop) + ;; Append the matching environment-map pass using the shared shader and its own stream cursors. + (add a2 a2 t8) + (l.bu t7 a3) + (add.i t1 t1 -1) + (s.q t2 a2) + (add t8 t7 t7) + (s.w t0 a2 12) + (add t8 t8 t7) + (s.q t3 a2 16) + (add.i t8 t8 9) + (s.w t7 a2 28) + (add.i a3 a3 1) + (nop!) + (add t0 t0 t8) + (s.q t4 a2 32) + (add.i t8 r0 80) + (s.q t5 a2 48) + (b.gt t1 r0 generic-prepare-dma-double-copy-env-shader-loop :delay (s.q t6 a2 64)) + (ori a3 t7 #x8000) + (s.w a0 at (generic-work-offset saves num-dps)) + (nop!) + (s.w a3 a2 28) + (nop!) + (s.w v1 a1 92) + (nop!) + (s.w a0 a1 108) + (b generic-prepare-dma-double-finish :delay (s.w r0 a1 124))) + (asm-block empty-packet + (label generic-prepare-dma-double-empty-packet) + (nop!) + (l.q a3 a2 16) + (nop!) + (l.q t0 a2 32) + (nop!) + (l.q t1 a2 48) + (nop!) + (l.q t2 a2 64) + (nop!) + (s.q a3 a1 16) + (nop!) + (s.q t0 a1 32) + (nop!) + (s.q t1 a1 48) + (nop!) + (s.q t2 a1 64) + (nop!) + (l.q a3 at (generic-work-offset fx-buf work consts envmap strgif)) + (nop!) + (l.q t0 at (generic-work-offset fx-buf work consts adcmds 3)) + (nop!) + (s.q a3 a1 80) + (nop!) + (s.q t0 a1 96) + (nop!) + (s.q t0 a1 112) + (m a3 v1) + (l.w t5 at (generic-work-offset saves ptr-env-shader)) + (m t0 a1) + (add.i t7 r0 128) + (nop!) + (l.q t1 t5) + (nop!) + (l.q t2 t5 16) + (nop!) + (l.q t3 t5 32) + (nop!) + (l.q t4 t5 48) + (nop!) + (l.q t5 t5 64) + (add a2 a2 t7) + (nop!)) + (asm-block copy-existing-packet-loop + (label generic-prepare-dma-double-copy-existing-packet-loop) + ;; The empty-input case mirrors the already prepared packet headers into the second pass so + ;; both chains retain valid termination and cursor state. + (add t0 t0 t7) + (l.wu t8 a2 12) + (add.i a3 a3 -1) + (l.wu t7 a2 28) + (add.i a2 a2 80) + (s.q t1 t0) + (nop!) + (s.w t8 t0 12) + (nop!) + (s.q t2 t0 16) + (add t8 t8 t6) + (s.w t7 t0 28) + (nop!) + (s.q t3 t0 32) + (add.i t7 r0 80) + (s.q t4 t0 48) + (b.gt a3 r0 generic-prepare-dma-double-copy-existing-packet-loop :delay (s.q t5 t0 64)) + (nop!) + (s.w a0 at (generic-work-offset saves num-dps)) + (nop!) + (s.w v1 a1 92) + (nop!) + (s.w a0 a1 108) + (nop!) + (s.w r0 a1 124)) + (asm-block finish + (label generic-prepare-dma-double-finish) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 128)) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-envmap-proc () + "Generate the Generic environment-map pass from expanded vertices: transform normals and eye vectors, + call VU0 entry 48 for reflected ST coordinates, and build the extra color and texture stream." + (declare (asm-func none) (allow-saved-regs)) + ;; The environment-map processor mercneric installs. It writes exactly one thing: the second pass's + ;; coordinate stream at saves.ptr-env-texs, in draw-point order. The base streams belong to + ;; generic-light-proc, which mercneric calls first, so a lit environment-mapped character is two + ;; walks over the index list rather than one. + ;; + ;; The second pass's color stream is not per vertex at all - every draw point in a fragment gets the + ;; same tint out of consts.envmap.colors - so it is filled wholesale before conversion starts, four + ;; quadwords and therefore sixteen draw points per pass, with a three-quadword remainder ladder. + ;; Keeping it out of the inner loop is worth more than the stores cost. + ;; The environment map is a normal-based sphere map, and the EE half is unreadable without knowing + ;; the VU0 half. consts.matrix is the projection matrix alone, so pos and nrm in a gsf-vertex are + ;; already in camera space with +z forward. Writing p for the position, n for the normal and + ;; k = (0, 0, 1) for the camera axis, entry 48 computes + ;; + ;; m = n - k + ;; r = p + m * (m.p) / m.z + ;; ST = 0.5 * r.xy / |r| + 0.5 + ;; + ;; which is the reflection of the eye vector in the plane perpendicular to m: the one that swaps the + ;; camera axis for the normal. It comes out that cheaply because |n| = 1 makes |m|^2 = -2 m.z + ;; exactly, so the 2/|m|^2 a Householder reflection needs is already sitting in m.z and the only + ;; division in the whole thing is the EE-side 1/(n.z - 1) that the FPU chain below computes, four per + ;; group, crossed in together as vf27. + ;; + ;; Two things follow. Unit-length normals are a hard precondition - nothing normalizes n. And for a + ;; vertex on the optical axis the answer is just n, so what lands at the centre of the texture is a + ;; *normal* facing the camera, not a reflection; a true mirror lookup would swing at twice the rate. + ;; The 0.5 scale and 0.5 bias are consts.envmap.consts z and w, and the shader clamps in both + ;; directions, which is what keeps the rim of the sphere from wrapping. n.z = +1 would divide by + ;; zero, but that is a normal pointing directly away from the camera, and front-facing geometry has + ;; n.z in [-1, 0). + ;; + ;; ftoi12 is a transport format rather than a GS coordinate: VU1 converts it back to float before + ;; emitting ST, so the resolution is 1/4096 - 1/2048 in S once its low bit is taken for the kick flag. + ;; + ;; A wrong result is unmistakable. Sign flip on the reciprocal and the reflection slides the wrong + ;; way, mirrored through the texture centre; non-unit normals and it drifts as the model scales; + ;; missing bias and half of every object collapses to one clamped edge color; missing normalize and + ;; distant geometry smears radially and pins at the edge. + ;; + ;; Four vertices per pass, and entry 48 runs one group behind the caller like the lighting entry does + ;; - but structurally rather than by copying its outputs aside. Its last two pairs start the fourth + ;; vertex and the first thirteen pairs of the *next* call finish it, with vf05-vf08, vf29 and vf30 + ;; carrying the pipeline across the call boundary. So the first call's output is garbage, which is + ;; why every caller calls entry 48 twice before reading anything, and why no other VU0 entry may be + ;; used between two of these calls. + ;; + ;; The kick flags need the same two-group delay the coordinates do, which is what s0, s1 and s2 are: + ;; a three-deep shift register moved along once per pass. That delay line plus the four-address + ;; bookkeeping is why this function needs the saved registers at all. + (asm-block load-stream-cursors + (label generic-envmap-proc-entry) + (add.i sp sp -128) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (lui at #x7000) + (nop!) + (rlet ((gsf-buf :reg v1 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a0 at (generic-work-offset saves num-dps)) + (nop!) + (l.w t0 (-> gsf-buf info ptr-iks)) + (nop!) + (l.w a2 (-> gsf-buf info ptr-verts))) + (nop!) + (l.w t3 at (generic-work-offset saves ptr-env-clrs)) + (nop!) + (l.w v1 at (generic-work-offset saves ptr-env-texs)) + (nop!) + (add.i t1 r0 255) + (add.i a3 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (lui a1 -2) + (lui t2 #x3f80) + (ori a1 a1 #xfffe) + (m f0 t2) + (add.i t2 a0 3) + (sra t5 t2 2) + (l.q t2 at (generic-work-offset fx-buf work consts envmap colors)) + (sra t4 t5 2) + (and.i t5 t5 3) + (b.z t4 generic-envmap-proc-clear-tail :delay (nop!))) + (asm-block clear-four-loop + (label generic-envmap-proc-clear-four-loop) + ;; Seed the environment color stream four vertices at a time. + (add.i t3 t3 64) + (s.q t2 t3 -64) + (nop!) + (s.q t2 t3 -48) + (add.i t4 t4 -1) + (s.q t2 t3 -32) + (b.gt t4 r0 generic-envmap-proc-clear-four-loop :delay (s.q t2 t3 -16))) + (asm-block clear-tail + (label generic-envmap-proc-clear-tail) + (b.z t5 generic-envmap-proc-prime-conversion :delay (add.i t4 t5 -1)) + (b.z t4 generic-envmap-proc-prime-conversion :delay (s.q t2 t3)) + (add.i t3 t3 16) + (add.i t4 t4 -1) + (b.z t4 generic-envmap-proc-prime-conversion :delay (s.q t2 t3)) + (add.i t3 t3 16) + (add.i t4 t4 -1) + (nop!) + (s.q t2 t3)) + (asm-block prime-conversion + (label generic-envmap-proc-prime-conversion) + ;; Prime four source vertices, the camera transform, and environment-map constants before + ;; starting the overlapped EE/VU0 loop. + (add.i a0 a0 -4) + (l.vf vf31 at (generic-work-offset fx-buf work consts envmap consts)) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a1 a1 a1) + (mmi-nop!) + (pextlw a2 a2 a2) + (mmi-nop!) + (pextlw a2 a2 a2) + (mmi-nop!) + (pcpyh t2 t1) + (l.d t1 t0) + (pcpyld t2 t2 t2) + (mmi-nop!) + (pcpyh a3 a3) + (mmi-nop!) + (pcpyld a3 a3 a3) + (mmi-nop!) + (add.i t0 t0 8) + (s.q t2 at (generic-work-offset saves envmap index-mask)) + (pextlh t1 r0 t1) + (mmi-nop!) + (and.q t2 t1 t2) + (mmi-nop!) + (sll.w t2 t2 5) + (mmi-nop!) + (add.w t5 t2 a2) + (mmi-nop!) + (srl32 t6 t5 0) + (l.s f4 t5 24) + (pcpyud t7 t5 r0) + (l.s f3 t6 24) + (srl32 t8 t7 0) + (l.s f2 t7 24) + (and.q t1 t1 a3) + (l.s f1 t8 24) + (sra.w t2 t1 8) + (l.q t1 t5 16) + (m.q s0 t2) + (sub.s f4 f4 f0) + (div.s f4 f0 f4) + (l.q t2 t6 16) + (nop!) + (l.q t3 t7 16) + (nop!) + (l.q t4 t8 16) + (nop!) + (l.q t5 t5) + (nop!) + (l.q t6 t6) + (nop!) + (l.q t7 t7) + (nop!) + (l.q t8 t8) + (mul.s f4 f4 f0) + (nop!) + (sub.s f3 f3 f0) + (nop!) + (div.s f3 f0 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f4) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (sub.s f2 f2 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m ra f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (sub.s f1 f1 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw t9 ra t9) + (nop!) + (nop!) + (nop!) + (nop!) + (m ra f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m gp f1) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (pextlw ra gp ra) + (nop!) + (pcpyld t9 ra t9) + (nop!) + (nop!) + (m.ni vf21 t1) + (nop!) + (m.ni vf22 t2) + (nop!) + (m.ni vf23 t3) + (nop!) + (m.ni vf24 t4) + (nop!) + (m.ni vf9 t5) + (nop!) + (m.ni vf10 t6) + (nop!) + (m.ni vf11 t7) + (nop!) + (m.ni vf12 t8) + (nop!) + (m.ni vf27 t9) + (l.q t2 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (nop!) + (l.d t1 t0) + (nop!) + (nop!) + (nop!) + (add.i t0 t0 8) + (pextlh t1 r0 t1) + (mmi-nop!) + (and.q t2 t1 t2) + (mmi-nop!) + (sll.w t2 t2 5) + (mmi-nop!) + (add.w t5 t2 a2) + (mmi-nop!) + (srl32 t6 t5 0) + (l.s f3 t5 24) + (pcpyud t7 t5 r0) + (l.s f2 t6 24) + (srl32 t8 t7 0) + (l.s f1 t7 24) + (and.q t1 t1 a3) + (l.s f4 t8 24) + (sra.w t2 t1 8) + (l.q t1 t5 16) + (m.q s1 t2) + (sub.s f5 f3 f0) + (sub.s f3 f2 f0) + (nop!) + (sub.s f2 f1 f0) + (nop!) + (sub.s f1 f4 f0) + (nop!) + (div.s f4 f0 f5) + (l.q t2 t6 16) + (nop!) + (l.q t3 t7 16) + (nop!) + (l.q t4 t8 16) + (nop!) + (l.q t5 t5) + (nop!) + (l.q t6 t6) + (nop!) + (l.q t7 t7) + (nop!) + (l.q t8 t8) + (mul.s f4 f4 f0) + (nop!) + (div.s f3 f0 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f4) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m ra f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw t9 ra t9) + (nop!) + (nop!) + (m ra f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m gp f1) + (nop!) + (nop!) + (nop!) + (m.ni vf21 t1) + (nop!) + (m.ni vf9 t5) + (nop!) + (m.ni vf10 t6) + (nop!) + (m.ni vf11 t7) + (nop!) + (m.ni vf12 t8) + (pextlw t1 gp ra) + (nop!) + (pcpyld t1 t1 t9) + (nop!) + (nop!) + (m.ni vf22 t2) + (nop!) + (m.ni vf23 t3) + (nop!) + (m.ni vf24 t4) + (nop!) + (m.ni vf27 t1) + (l.q t2 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (nop!) + (l.d t1 t0) + (nop!) + (nop!) + (nop!) + (add.i t0 t0 8) + (pextlh t1 r0 t1) + (mmi-nop!) + (and.q t2 t1 t2) + (mmi-nop!) + (sll.w t2 t2 5) + (mmi-nop!) + (add.w t2 t2 a2) + (mmi-nop!) + (srl32 t3 t2 0) + (l.s f3 t2 24) + (pcpyud t7 t2 r0) + (l.s f2 t3 24) + (srl32 t8 t7 0) + (l.s f1 t7 24) + (and.q t1 t1 a3) + (l.s f4 t8 24) + (sra.w t4 t1 8) + (l.q t1 t2 16) + (m.q s2 t4) + (sub.s f5 f3 f0) + (sub.s f3 f2 f0) + (nop!) + (sub.s f2 f1 f0) + (nop!) + (sub.s f1 f4 f0) + (nop!) + (div.s f4 f0 f5) + (l.q t4 t3 16) + (nop!) + (l.q t5 t7 16) + (nop!) + (l.q t6 t8 16) + (nop!) + (l.q t2 t2) + (nop!) + (l.q t3 t3) + (nop!) + (l.q t7 t7) + (nop!) + (l.q t8 t8) + (mul.s f4 f4 f0) + (nop!) + (div.s f3 f0 f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t9 f4) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f3 f3 f0) + (nop!) + (div.s f2 f0 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m ra f3) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f2 f0) + (nop!) + (div.s f1 f0 f1) + (nop!) + (pextlw t9 ra t9) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (m ra f2) + (nop!) + (m.ni vf21 t1) + (nop!) + (m.ni vf22 t4) + (nop!) + (m.ni vf23 t5) + (nop!) + (m.ni vf24 t6) + (nop!) + (nop!) + (nop!) + (m t1 f1) + (pextlw t1 t1 ra) + (nop!) + (pcpyld t1 t1 t9) + (m.ni vf9 t2) + (nop!) + (m.ni vf10 t3) + (nop!) + (m.ni vf11 t7) + (nop!) + (m.ni vf12 t8) + (nop!) + (m.ni vf27 t1) + (nop!) + (m.ni t4 vf17) + (nop!) + (m.ni t5 vf18) + (nop!) + (m.ni t6 vf19) + (b.le a0 r0 generic-envmap-proc-finish-tail :delay (m.ni t7 vf20))) + (asm-block vertex-loop + (label generic-envmap-proc-vertex-loop) + ;; Entry 48 produces reflection coordinates for the next group while the EE packs the previous + ;; group's reflected ST, environment color, and source texture records. + (l.q t2 at (generic-work-offset saves envmap index-mask)) + (callms GENERIC-VU0-ENVMAP) + (add.i a0 a0 -4) + (l.d t1 t0) + (nop!) + (nop!) + (add.i v1 v1 16) + (add.i t0 t0 8) + (pextlh t1 r0 t1) + (mmi-nop!) + (and.q t2 t1 t2) + (mmi-nop!) + (sll.w t2 t2 5) + (mmi-nop!) + (add.w t9 t2 a2) + (mmi-nop!) + (srl32 ra t9 0) + (l.s f1 t9 24) + (pcpyud s4 t9 r0) + (l.s f4 ra 24) + (sub.s f3 f1 f0) + (nop!) + (srl32 s3 s4 0) + (l.s f1 s4 24) + (and.q t1 t1 a3) + (l.s f2 s3 24) + (sra.w s5 t1 8) + (l.q t1 t9 16) + (div.s f3 f0 f3) + (l.q t2 ra 16) + (m.q gp s0) + (sub.s f4 f4 f0) + (ppach t4 r0 t4) + (l.q t3 s4 16) + (ppach t5 r0 t5) + (l.q t8 s3 16) + (ppach t6 r0 t6) + (l.q t9 t9) + (ppach t7 r0 t7) + (l.q ra ra) + (pextlw t4 t5 t4) + (l.q t5 s4) + (pextlw t6 t7 t6) + (l.q t7 s3) + (m.q s0 s1) + (mul.s f5 f3 f0) + (m.q s1 s2) + (div.s f3 f0 f4) + (pcpyld t4 t6 t4) + (mmi-nop!) + (and.q t4 t4 a1) + (mmi-nop!) + (m.q s2 s5) + (m t6 f5) + (sub.s f4 f1 f0) + (nop!) + (or.q t4 t4 gp) + (nop!) + (sub.s f1 f2 f0) + (nop!) + (mul.s f2 f3 f0) + (s.q t4 v1 -16) + (nop!) + (nop!) + (div.s f3 f0 f4) + (nop!) + (nop!) + (nop!) + (nop!) + (m t4 f2) + (nop!) + (nop!) + (nop!) + (nop!) + (mul.s f2 f3 f0) + (nop!) + (pextlw t4 t4 t6) + (nop!) + (nop!) + (nop!) + (div.s f1 f0 f1) + (nop!) + (nop!) + (m t6 f2) + (nop!) + (m.ni vf21 t1) + (nop!) + (m.ni vf22 t2) + (nop!) + (m.ni vf23 t3) + (nop!) + (m.ni vf24 t8) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (m t1 f1) + (nop!) + (m.ni vf9 t9) + (pextlw t1 t1 t6) + (m.ni vf10 ra) + (pcpyld t1 t1 t4) + (m.ni vf11 t5) + (nop!) + (m.ni vf12 t7) + (nop!) + (m.ni vf27 t1) + (nop!) + (m.ni t4 vf17) + (nop!) + (m.ni t5 vf18) + (nop!) + (m.ni t6 vf19) + (b.gt a0 r0 generic-envmap-proc-vertex-loop :delay (m.ni t7 vf20))) + (asm-block finish-tail + (label generic-envmap-proc-finish-tail) + ;; Drain the final VU0 group and write only the remaining vertices. + (add.i v1 v1 16) + (nop!) + (ppach t4 r0 t4) + (mmi-nop!) + (ppach t5 r0 t5) + (mmi-nop!) + (ppach t6 r0 t6) + (mmi-nop!) + (ppach t7 r0 t7) + (mmi-nop!) + (pextlw t4 t5 t4) + (mmi-nop!) + (pextlw t6 t7 t6) + (mmi-nop!) + (pcpyld t4 t6 t4) + (mmi-nop!) + (and.q t4 t4 a1) + (mmi-nop!) + (or.q t4 t4 s0) + (mmi-nop!) + (nop!) + (s.q t4 v1 -16) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 7)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 6)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s1 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s0 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 128)) + (nop!) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-light-proc () + "Expand indexed GSF vertices in groups of four, run Generic VU0 lighting entry zero, and write the + lit colors plus reconstructed position, normal, and texture streams." + (declare (asm-func none) (allow-saved-regs)) + ;; The lit processor, and the one mercneric installs for every fragment. Four vertices per pass: + ;; their normals cross into vf1-vf4 and their packed colors into vf5-vf8, VU0 entry zero dots each + ;; normal against the three light directions, clamps the negative side away, sums the ambient and + ;; the three directional colors, modulates the vertex's own color by the result and saturates at + ;; 255. The seven light quadwords and the clamp ceiling were copied into scratchpad by + ;; generic-initialize. + ;; + ;; The one thing to know before reading the loop: entry zero copies vf17-vf20 into vf21-vf24 as its + ;; very first instruction, so the colors the EE reads out after a call belong to the group + ;; submitted by the *previous* call. Everything here is therefore one group behind, and the tail + ;; block exists to publish the last group with nothing new to submit. + ;; + ;; A wrong result looks like a character lit from the wrong direction, or clipping to white where + ;; the saturate should have held it at 255. + (asm-block load-cursors-and-lights + (label generic-light-proc-entry) + (add.i sp sp -96) + (s.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (s.q s2 at (generic-work-offset fx-buf work storage2 data 1)) + (s.q s3 at (generic-work-offset fx-buf work storage2 data 2)) + (s.q s4 at (generic-work-offset fx-buf work storage2 data 3)) + (s.q s5 at (generic-work-offset fx-buf work storage2 data 4)) + (s.q gp at (generic-work-offset fx-buf work storage2 data 5)) + (lui at #x7000) + (nop!) + (nop!) + (rlet ((gsf-buf :reg v1 :type gsf-buffer)) + (l.w gsf-buf at (generic-work-offset saves gsf-buf)) + (nop!) + (l.w a1 at (generic-work-offset saves num-dps)) + (nop!) + (l.w a0 (-> gsf-buf info ptr-verts)) + (nop!) + (l.w t0 (-> gsf-buf info ptr-iks))) + (nop!) + (l.w t3 at (generic-work-offset saves ptr-vtxs)) + (nop!) + (l.w v1 at (generic-work-offset saves ptr-clrs)) + (add.i a3 r0 255) + (l.w t2 at (generic-work-offset saves ptr-texs)) + (add.i a2 r0 256) ;; not a DMA value: 255 and 256 broadcast into the index and kick-bit masks + (lui t1 -2) + (m t4 a1) + (l.vf vf10 at (generic-work-offset fx-buf work lights direction 0)) + (ori a1 t1 #xfffe) + (l.vf vf11 at (generic-work-offset fx-buf work lights direction 1)) + (pextlw a1 a1 a1) + (l.vf vf12 at (generic-work-offset fx-buf work lights direction 2)) + (pextlw t1 a0 a0) + (l.vf vf15 at (generic-work-offset fx-buf work lights color 1)) + (pextlw a0 a1 a1) + (l.vf vf14 at (generic-work-offset fx-buf work lights color 0)) + (pextlw a1 t1 t1) + (l.vf vf16 at (generic-work-offset fx-buf work lights color 2)) + (pcpyh a3 a3) + (l.vf vf13 at (generic-work-offset fx-buf work lights ambient)) + (pcpyh t1 a2) + (mmi-nop!) + (pcpyld a2 a3 a3) + (l.vf vf9 at (generic-work-offset fx-buf work consts light-consts)) + (pcpyld a3 t1 t1) + (mmi-nop!) + (nop!) + (l.dr t1 t0) + (nop!) + (l.dl t1 t0 7) + (nop!) + (add.i t0 t0 8) + (pextlh t1 r0 t1) + (mmi-nop!) + (and.q t5 t1 a2) + (mmi-nop!) + (sll.w t5 t5 5) + (mmi-nop!) + (add.w s5 t5 a1) + (mmi-nop!) + (srl32 t9 s5 0) + (mmi-nop!) + (pcpyud s4 s5 r0) + (l.q t6 s5) + (srl32 ra s4 0) + (l.q t7 t9) + (and.q t8 t1 a3) + (l.q t5 s4) + (sra.w gp t8 8) + (l.q t8 ra) + (pextuw s3 t7 t6) + (l.q s5 s5 16) + (pextuw s2 t8 t5) + (l.q t9 t9 16) + (pcpyud s3 s3 s2) + (l.q s4 s4 16) + (and.q s3 s3 a0) + (l.q ra ra 16) + (or.q s3 s3 gp) + (m.ni vf1 s5) + (pextub gp r0 s5) + (s.q s3 t2) + (pextub s5 r0 t9) + (m.ni vf2 t9) + (pextub t9 r0 s4) + (m.ni vf3 s4) + (pextub s4 r0 ra) + (m.ni vf4 ra) + (pextuh ra r0 gp) + (mmi-nop!) + (pextuh gp r0 s5) + (m.ni vf5 ra) + (pextuh t9 r0 t9) + (m.ni vf6 gp) + (pextuh ra r0 s4) + (m.ni vf7 t9) + (prot3w t8 t8) + (m.ni vf8 ra) + (prot3w t7 t7) + (callms GENERIC-VU0-LIGHT) + (pextuw t9 t7 t6) + (mmi-nop!) + (pcpyld t7 t5 t7) + (mmi-nop!) + (pcpyld t6 t9 t6) + (mmi-nop!) + (add.i t2 t2 16) + (mmi-nop!) + (nop!) + (mmi-nop!) + (pextuw t5 t8 t5) + (s.q t7 t3 16) + (pcpyld t5 t8 t5) + (s.q t6 t3) + (nop!) + (s.q t5 t3 32) + (add.i t3 t3 48) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (add.i t4 t4 -4) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (b.le t4 r0 generic-light-proc-finish-tail :delay (mmi-nop!))) + (asm-block vertex-loop + (label generic-light-proc-vertex-loop) + ;; Gather four indexed vertices and call VU0 entry zero. The EE simultaneously drains the + ;; preceding lit colors and reconstructs its position, normal, and texture streams. + (nop!) + (l.dr t1 t0) + (nop!) + (l.dl t1 t0 7) + (nop!) + (add.i t0 t0 8) + (pextlh t1 r0 t1) + (mmi-nop!) + (and.q t5 t1 a2) + (mmi-nop!) + (sll.w t5 t5 5) + (mmi-nop!) + (add.w s5 t5 a1) + (mmi-nop!) + (srl32 t9 s5 0) + (mmi-nop!) + (pcpyud s4 s5 r0) + (l.q t6 s5) + (srl32 ra s4 0) + (l.q t7 t9) + (and.q t8 t1 a3) + (l.q t5 s4) + (sra.w gp t8 8) + (l.q t8 ra) + (pextuw s3 t7 t6) + (l.q s5 s5 16) + (pextuw s2 t8 t5) + (l.q t9 t9 16) + (pcpyud s3 s3 s2) + (l.q s4 s4 16) + (and.q s3 s3 a0) + (l.q ra ra 16) + (or.q s3 s3 gp) + (m.ni vf1 s5) + (pextub gp r0 s5) + (s.q s3 t2) + (pextub s5 r0 t9) + (m.ni vf2 t9) + (pextub t9 r0 s4) + (m.ni vf3 s4) + (pextub s4 r0 ra) + (m.ni vf4 ra) + (pextuh ra r0 gp) + (mmi-nop!) + (pextuh gp r0 s5) + (m.ni vf5 ra) + (pextuh t9 r0 t9) + (m.ni vf6 gp) + (pextuh ra r0 s4) + (m.ni vf7 t9) + (prot3w t8 t8) + (m.ni vf8 ra) + (prot3w t7 t7) + (callms GENERIC-VU0-LIGHT) + (pextuw t9 t7 t6) + (mmi-nop!) + (pcpyld t7 t5 t7) + (mmi-nop!) + (pcpyld t6 t9 t6) + (mmi-nop!) + (pextuw t5 t8 t5) + (mmi-nop!) + (pcpyld t5 t8 t5) + (s.q t6 t3) + (nop!) + (s.q t7 t3 16) + (nop!) + (s.q t5 t3 32) + (add.i t2 t2 16) + (mmi-nop!) + (add.i t3 t3 48) + (m.ni t7 vf21) + (nop!) + (m.ni t8 vf22) + (nop!) + (m.ni t5 vf23) + (nop!) + (m.ni t6 vf24) + (ppach t7 t8 t7) + (mmi-nop!) + (ppach t5 t6 t5) + (mmi-nop!) + (ppacb t5 t5 t7) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (s.q t5 v1) + (add.i t4 t4 -4) + (add.i v1 v1 16) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (b.gt t4 r0 generic-light-proc-vertex-loop :delay (nop!))) + (asm-block finish-tail + (label generic-light-proc-finish-tail) + ;; Pack the final VU0 colors after the main loop has consumed every complete group. + (nop!) + (vnop) + (nop!) + (m.ni a2 vf17) + (nop!) + (m.ni a3 vf18) + (nop!) + (m.ni a0 vf19) + (nop!) + (m.ni a1 vf20) + (ppach a2 a3 a2) + (mmi-nop!) + (ppach a0 a1 a0) + (mmi-nop!) + (ppacb a0 a0 a2) + (mmi-nop!) + (nop!) + (s.q a0 v1) + (m v0 r0) + (l.d ra at (generic-work-offset fx-buf work storage2 data 0)) + (l.q gp at (generic-work-offset fx-buf work storage2 data 5)) + (l.q s5 at (generic-work-offset fx-buf work storage2 data 4)) + (l.q s4 at (generic-work-offset fx-buf work storage2 data 3)) + (l.q s3 at (generic-work-offset fx-buf work storage2 data 2)) + (l.q s2 at (generic-work-offset fx-buf work storage2 data 1)) + (jr ra :delay (add.i sp sp 96)) + (nop!) + (nop!) + (nop!)) + ) + + (defun generic-dma-from-spr ((scratch-address int) (qwc int)) + "Wait for the scratchpad-to-memory DMA channel, then transfer qwc quadwords from scratch-address to + the current Generic DMA output and advance that output cursor." + (declare (asm-func none)) + ;; The standalone form of the send tail that the three wrappers below have inlined: hand one + ;; finished packet to the fromSPR channel and advance the main-memory write cursor by exactly what + ;; it will deliver. The scratchpad address register takes only the low fourteen bits of an offset + ;; into the page, which is what the mask is for. + ;; + ;; The wait is before programming the channel, not after starting it, so the copy overlaps whatever + ;; the caller does next. Each busy poll is charged to saves.from-spr-waits, and that is how a frame + ;; spent waiting on scratchpad bandwidth shows up in the timing display instead of looking like + ;; slow conversion. + (asm-block address-the-channel + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-dma-from-spr-entry) + (nop!) + (lui at #x7000) + (lui from-spr #x1000) + (l.wu v1 at (generic-work-offset saves basep)) + (ori from-spr from-spr #xd000) + (l.w t0 (-> from-spr chcr)) + (nop!) + (add.i a3 at (generic-work-offset saves from-spr-waits)) + (and.i a0 a0 #x3fff) + (and.i t0 t0 DMA-CHCR-STR) + (nop!) + (b.z t0 generic-dma-from-spr-start-dma :delay (nop!)) + (m t0 from-spr) + (nop!))) + (asm-block wait-for-dma + (label generic-dma-from-spr-wait-for-dma) + (l.w t1 a3) + (nop!) + (l.w t2 t0) + (nop!) + (and.i t2 t2 DMA-CHCR-STR) + (add.i t1 t1 1) + (b.nz t2 generic-dma-from-spr-wait-for-dma :delay (s.w t1 a3)) + (m a3 r0)) + ;; Program the channel, advance saves.basep by exactly what it will deliver, and switch to the + ;; other output buffer. Nothing waits for this transfer - the wait above is what makes that safe. + (asm-block start-dma + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (label generic-dma-from-spr-start-dma) + (sll a3 a1 4) + (s.w a0 (-> from-spr sadr)) + (nop!) + (s.w v1 (-> from-spr madr)) + (add.i a0 r0 DMA-CHCR-STR) + (s.w a1 (-> from-spr qwc)) + (add v1 v1 a3) + (s.w a0 (-> from-spr chcr)) + (nop!) + (s.w v1 at (generic-work-offset saves basep)) + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!))) + ) + + (defun upload-vu0-program ((func vu-function) (wait-counter pointer)) + "Upload func to VU0 in blocks of at most 127 instruction pairs, waiting for VIF0 DMA to become idle + and charging every busy poll to wait-counter. The DMA construction is highly optimized." + (declare (asm-func none)) + ;; VIF0's MPG count field is eight bits and counts instruction pairs, so a program longer than 127 + ;; pairs cannot be one unpack. The loop peels 127 pairs at a time and emits a REF tag per block, + ;; filling in each block's source address, pair count and destination instruction address as it + ;; goes, then appends an END tag. Both the Generic and the mercneric VU0 programs are longer than + ;; 127 pairs, which is why this exists rather than a single tag. + ;; + ;; The two cache write-backs are the load-bearing part: the chain was just written by ordinary EE + ;; stores and is about to be read by the DMA controller, which does not see the data cache. + ;; After VIF0 becomes idle, clear its qword count, install the chain address, and start chain mode. + ;; Each busy poll increments wait-counter. + (asm-block set-up-upload-chain + (label upload-vu0-program-entry) + (m! v1 *vu0-dma-list*) + (lui a2 #x3000) + (l.wu a3 a0 8) + (lui t0 #x1000) + (l.wu t1 a0 4) + (add.i v1 v1 12) + (add.i t2 a0 16) + (m a0 v1) + (lui t3 #x4a00)) + ;; One DMA tag per upload block. VIF0's unpack count is eight bits, so a program longer than 127 + ;; instruction pairs has to arrive as several tags; the loop peels 127 pairs at a time and fills in + ;; each tag's address, count and MPG destination as it goes. The two cache write-backs at the end + ;; are for the chain itself, which the EE has just written and the DMA controller is about to read. + (asm-block append-upload-block + (rlet ((vif0 :reg a2 :type dma-bank-vif)) + (label upload-vu0-program-append-upload-block) + (add.i t4 a3 -127) + (s.w a2 a0) + (max.w t5 t4 r0) + (s.w t2 a0 4) + (sub t4 a3 t5) + (s.w t0 a0 8) + (m a3 t5) + (s.b t4 a0) + (sll t6 t4 17) + (sll t5 t4 1) + (add t6 t6 t1) + (add t1 t1 t5) + (add t5 t6 t3) + (sll t4 t4 4) + (s.w t5 a0 12) + (add t2 t2 t4) + (b.nz a3 upload-vu0-program-append-upload-block :delay (add.i a0 a0 16)) + (lui a2 #x7000) + (lui a3 #x1000) + (s.d a2 a0) + (ori vif0 a3 #x8000) + (s.d r0 a0 8) + (l.w a3 (-> vif0 chcr)) + (m t0 v1) + (sync.l) + (cache dxwbin t0 0) + (sync.l) + (cache dxwbin t0 1) + (sync.l) + (m t0 r0) + (sync.l) + (cache dxwbin a0 0) + (sync.l) + (cache dxwbin a0 1) + (sync.l) + (m a0 r0) + (and.i a0 a3 DMA-CHCR-STR) + (b.z a0 upload-vu0-program-start-vif0 :delay (add.i a0 r0 325)) + (m a3 vif0) + (nop!))) + ;; Charge every busy poll to the caller's counter. a3 is the channel again by now, copied out of + ;; a2 so the store that starts it can use the delay slots either side. + (asm-block wait-for-vif0 + (rlet ((vif0-channel :reg a3 :type dma-bank-vif) + (stall-count-ptr :reg a1) + (stall-count :reg t0) + (status :reg t1)) + (label upload-vu0-program-wait-for-vif0) + (l.w stall-count stall-count-ptr) + (nop!) + (l.w status (-> vif0-channel chcr)) + (nop!) + (and.i status status DMA-CHCR-STR) + (add.i stall-count stall-count 1) + (b.nz status upload-vu0-program-wait-for-vif0 + :delay (s.w stall-count stall-count-ptr)) + (m stall-count-ptr r0))) + ;; Chain mode with the tag transfer enabled, and a sync either side of every register write: this + ;; is the one place the converter hands the EE's own cache-line writes straight to a DMA channel. + (asm-block start-vif0 + (rlet ((vif0 :reg a2 :type dma-bank-vif)) + (label upload-vu0-program-start-vif0) + (sync.l) + (s.w r0 (-> vif0 qwc)) + (s.w v1 (-> vif0 tadr)) + (sync.l) + (s.w a0 (-> vif0 chcr)) + (sync.l) + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!))) + ) \ No newline at end of file diff --git a/goal_src/jak1/engine/gfx/generic/generic-effect.gc b/goal_src/jak1/engine/gfx/generic/generic-effect.gc index 62d54a89ad..d3555c35ac 100644 --- a/goal_src/jak1/engine/gfx/generic/generic-effect.gc +++ b/goal_src/jak1/engine/gfx/generic/generic-effect.gc @@ -8,17 +8,16 @@ (require "engine/gfx/generic/generic-h.gc") (define-extern *generic-envmap-texture* texture) -;; GENERIC converts expanded fragment geometry into the DMA/VIF packets consumed by the Generic VU1 -;; renderer. MERC and Generic TIE first build a GSF source buffer in main memory. Its header supplies -;; strip and draw-point counts, ptr-iks supplies the indexed draw order, and ptr-verts addresses the -;; expanded 32-byte position/texture/normal/color records. Camera, lighting, packet-template, shader, -;; and output-cursor state lives in the shared generic-work scratchpad page. +;; GENERIC Effect processors convert GSF format vertices generated by generic-tie/generic-merc into +;; DMA/VIF packets for the GENERIC VU1 renderer. Generic VU1 doesn't support effects like lighting +;; or environment mapping, so these are done on the EE during conversion. The input GSF format supports +;; indexed strips, which are de-indexed in this process. ;; ;; Each packet is assembled in one of the two scratchpad banks selected by saves.cur-outbuf: ;; ;; 1. A packet builder writes the outer DMA/VIF prefix, VU1 header, and per-strip A+D shaders. -;; 2. It reserves packed position, color, and texture-coordinate payloads and publishes their -;; scratchpad addresses through generic-saves. +;; 2. It reserves packed position, color, and texture-coordinate payloads and stores their +;; scratchpad addresses in generic-saves. ;; 3. The selected effect processors gather GSF vertices in draw-point order and fill those spans. ;; 4. The caller copies saves.qwc quadwords from cur-outbuf through fromSPR to saves.basep, the ;; current write cursor in the frame's main-memory DMA buffer, then flips the scratchpad bank. @@ -30,8 +29,9 @@ ;; Builders and processors take no arguments because generic-saves is their shared interface. Stream ;; sizes are rounded to four draw points, matching the original four-at-a-time processors and keeping ;; each payload qword-aligned; the final group may therefore consume converter-provided padding. -;; Each gsf-ik contributes an 8-bit vertex index and an 8-bit no-kick flag. The processors place the -;; flag in bit 0 of S, which VU1 removes from the coordinate and converts to the GS ADC bit. Authored +;; Each gsf-ik specifies an 8-bit vertex index and an 8-bit no-kick flag. The processors de-index +;; the vertices and plage the flag in bit 0 of S +;; which VU1 removes from the coordinate and converts to the GS ADC bit. Authored ;; texture coordinates must consequently keep bit 0 clear. ;; ;; Single-pass packet: @@ -47,6 +47,11 @@ ;; once and shaded twice. generic-light-proc fills the base streams; generic-envmap-proc fills the two ;; environment streams. +;; On PC, this is only used as a fallback from the more optimized C++ render when GOAL code +;; queries vertex positions. This only happens during enemy deaths and dark eco pools to find particle +;; spawn points. +;; Generic-tie is no longer supported on PC. + (define *target-lock* (the-as symbol 0)) ;; Immutable packet templates and arithmetic constants. generic-work-init copies this block into @@ -231,11 +236,11 @@ ;; Plain GOAL effect implementations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Portable versions of the packet builders and per-draw-point processors used by the PC converters. -;; They preserve the original scratchpad ABI: builders publish raw stream addresses in generic-saves, -;; processors fill those addresses, and the surrounding MERC/TIE code performs the fromSPR copy. +;; Simplified PC-port implementations. These are faster than MIPS2C and more understandable, +;; but lack the extreme optimization from the original game. See generic-effect-ee-asm.gc for +;; the original optimized version -;; Optional counters for comparing the portable effect path with the surrounding conversion cost. +;; Enable optional counters (define *generic-effect-stats* #f) (deftype generic-effect-debug-stats (structure) diff --git a/goal_src/jak1/engine/gfx/generic/generic-h.gc b/goal_src/jak1/engine/gfx/generic/generic-h.gc index 374bb7073d..4a8978455c 100644 --- a/goal_src/jak1/engine/gfx/generic/generic-h.gc +++ b/goal_src/jak1/engine/gfx/generic/generic-h.gc @@ -132,7 +132,7 @@ ;; the packet builders have to recompute the divisions. (deftype gsf-info (structure) ((ptr-iks uint32) ;; gsf-ik, one per draw point - (ptr-verts uint32) ;; gsf-vertex, one per vertex + (ptr-verts (inline-array gsf-vertex)) ;; one per vertex (ptr-fx uint32) ;; gsf-fx-vertex, one per draw point; environment-map pass only (dummy2 uint32))) diff --git a/goal_src/jak1/engine/gfx/generic/generic-merc-ee-asm.gc b/goal_src/jak1/engine/gfx/generic/generic-merc-ee-asm.gc new file mode 100644 index 0000000000..7e07a058b7 --- /dev/null +++ b/goal_src/jak1/engine/gfx/generic/generic-merc-ee-asm.gc @@ -0,0 +1,3215 @@ + +(#unless PC_PORT + ;; The program occupies addresses 280 through 469. Entry 280 initializes constant vectors and + ;; selects return addresses for one-, two-, or three-matrix skinning: three chained tests on the + ;; three counts, each skipping the assignment of that section's exit address when the section is + ;; empty, so the vertex loop can run straight through all three without a per-vertex branch on how + ;; many bones a vertex has. vi02, vi04 and vi06 end up holding the three section ends. + ;; + ;; Within a section, the reuse joins exist because MERC vertices frequently blend the same bones as + ;; the vertex before them; the ibeq on vi09 skips reloading the matrices and drops straight into the + ;; next section's stream. Entry 303 stores the seven + ;; current matrix vectors and primes scaled rows. Entry 311 expands weighted positions and + ;; normals: the packed indices select one to three matrix records, and the indirect return through + ;; vi01 advances through the matching pipeline without repeating common work. Entries 438 and 454 + ;; transform the first and second groups of four high-speed-reject test points and return their + ;; masked VU flag words in vi01 through vi08. + ;; + ;; vi08 is the output cursor during vertex conversion. vi10 walks packed control records, vi11 + ;; through vi13 hold matrix indices, and vi01 is the continuation address selected for the current + ;; influence count. vf08 through vf10 are the accumulated transform rows; vf11 through vf13 carry + ;; the converted vertex streams, vf16 holds the normalization sum, and vf17 supplies the fixed + ;; conversion scale. + (defvu0 mercneric-vu0-block + (vu-pair (0.003921569) (maxw.x vf17 vf00 vf00 :i)) ;; #x000 + (vu-pair (-65537.0) (maxi.y vf17 vf00 I :i)) ;; #x001 + (vu-pair (iaddiu vi10 vi10 0x88) (minii.z vf05 vf00 I)) ;; #x002 + (vu-pair (iaddiu vi08 vi00 0x8c) (minii.z vf06 vf00 I)) ;; #x003 + (vu-pair (sqi.xyzw vf01 vi08) (minii.z vf07 vf00 I)) ;; #x004 + (vu-pair (sqi.xyzw vf02 vi08) (minix.w vf05 vf00 vf27)) ;; #x005 + (vu-pair (sqi.xyzw vf03 vi08) (miniy.w vf06 vf00 vf27)) ;; #x006 + (vu-pair (sqi.xyzw vf04 vi08) (miniz.w vf07 vf00 vf27)) ;; #x007 + (vu-pair (ibne vi00 vi13 check-two-matrix-count) (nop)) ;; #x008 + (vu-pair (iaddiu vi05 vi00 0x182) (nop)) ;; #x009 + (vu-pair (iaddiu vi05 vi00 0x1ab) (nop)) ;; #x00a + (label check-two-matrix-count) + (vu-pair (ibne vi00 vi12 check-three-matrix-count) (nop)) ;; #x00b + (vu-pair (iaddiu vi03 vi00 0x146) (nop)) ;; #x00c + (vu-pair (ior vi03 vi05 vi00) (nop)) ;; #x00d + (label check-three-matrix-count) + (vu-pair (ibne vi00 vi11 stage-vertex-stream) (nop)) ;; #x00e + (vu-pair (iaddiu vi01 vi00 0x13a) (nop)) ;; #x00f + (vu-pair (ior vi01 vi03 vi00) (nop)) ;; #x010 + (label stage-vertex-stream) + (vu-pair (lqi.xyzw vf29 vi10) (nop)) ;; #x011 + (vu-pair (iadd vi02 vi08 vi11) (nop)) ;; #x012 + (vu-pair (iadd vi04 vi02 vi12) (nop)) ;; #x013 + (vu-pair (iadd vi06 vi04 vi13) (nop)) ;; #x014 + (vu-pair (mtir vi13 vf29.w) (itof0.xyzw vf18 vf29 :e)) ;; #x015 + (vu-pair (iaddiu vi09 vi00 0x7f) (nop)) ;; #x016 + (vu-pair (sq.xyzw vf23 vi14 3) (mulx.xyzw vf11 vf20 vf19)) ;; #x017 + (vu-pair (sq.xyzw vf24 vi14 4) (mulx.xyzw vf12 vf21 vf19)) ;; #x018 + (vu-pair (sq.xyzw vf25 vi14 5) (mulx.xyzw vf13 vf22 vf19)) ;; #x019 + (vu-pair (sq.xyzw vf26 vi14 6) (nop)) ;; #x01a + (vu-pair (sq.xyzw vf11 vi14 0) (nop)) ;; #x01b + (vu-pair (sq.xyzw vf12 vi14 1) (nop)) ;; #x01c + (vu-pair (sq.xyzw vf13 vi14 2) (nop :e)) ;; #x01d + (vu-pair (nop) (nop)) ;; #x01e + (vu-pair (mtir vi11 vf01.x) (maddz.xyzw vf11 vf26 vf10)) ;; #x01f + (vu-pair (jr vi01) (mul.xyzw vf14 vf13 Q)) ;; #x020 + (vu-pair (sqi.xyzw vf04 vi08) (mulaw.xyzw ACC vf20 vf08)) ;; #x021 + (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x022 + (vu-pair (lq.xyzw vf24 vi11 -124) (maddaw.xyzw ACC vf22 vf10)) ;; #x023 + (vu-pair (lq.xyzw vf25 vi11 -123) (maddw.xyzw vf15 vf23 vf00)) ;; #x024 + (vu-pair (lq.xyzw vf26 vi11 -122) (mul.xyzw vf16 vf11 vf11)) ;; #x025 + (vu-pair (lq.xyzw vf20 vi11 -128) (add.xyzw vf08 vf01 vf05)) ;; #x026 + (vu-pair (lq.xyzw vf21 vi11 -127) (add.xyzw vf09 vf02 vf06)) ;; #x027 + (vu-pair (lq.xyzw vf22 vi11 -126) (add.xyzw vf10 vf03 vf07)) ;; #x028 + (vu-pair (ibne vi08 vi02 skin-one-matrix-vertex) (adday.xyzw vf16 vf16)) ;; #x029 + (vu-pair (lq.xyzw vf23 vi11 -125) (maddz.xyzw vf16 vf17 vf16)) ;; #x02a + (vu-pair (ior vi01 vi03 vi00) (nop)) ;; #x02b + (label skin-one-matrix-vertex) + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x02c + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x02d + (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x02e + (vu-pair (mtir vi12 vf01.y) (maddaw.xyzw ACC vf22 vf10)) ;; #x02f + (vu-pair (iand vi11 vi11 vi09) (maddw.xyzw vf15 vf23 vf00)) ;; #x030 + (vu-pair (lq.xyzw vf19 vi11 4) (mul.xyzw vf16 vf11 vf11)) ;; #x031 + (vu-pair (ibeq vi09 vi11 skin-two-matrix-reuse) (add.xyzw vf08 vf01 vf05)) ;; #x032 + (vu-pair (iand vi12 vi12 vi09) (add.xyzw vf09 vf02 vf06)) ;; #x033 + (vu-pair (nop) (muly.xyzw vf18 vf18 vf17)) ;; #x034 + (vu-pair (lq.xyzw vf24 vi12 4) (add.xyzw vf10 vf03 vf07)) ;; #x035 + (vu-pair (lq.xyzw vf27 vi11 5) (adday.xyzw vf16 vf16)) ;; #x036 + (vu-pair (lq.xyzw vf25 vi12 5) (maddz.xyzw vf16 vf17 vf16)) ;; #x037 + (vu-pair (lq.xyzw vf28 vi11 6) (mulax.xyzw ACC vf19 vf18)) ;; #x038 + (vu-pair (lq.xyzw vf26 vi12 6) (maddy.xyzw vf24 vf24 vf18)) ;; #x039 + (vu-pair (lq.xyzw vf29 vi11 0) (mulax.xyzw ACC vf27 vf18)) ;; #x03a + (vu-pair (lq.xyzw vf20 vi12 0) (maddy.xyzw vf25 vf25 vf18)) ;; #x03b + (vu-pair (lq.xyzw vf19 vi11 1) (mulax.xyzw ACC vf28 vf18)) ;; #x03c + (vu-pair (lq.xyzw vf21 vi12 1) (maddy.xyzw vf26 vf26 vf18)) ;; #x03d + (vu-pair (lq.xyzw vf27 vi11 2) (mulax.xyzw ACC vf29 vf18)) ;; #x03e + (vu-pair (lq.xyzw vf22 vi12 2) (maddy.xyzw vf20 vf20 vf18)) ;; #x03f + (vu-pair (lq.xyzw vf28 vi11 3) (mulax.xyzw ACC vf19 vf18)) ;; #x040 + (vu-pair (lq.xyzw vf23 vi12 3) (maddy.xyzw vf21 vf21 vf18)) ;; #x041 + (vu-pair (nop) (mulax.xyzw ACC vf27 vf18)) ;; #x042 + (vu-pair (iaddiu vi01 vi00 0x161) (maddy.xyzw vf22 vf22 vf18)) ;; #x043 + (vu-pair (ibne vi08 vi04 skin-two-matrix-vertex) (mulax.xyzw ACC vf28 vf18)) ;; #x044 + (vu-pair (nop) (maddy.xyzw vf23 vf23 vf18)) ;; #x045 + (vu-pair (ior vi01 vi05 vi00) (nop)) ;; #x046 + (label skin-two-matrix-vertex) + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x047 + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x048 + (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x049 + (vu-pair (mtir vi12 vf01.y) (maddaw.xyzw ACC vf22 vf10)) ;; #x04a + (vu-pair (iand vi11 vi11 vi09) (maddw.xyzw vf15 vf23 vf00)) ;; #x04b + (vu-pair (lq.xyzw vf19 vi11 4) (mul.xyzw vf16 vf11 vf11)) ;; #x04c + (vu-pair (ibeq vi09 vi11 skin-two-matrix-reuse) (add.xyzw vf08 vf01 vf05)) ;; #x04d + (vu-pair (iand vi12 vi12 vi09) (add.xyzw vf09 vf02 vf06)) ;; #x04e + (vu-pair (lq.xyzw vf24 vi12 4) (add.xyzw vf10 vf03 vf07)) ;; #x04f + (vu-pair (lq.xyzw vf27 vi11 5) (adday.xyzw vf16 vf16)) ;; #x050 + (vu-pair (lq.xyzw vf25 vi12 5) (maddz.xyzw vf16 vf17 vf16)) ;; #x051 + (vu-pair (lq.xyzw vf28 vi11 6) (mulaz.xyzw ACC vf19 vf18)) ;; #x052 + (vu-pair (lq.xyzw vf26 vi12 6) (maddw.xyzw vf24 vf24 vf18)) ;; #x053 + (vu-pair (lq.xyzw vf29 vi11 0) (mulaz.xyzw ACC vf27 vf18)) ;; #x054 + (vu-pair (lq.xyzw vf20 vi12 0) (maddw.xyzw vf25 vf25 vf18)) ;; #x055 + (vu-pair (lq.xyzw vf19 vi11 1) (mulaz.xyzw ACC vf28 vf18)) ;; #x056 + (vu-pair (lq.xyzw vf21 vi12 1) (maddw.xyzw vf26 vf26 vf18)) ;; #x057 + (vu-pair (lq.xyzw vf27 vi11 2) (mulaz.xyzw ACC vf29 vf18)) ;; #x058 + (vu-pair (lq.xyzw vf22 vi12 2) (maddw.xyzw vf20 vf20 vf18)) ;; #x059 + (vu-pair (lq.xyzw vf28 vi11 3) (mulaz.xyzw ACC vf19 vf18)) ;; #x05a + (vu-pair (lq.xyzw vf23 vi12 3) (maddw.xyzw vf21 vf21 vf18)) ;; #x05b + (vu-pair (lqi.xyzw vf29 vi10) (mulaz.xyzw ACC vf27 vf18)) ;; #x05c + (vu-pair (iaddiu vi01 vi00 0x146) (maddw.xyzw vf22 vf22 vf18)) ;; #x05d + (vu-pair (ibne vi08 vi04 skin-two-matrix-reload) (mulaz.xyzw ACC vf28 vf18)) ;; #x05e + (vu-pair (nop) (maddw.xyzw vf23 vf23 vf18)) ;; #x05f + (vu-pair (ior vi01 vi05 vi00) (nop)) ;; #x060 + (label skin-two-matrix-reload) + (vu-pair (mtir vi13 vf29.w) (itof0.xyzw vf18 vf29)) ;; #x061 + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x062 + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x063 + (label skin-two-matrix-reuse) + (vu-pair (nop) (add.xyzw vf10 vf03 vf07)) ;; #x064 + (vu-pair (ibne vi08 vi04 skin-two-matrix-next) (adday.xyzw vf16 vf16)) ;; #x065 + (vu-pair (nop) (maddz.xyzw vf16 vf17 vf16)) ;; #x066 + (vu-pair (ior vi01 vi05 vi00) (nop)) ;; #x067 + (label skin-two-matrix-next) + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x068 + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x069 + (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x06a + (vu-pair (mtir vi12 vf01.y) (maddaw.xyzw ACC vf22 vf10)) ;; #x06b + (vu-pair (iand vi11 vi11 vi09) (maddw.xyzw vf15 vf23 vf00)) ;; #x06c + (vu-pair (lq.xyzw vf19 vi11 4) (mul.xyzw vf16 vf11 vf11)) ;; #x06d + (vu-pair (ibeq vi09 vi11 skin-three-matrix-reuse) (add.xyzw vf08 vf01 vf05)) ;; #x06e + (vu-pair (iand vi12 vi12 vi09) (add.xyzw vf09 vf02 vf06)) ;; #x06f + (vu-pair (nop) (muly.xyzw vf18 vf18 vf17)) ;; #x070 + (vu-pair (lq.xyzw vf27 vi12 4) (add.xyzw vf10 vf03 vf07)) ;; #x071 + (vu-pair (lq.xyzw vf24 vi13 4) (adday.xyzw vf16 vf16)) ;; #x072 + (vu-pair (lq.xyzw vf28 vi11 5) (maddz.xyzw vf16 vf17 vf16)) ;; #x073 + (vu-pair (lq.xyzw vf19 vi12 5) (mulax.xyzw ACC vf19 vf18)) ;; #x074 + (vu-pair (lq.xyzw vf25 vi13 5) (madday.xyzw ACC vf27 vf18)) ;; #x075 + (vu-pair (lq.xyzw vf27 vi11 6) (maddz.xyzw vf24 vf24 vf18)) ;; #x076 + (vu-pair (lq.xyzw vf28 vi12 6) (mulax.xyzw ACC vf28 vf18)) ;; #x077 + (vu-pair (lq.xyzw vf26 vi13 6) (madday.xyzw ACC vf19 vf18)) ;; #x078 + (vu-pair (lq.xyzw vf19 vi11 0) (maddz.xyzw vf25 vf25 vf18)) ;; #x079 + (vu-pair (lq.xyzw vf27 vi12 0) (mulax.xyzw ACC vf27 vf18)) ;; #x07a + (vu-pair (lq.xyzw vf20 vi13 0) (madday.xyzw ACC vf28 vf18)) ;; #x07b + (vu-pair (lq.xyzw vf28 vi11 1) (maddz.xyzw vf26 vf26 vf18)) ;; #x07c + (vu-pair (lq.xyzw vf19 vi12 1) (mulax.xyzw ACC vf19 vf18)) ;; #x07d + (vu-pair (lq.xyzw vf21 vi13 1) (madday.xyzw ACC vf27 vf18)) ;; #x07e + (vu-pair (lq.xyzw vf27 vi11 2) (maddz.xyzw vf20 vf20 vf18)) ;; #x07f + (vu-pair (lq.xyzw vf28 vi12 2) (mulax.xyzw ACC vf28 vf18)) ;; #x080 + (vu-pair (lq.xyzw vf22 vi13 2) (madday.xyzw ACC vf19 vf18)) ;; #x081 + (vu-pair (lq.xyzw vf19 vi11 3) (maddz.xyzw vf21 vf21 vf18)) ;; #x082 + (vu-pair (lq.xyzw vf27 vi12 3) (mulax.xyzw ACC vf27 vf18)) ;; #x083 + (vu-pair (lq.xyzw vf23 vi13 3) (madday.xyzw ACC vf28 vf18)) ;; #x084 + (vu-pair (lqi.xyzw vf29 vi10) (maddz.xyzw vf22 vf22 vf18)) ;; #x085 + (vu-pair (ibne vi08 vi06 skin-three-matrix-vertex) (mulax.xyzw ACC vf19 vf18)) ;; #x086 + (vu-pair (nop) (madday.xyzw ACC vf27 vf18)) ;; #x087 + (vu-pair (iaddiu vi01 vi00 0x1ab) (nop)) ;; #x088 + (label skin-three-matrix-vertex) + (vu-pair (nop) (maddz.xyzw vf23 vf23 vf18)) ;; #x089 + (vu-pair (mtir vi13 vf29.w) (itof0.xyz vf18 vf29)) ;; #x08a + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x08b + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x08c + (label skin-three-matrix-reuse) + (vu-pair (nop) (add.xyzw vf10 vf03 vf07)) ;; #x08d + (vu-pair (ibne vi08 vi06 skin-three-matrix-next) (adday.xyzw vf16 vf16)) ;; #x08e + (vu-pair (nop) (maddz.xyzw vf16 vf17 vf16)) ;; #x08f + (vu-pair (iaddiu vi01 vi00 0x1ab) (nop)) ;; #x090 + (label skin-three-matrix-next) + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x091 + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x092 + (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x093 + (vu-pair (nop) (maddaw.xyzw ACC vf22 vf10)) ;; #x094 + (vu-pair (nop) (maddw.xyzw vf15 vf23 vf00)) ;; #x095 + (vu-pair (nop) (mul.xyzw vf16 vf11 vf11)) ;; #x096 + (vu-pair (nop) (add.xyzw vf08 vf01 vf05)) ;; #x097 + (vu-pair (nop) (add.xyzw vf09 vf02 vf06)) ;; #x098 + (vu-pair (nop) (add.xyzw vf10 vf03 vf07)) ;; #x099 + (vu-pair (nop) (adday.xyzw vf16 vf16)) ;; #x09a + (vu-pair (nop) (maddz.xyzw vf16 vf17 vf16)) ;; #x09b + (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x09c + (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x09d + (vu-pair (nop) (mulaz.xyzw ACC vf09 vf01)) ;; #x09e + (vu-pair (nop) (maddax.xyzw ACC vf10 vf01)) ;; #x09f + (vu-pair (nop) (maddy.xyzw vf01 vf11 vf01)) ;; #x0a0 + (vu-pair (nop) (mulaz.xyzw ACC vf09 vf02)) ;; #x0a1 + (vu-pair (nop) (maddax.xyzw ACC vf10 vf02)) ;; #x0a2 + (vu-pair (nop) (maddy.xyzw vf02 vf11 vf02)) ;; #x0a3 + (vu-pair (fmand vi01 vi09) (mulaz.xyzw ACC vf09 vf03)) ;; #x0a4 + (vu-pair (nop) (maddax.xyzw ACC vf10 vf03)) ;; #x0a5 + (vu-pair (nop) (maddy.xyzw vf03 vf11 vf03)) ;; #x0a6 + (vu-pair (fmand vi02 vi09) (mulaz.xyzw ACC vf09 vf04)) ;; #x0a7 + (vu-pair (nop) (maddax.xyzw ACC vf10 vf04)) ;; #x0a8 + (vu-pair (nop) (maddy.xyzw vf04 vf11 vf04)) ;; #x0a9 + (vu-pair (fmand vi03 vi09) (nop)) ;; #x0aa + (vu-pair (nop) (nop)) ;; #x0ab + (vu-pair (nop) (nop :e)) ;; #x0ac + (vu-pair (fmand vi04 vi09) (nop)) ;; #x0ad + (vu-pair (nop) (mulaz.xyzw ACC vf09 vf05)) ;; #x0ae + (vu-pair (nop) (maddax.xyzw ACC vf10 vf05)) ;; #x0af + (vu-pair (nop) (maddy.xyzw vf05 vf11 vf05)) ;; #x0b0 + (vu-pair (nop) (mulaz.xyzw ACC vf09 vf06)) ;; #x0b1 + (vu-pair (nop) (maddax.xyzw ACC vf10 vf06)) ;; #x0b2 + (vu-pair (nop) (maddy.xyzw vf06 vf11 vf06)) ;; #x0b3 + (vu-pair (fmand vi05 vi09) (mulaz.xyzw ACC vf09 vf07)) ;; #x0b4 + (vu-pair (nop) (maddax.xyzw ACC vf10 vf07)) ;; #x0b5 + (vu-pair (nop) (maddy.xyzw vf07 vf11 vf07)) ;; #x0b6 + (vu-pair (fmand vi06 vi09) (mulaz.xyzw ACC vf09 vf08)) ;; #x0b7 + (vu-pair (nop) (maddax.xyzw ACC vf10 vf08)) ;; #x0b8 + (vu-pair (nop) (maddy.xyzw vf08 vf11 vf08)) ;; #x0b9 + (vu-pair (fmand vi07 vi09) (nop)) ;; #x0ba + (vu-pair (nop) (nop)) ;; #x0bb + (vu-pair (nop) (nop :e)) ;; #x0bc + (vu-pair (fmand vi08 vi09) (nop)) ;; #x0bd + ) + ) + + +(#unless PC_PORT + ;; These functions share the Generic renderer's fixed scratchpad layout. generic-merc-execute-asm + ;; overlaps main-memory DMA with conversion in two scratchpad input buffers. mercneric-convert + ;; builds the expanded Generic streams by invoking the bit-table, shader, and matrix stages; the + ;; matrix stage starts the VU0 program above while the EE prepares the next records. + (asm-data + (label generic-merc-one) + (word #x3f800000 0)) + (defun generic-merc-init-asm () + "Upload the MERC VU0 program at address 280, install the Generic conversion callbacks, copy + camera transforms and reject scales into scratchpad, and initialize both expanded-vertex work + buffers." + (declare (asm-func none) (allow-saved-regs)) + ;; The three camera matrices are copied because conversion runs entirely from scratchpad. + ;; hsr-xmult and hsr-ymult convert clip-space x and y into the sign tests used by + ;; high-speed-reject. The inverse tables map packed matrix references to expanded slots; both + ;; output buffers receive identical seeds so either half of the double buffer can start first. + (add.i sp sp -16) + (s.d ra sp) + (s.d fp sp 8) + (m fp t9) + (m! t9 upload-vu0-program) + (m! a0 mercneric-vu0-block) + (lui v1 #x7000) + (ori a1 v1 100) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! v1 mercneric-convert) + (lui a0 #x7000) + (s.w v1 a0 (generic-work-offset in-buf merc shadow mercneric-convert)) + (m! v1 generic-prepare-dma-single) + (lui a0 #x7000) + (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-prepare-dma-single)) + (m! v1 generic-prepare-dma-double) + (lui a0 #x7000) + (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-prepare-dma-double)) + (m! v1 generic-light-proc) + (lui a0 #x7000) + (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-light-proc)) + (m! v1 generic-envmap-proc) + (lui a0 #x7000) + (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-envmap-proc)) + (m! v1 high-speed-reject) + (lui a0 #x7000) + (s.w v1 a0 (generic-work-offset in-buf merc shadow high-speed-reject)) + (m! v1 *math-camera*) + (add.i a0 r0 #x1b60) + (lui a1 #x7000) + (add a3 a0 a1) + (add.i t0 v1 156) + (l.q a0 t0) + (l.q a1 t0 16) + (l.q a2 t0 32) + (l.q t0 t0 48) + (s.q a0 a3) + (s.q a1 a3 16) + (s.q a2 a3 32) + (s.q t0 a3 48) + (add.i a0 r0 #x1ba0) + (lui a1 #x7000) + (add a3 a0 a1) + (add.i t0 v1 220) + (l.q a0 t0) + (l.q a1 t0 16) + (l.q a2 t0 32) + (l.q t0 t0 48) + (s.q a0 a3) + (s.q a1 a3 16) + (s.q a2 a3 32) + (s.q t0 a3 48) + (add.i a0 r0 #x1be0) + (lui a1 #x7000) + (add a3 a0 a1) + (add.i t0 v1 428) + (l.q a0 t0) + (l.q a1 t0 16) + (l.q a2 t0 32) + (l.q t0 t0 48) + (s.q a0 a3) + (s.q a1 a3 16) + (s.q a2 a3 32) + (s.q t0 a3 48) + (l.s f0 fp generic-merc-one) + (l.s f1 v1 12) + (div.s f1 f0 f1) + (l.s f0 fp generic-merc-one) + (l.s f2 v1 16) + (div.s f0 f0 f2) + (add.i v1 r0 #x1cc0) + (lui a0 #x7000) + (add a0 v1 a0) + (add.i v1 r0 #x1cd0) + (lui a1 #x7000) + (add v1 v1 a1) + (s.s f1 a0) + (neg.s f1 f1) + (s.s f1 a0 4) + (m f1 r0) + (s.s f1 a0 8) + (m f1 r0) + (s.s f1 a0 12) + (m f1 r0) + (s.s f1 v1) + (m f1 r0) + (s.s f1 v1 4) + (s.s f0 v1 8) + (neg.s f0 f0) + (s.s f0 v1 12) + (m! v1 *gsf-buffer*) + (add.i a0 v1 32) + (s.w a0 v1) + (add.i a0 v1 448) + (s.w a0 v1 4) + (add.i a0 v1 #x19d0) + (s.w a0 v1 8) + (add.i a0 v1 2784) + (s.w a0 v1 2752) + (add.i a0 v1 3200) + (s.w a0 v1 2756) + (add.i a0 v1 #x19d0) + (s.w a0 v1 2760) + (m! v1 *gsf-buffer*) + (add.i a0 v1 #x1580) + (m a0 a0) + (m! a1 *inv-init-table*) + (m a1 a1) + (s.q r0 a0) + (s.q r0 a0 544) + (s.q r0 a0 16) + (s.q r0 a0 560) + (s.q r0 a0 208) + (s.q r0 a0 752) + (s.q r0 a0 224) + (s.q r0 a0 768) + (s.q r0 a0 240) + (s.q r0 a0 784) + (s.q r0 a0 384) + (s.q r0 a0 800) + (s.q r0 a0 400) + (s.q r0 a0 944) + (s.q r0 a0 416) + (s.q r0 a0 960) + (s.q r0 a0 432) + (s.q r0 a0 976) + (s.q r0 a0 448) + (s.q r0 a0 992) + (s.q r0 a0 464) + (s.q r0 a0 1008) + (s.q r0 a0 480) + (s.q r0 a0 1024) + (s.q r0 a0 496) + (s.q r0 a0 1040) + (s.q r0 a0 512) + (s.q r0 a0 1056) + (s.q r0 a0 528) + (s.q r0 a0 1072) + (add.i a2 r0 8) + (nop!) + (label merc-init-seed-next-matrix-row) + (l.hu t1 a1 2) + (add.i a2 a2 -1) + (l.bu a3 a1) + (nop!) + (l.bu t0 a1 1) + (nop!) + (add t1 t1 a0) + (add.i a1 a1 4) + (label merc-init-write-inverse-pair) + (s.b t0 t1) + (add.i a3 a3 -1) + (s.b t0 t1 550) + (add.i t0 t0 2) + (add.i t1 t1 3) + (nop!) + (b.nz a3 merc-init-write-inverse-pair :delay (nop!)) + (b.nz a2 merc-init-seed-next-matrix-row :delay (nop!)) + (add a0 r0 v1) + (m a0 a0) + (s.q r0 a0 416) + (s.q r0 a0 432) + (add.i v1 v1 2752) + (m v1 v1) + (s.q r0 v1 416) + (s.q r0 v1 432) + (m v1 r0) + (lui v1 #x1000) + (ori v1 v1 #x8000) + (lui a0 #x7000) + (ori a0 a0 100) + (l.w a1 v1) + (and.i a1 a1 DMA-CHCR-STR) + (b.z a1 merc-init-done :delay (at-label merc-init-poll-vu0-upload (l.w a1 a0))) + (nop!) + (l.w a2 v1) + (nop!) + (and.i a2 a2 DMA-CHCR-STR) + (add.i a1 a1 1) + (b.nz a2 merc-init-poll-vu0-upload :delay (s.w a1 a0)) + (label merc-init-done) + (m v1 r0) + (m v0 r0) + (l.d ra sp) + (l.d fp sp 8) + (jr ra :delay (add.i sp sp 16)) + + ) + + (defun mercneric-bittable-asm () + "Expand the fragment's packed kick-suppression bits into the high byte of each gsf-ik record." + (declare (asm-func none) (allow-saved-regs)) + ;; MERC keeps one bit per draw point saying whether that point suppresses the strip kick, packed + ;; sixteen to a halfword at merc-fp-header.kick-info-offset. GENERIC wants a whole byte for it, in + ;; the second half of each gsf-ik record, so the bits have to be spread out by eight. + ;; + ;; Three chained pextlb replicate one byte into all sixteen lanes, an AND against a one-bit-per-lane + ;; ladder leaves a distinct bit alive in each, pceqb turns "set" into all ones and a second AND + ;; reduces that to 0x01. Widening the result to halfwords puts the flag where gsf-ik.no-kick lives + ;; and leaves gsf-ik.index at zero for the vertex stage to fill in - sixteen flags, thirty-two + ;; bytes of index/kick list, no branches inside the pass. + (nop!) + (lui v1 #x7000) + (nop!) + (l.w a1 v1 (generic-work-offset in-buf merc shadow p-fheader)) + (nop!) + (l.w a0 v1 (generic-work-offset in-buf merc shadow indexed-output-base)) + (nop!) + (l.bu a2 a1 13) + (nop!) + (l.bu v1 a1 14) + (add.i a0 a0 32) + (l.bu a3 a1 15) + (add a1 a1 a2) + (mult3 a3 v1 a3) + (m a1 a1) + (m v1 v1) + (m a0 a0) + (add.i a2 r0 513) + (add.i t0 r0 257) + (sll t2 a2 18) + (sll t1 t0 16) + (or a2 a2 t2) + (or t0 t0 t1) + (dsll32 t2 a2 4) + (dsll32 t1 t0 0) + (or a2 a2 t2) + (or t0 t0 t1) + (pcpyld a2 a2 a2) + (mmi-nop!) + (pcpyld t0 t0 t0) + (mmi-nop!) + (add a3 a3 a1) + (mmi-nop!) + (l.hu t1 a1) + (add a1 a1 v1) + (nop!) + (mmi-nop!) + (nop!) + (pextlb t1 t1 t1) + (b mnb-expand-16-flags :delay (pextlb t1 t1 t1)) + ;; Loop head: step the bitfield source by kick-info-step, the list by 32 bytes, run the third + ;; replication, and lazily store the previous pass's second quadword. + (asm-block next-hword + (label mnb-next-hword) + (add a1 a1 v1) + (add.i a0 a0 32) + (pextlb t1 t1 t1) + (s.q v0 a0 -16)) + ;; Sixteen flags out of one halfword: mask against the bit ladder, pceqb back to all-ones, + ;; reduce to 0x01, then widen to halfwords so each flag lands in gsf-ik.no-kick with the + ;; index byte left at zero for the vertex stage to fill in. + (asm-block expand-16-flags + (label mnb-expand-16-flags) + (pextlb t1 t1 t1) + (mmi-nop!) + (and.q t1 t1 a2) + (mmi-nop!) + (pceqb t1 t1 a2) + (mmi-nop!) + (and.q t3 t1 t0) + (mmi-nop!) + (pextlb t1 t3 r0) + (l.hu t2 a1) + (pextub v0 t3 r0) + (s.q t1 a0) + (b.ne a1 a3 mnb-next-hword :delay (pextlb t1 t2 t2)) + (nop!) + (s.q v0 a0 16) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!)) + ) + + ;; Builds the outgoing packet header and shader list for one fragment, and assembles the fragment's + ;; inverse table. The header is the 128-byte generic-texbuf prologue - DMA tag, four camera-matrix + ;; rows, the strip GIF tag, and two A+D quadwords whose GS registers are selected out of + ;; consts.adcmd-offsets by the envmap and translucency flags. Each shader is then copied across with + ;; its two software-owned words patched. A fragment that starts mid-strip prepends a copy of the + ;; previous fragment's shader, because a GENERIC packet cannot inherit GS state the way MERC's could. + (defun mercneric-shader-asm () + "Copy the current fragment shader records into the Generic input packet and patch their packet + addresses and effect references." + (declare (asm-func none) (allow-saved-regs)) + ;; These values cannot be baked into the level data: packet addresses depend on the current + ;; scratchpad half, while effect records select per-draw lighting, environment mapping, + ;; blending, and texture state. Copy the compact source records, expand their GIF/VIF fields, + ;; and link each effect to the packet data produced for this draw. + (add.i sp sp -64) + (s.d ra sp) + (s.q s4 sp 16) + (s.q s5 sp 32) + (s.q gp sp 48) + (lui t2 #x7000) + (add.i a0 r0 0) + (l.bu a1 t2 (generic-work-offset in-buf merc ctrl effect envmap-usage)) + (add.i a2 r0 4) + (l.w t0 t2 (generic-work-offset in-buf merc shadow p-input)) + (nop!) + (l.w v1 t2 (generic-work-offset in-buf merc shadow gsf-buf)) + (sub t4 a1 r0) + (l.w a1 t2 (generic-work-offset saves cur-outbuf)) + (movn a0 a2 t4) + (l.bu t3 t2 (generic-work-offset in-buf merc ctrl header use-translucent)) + (add t1 a0 t2) + (l.w a0 t2 (generic-work-offset in-buf merc shadow indexed-input-base)) + (add.i a2 a1 128) + (l.bu a3 t0 10) + (sll t5 t3 1) + (l.bu t3 t0 9) + (add t1 t1 t5) + (l.bu t5 t1 #x2eb1) + (nop!) + (l.bu t6 t1 #x2eb0) + (nop!) + (l.w t1 t2 (generic-work-offset in-buf merc shadow other-input-base)) + (sll t7 a3 4) + (add t6 t6 t2) + (add t8 t5 t2) + (l.w a3 t2 (generic-work-offset in-buf merc shadow indexed-output-base)) + (add t5 t7 t0) + (s.w t5 t2 (generic-work-offset in-buf merc shadow p-fheader)) + (nop!) + (l.bu t0 t5 12) + (add.i t7 r0 0) + (l.q t8 t8 #x2e70) + (movn t7 r0 t4) + (l.q t4 t6 #x2e70) + (movz t7 r0 t3) + (s.q t8 a1 96) + (add.i t6 v1 #x1580) + (s.q t4 a1 112) + (add.i v1 v1 #x17a0) + (s.w t7 a1 124) + (movn v1 t6 t3) + (l.q gp t2 (generic-work-offset fx-buf work consts)) + (add.i t6 t5 16) + (l.q s4 t2 (generic-work-offset fx-buf work consts matrix vector 0)) + (add.i t8 r0 0) + (l.q s5 t2 (generic-work-offset fx-buf work consts matrix vector 1)) + (add.i t5 r0 0) + (l.q t9 t2 (generic-work-offset fx-buf work consts matrix vector 2)) + (add.i t4 r0 -3) + (l.q ra t2 (generic-work-offset fx-buf work consts matrix vector 3)) + (add.i t7 r0 6) + (l.q t2 t2 (generic-work-offset fx-buf work consts base-strgif)) + (nop!) + (s.q gp a1) + (movz t7 r0 t3) + (s.q s4 a1 16) + (add.i gp r0 0) + (s.q s5 a1 32) + (add.i s5 r0 -32768) + (s.q t9 a1 48) + (movz gp s5 t0) + (s.q ra a1 64) + (nop!) + (b.z t3 mns-first-shader-from-fragment :delay (s.q t2 a1 80)) + (l.q t9 t1 2896) + (add gp t3 gp) + (l.q ra t1 2912) + (add.i s5 r0 -6) + (l.q t3 t1 2928) + (add.i t0 t0 1) + (l.q t2 t1 2944) + (add.i s4 t0 -3) + (b.le s4 r0 mns-store-shader :delay (l.q t1 t1 2960)) + (add.i t8 t0 -5) + (nop!) + (b.le t8 r0 mns-store-shader :delay (l.h t8 t6 172)) + (b mns-store-shader :delay (l.h t5 t6 332)) + ;; A fragment that starts a strip takes its first shader from its own data, so the two section + ;; boundaries come out of source shaders 3 and 5 rather than 2 and 4. + (asm-block first-shader-from-fragment + (label mns-first-shader-from-fragment) + (add.i t1 t0 -3) + (nop!) + (b.le t1 r0 mns-load-source-shader :delay (add.i t1 t0 -5)) + (b.le t1 r0 mns-load-source-shader :delay (l.h t8 t6 252)) + (nop!) + (l.h t5 t6 412)) + ;; One source shader: its kick offset, its strip length, and its five quadwords. + (asm-block load-source-shader + (label mns-load-source-shader) + (l.h s5 t6 12) + (nop!) + (l.h gp t6 28) + (nop!) + (l.q t9 t6) + (nop!) + (l.q ra t6 16) + (nop!) + (l.q t3 t6 32) + (nop!) + (l.q t2 t6 48) + (nop!) + (l.q t1 t6 64) + (add.i t6 t6 80)) + ;; Copy it out and patch the two words the software owns - the running kick offset in this + ;; record, and the strip length in the previous one. The draw-point total accumulates + ;; modulo 256 because that is the field width in the packet header. + (asm-block store-shader + (label mns-store-shader) + (s.q t9 a2) + (add s5 s5 t7) + (s.q ra a2 16) + (add.i t7 t7 2) + (s.q t3 a2 32) + (add t4 t4 gp) + (s.q t2 a2 48) + (add.i t4 t4 3) + (s.q t1 a2 64) + (and.i t4 t4 255) + (s.w s5 a2 12) + (add.i a2 a2 80) + (b.gt gp r0 mns-load-source-shader :delay (s.w gp a2 -52)) + (s.w t4 a1 108) + (add.i a1 t8 7) + (s.q t9 a0 2896) + (add.i t5 t5 7) + (s.q ra a0 2912) + (sra a2 a1 4) + (s.q t3 a0 2928) + (sra t3 t5 4) + (s.q t2 a0 2944) + (add.i a1 a3 192) + (s.q t1 a0 2960) + (sll a0 t0 2) + (s.h t4 a3 18) + (add a0 a0 t0) + (s.b t0 a3 16) + (add.i a0 a0 7) + (sub a0 t3 a2) + (l.q t0 v1) + (nop!) + (nop!) + (add.i t1 a2 -3) + (l.q a3 v1 16) + (nop!) + (s.q t0 a1) + (b.z t1 mns-inv-copy-b-1 :delay (l.q a2 v1 32)) + (add.i t0 t1 -1) + (s.q a3 a1 16) + (b.z t0 mns-inv-copy-b-2 :delay (l.q a3 v1 48)) + (add.i t0 t0 -1) + (s.q a2 a1 32) + (b.z t0 mns-inv-copy-b-3 :delay (l.q a2 v1 64)) + (add.i t0 t0 -1) + (s.q a3 a1 48) + (b.z t0 mns-inv-copy-b-4 :delay (l.q a3 v1 80)) + (add.i t0 t0 -1) + (s.q a2 a1 64) + (b.z t0 mns-inv-copy-b-5 :delay (l.q a2 v1 96)) + (add.i t0 t0 -1) + (s.q a3 a1 80) + (b.z t0 mns-inv-copy-b-6 :delay (l.q a3 v1 112)) + (add.i t0 t0 -1) + (s.q a2 a1 96) + (b.z t0 mns-inv-copy-b-7 :delay (l.q a2 v1 128)) + (add.i t0 t0 -1) + (s.q a3 a1 112) + (b.z t0 mns-inv-copy-b-8 :delay (l.q a3 v1 144)) + (add.i t0 t0 -1) + (s.q a2 a1 128) + (b.z t0 mns-inv-copy-b-9 :delay (l.q a2 v1 160)) + (add.i t0 t0 -1) + (s.q a3 a1 144) + (b.z t0 mns-inv-copy-b-10 :delay (l.q a3 v1 176)) + (add.i a0 t0 -1) + (s.q a2 a1 160) + (b.z a0 mns-inv-copy-b-11 :delay (l.q a0 v1 192)) + (nop!) + (s.q a3 a1 176) + (nop!) + (l.q v1 v1 208) + (nop!) + (s.q a0 a1 192) + (nop!) + (nop!) + (nop!) + (s.q v1 a1 208) + (b mns-shaders-done :delay (nop!))) + ;; The inverse table is gathered out of three regions of the template generic-merc-init-asm + ;; built: region A at +0, region B at +176, region C at +320. They are the same logical + ;; table with different gsf-ik bases, one per matrix-influence section, and the + ;; destination stays contiguous. The unrolled ladders below are one rung per quadword + ;; with a computed entry point, which is how a variable-length copy costs no branch. + (asm-block inv-copy-b-1 + (label mns-inv-copy-b-1) + (add.i a0 a0 -1) + (s.q a3 a1 16) + (nop!) + (l.q a3 v1 224)) + (asm-block inv-copy-b-2 + (label mns-inv-copy-b-2) + (add.i a0 a0 -1) + (s.q a2 a1 32) + (b.z a0 mns-inv-copy-c-1 :delay (l.q a2 v1 240))) + (asm-block inv-copy-b-3 + (label mns-inv-copy-b-3) + (add.i a0 a0 -1) + (s.q a3 a1 48) + (b.z a0 mns-inv-copy-c-2 :delay (l.q a3 v1 256))) + (asm-block inv-copy-b-4 + (label mns-inv-copy-b-4) + (add.i a0 a0 -1) + (s.q a2 a1 64) + (b.z a0 mns-inv-copy-c-3 :delay (l.q a2 v1 272))) + (asm-block inv-copy-b-5 + (label mns-inv-copy-b-5) + (add.i a0 a0 -1) + (s.q a3 a1 80) + (b.z a0 mns-inv-copy-c-4 :delay (l.q a3 v1 288))) + (asm-block inv-copy-b-6 + (label mns-inv-copy-b-6) + (add.i a0 a0 -1) + (s.q a2 a1 96) + (b.z a0 mns-inv-copy-c-5 :delay (l.q a2 v1 304))) + (asm-block inv-copy-b-7 + (label mns-inv-copy-b-7) + (add.i a0 a0 -1) + (s.q a3 a1 112) + (b.z a0 mns-inv-copy-c-6 :delay (l.q a3 v1 320))) + (asm-block inv-copy-b-8 + (label mns-inv-copy-b-8) + (add.i a0 a0 -1) + (s.q a2 a1 128) + (b.z a0 mns-inv-copy-c-7 :delay (l.q a2 v1 336))) + (asm-block inv-copy-b-9 + (label mns-inv-copy-b-9) + (add.i a0 a0 -1) + (s.q a3 a1 144) + (b.z a0 mns-inv-copy-c-8 :delay (l.q a3 v1 352))) + (asm-block inv-copy-b-10 + (label mns-inv-copy-b-10) + (add.i a0 a0 -1) + (s.q a2 a1 160) + (b.z a0 mns-inv-copy-c-9 :delay (l.q a0 v1 368))) + (asm-block inv-copy-b-11 + (label mns-inv-copy-b-11) + (nop!) + (s.q a3 a1 176) + (nop!) + (l.q v1 v1 384) + (nop!) + (s.q a0 a1 192) + (nop!) + (nop!) + (nop!) + (s.q v1 a1 208) + (b mns-shaders-done :delay (nop!))) + (asm-block inv-copy-c-1 + (label mns-inv-copy-c-1) + (nop!) + (s.q a3 a1 48) + (nop!) + (l.q a3 v1 400)) + (asm-block inv-copy-c-2 + (label mns-inv-copy-c-2) + (nop!) + (s.q a2 a1 64) + (nop!) + (l.q a2 v1 416)) + (asm-block inv-copy-c-3 + (label mns-inv-copy-c-3) + (nop!) + (s.q a3 a1 80) + (nop!) + (l.q a3 v1 432)) + (asm-block inv-copy-c-4 + (label mns-inv-copy-c-4) + (nop!) + (s.q a2 a1 96) + (nop!) + (l.q a2 v1 448)) + (asm-block inv-copy-c-5 + (label mns-inv-copy-c-5) + (nop!) + (s.q a3 a1 112) + (nop!) + (l.q a3 v1 464)) + (asm-block inv-copy-c-6 + (label mns-inv-copy-c-6) + (nop!) + (s.q a2 a1 128) + (nop!) + (l.q a2 v1 480)) + (asm-block inv-copy-c-7 + (label mns-inv-copy-c-7) + (nop!) + (s.q a3 a1 144) + (nop!) + (l.q a3 v1 496)) + (asm-block inv-copy-c-8 + (label mns-inv-copy-c-8) + (nop!) + (s.q a2 a1 160) + (nop!) + (l.q a0 v1 512)) + (asm-block inv-copy-c-9 + (label mns-inv-copy-c-9) + (nop!) + (s.q a3 a1 176) + (nop!) + (l.q v1 v1 528) + (nop!) + (s.q a0 a1 192) + (nop!) + (nop!) + (nop!) + (s.q v1 a1 208)) + ;; Shader list and inverse table complete. + (asm-block shaders-done + (label mns-shaders-done) + (m v0 r0) + (l.d ra sp) + (l.q gp sp 48) + (l.q s5 sp 32) + (l.q s4 sp 16) + (jr ra :delay (add.i sp sp 64)) + (nop!) + (nop!) + (nop!)) + ) + + ;; Despite the name, this is the whole vertex pipeline: matrix upload, then conversion, then the two + ;; vertex-sharing fixups. It sizes VU0's working set from the byte header, hands VU0 the fragment's + ;; position bias and the first sixteen bone-weight bytes and calls entry 280 to pick the one, two or + ;; three matrix continuation, uploads each bone matrix through entry 303, and then runs four packed + ;; vertices per pass through entry 311 while the EE converts texture coordinates, gathers palette + ;; colors and writes gsf-vertex records. samecopy and crosscopy afterwards handle vertices shared + ;; within this fragment and with the previous one. + (defun mercneric-matrix-asm () + "Expand the current fragment matrix data into the scratchpad table and run the MERC VU0 vertex + conversion entries." + (declare (asm-func none) (allow-saved-regs)) + ;; Matrix indices and weights arrive packed into the fragment header. The EE expands matrix + ;; rows into VU memory, starts entry 280 once to initialize the microprogram, and uses entry + ;; 303 for each matrix group. Entry 311 then consumes packed vertices. Its continuation + ;; address selects the one-, two-, or three-matrix path while the EE advances the source and + ;; destination cursors for the next group. + ;; + ;; The weight quadwords below are read from fixed +16, +32 and +48 of the byte header, which + ;; equals perc-off * 4 only when perc-off is 4 - and those bytes overlap mat-slot, declared ten + ;; long. Either a fragment never fills more than about three matrix slots, so the list's zero + ;; terminator doubles as the first weight byte, or the declared array length is too generous. + ;; The code alone does not decide it, so these three keep their numbers. + (add.i sp sp -128) + (s.q s0 sp 16) + (s.q s1 sp 32) + (s.q s2 sp 48) + (s.q s3 sp 64) + (s.q s4 sp 80) + (s.q s5 sp 96) + (s.q gp sp 112) + (nop!) + (lui s4 #x7000) + (nop!) + (l.w a0 s4 (generic-work-offset in-buf merc shadow p-input)) + (nop!) + (l.w a1 s4 (generic-work-offset in-buf merc shadow indexed-output-base)) + (nop!) + (l.q t8 a0 16) + (add.i s6 a0 48) + (l.bu a3 a0 12) + (nop!) + (l.bu t2 a0 10) + (pextlb t9 r0 t8) + (ctc2.ni vi10 a3) + (pextlh gp r0 t9) + (m.ni vf1 gp) + (pextuh t9 r0 t9) + (m.ni vf2 t9) + (pextub t9 r0 t8) + (l.bu a3 a0 4) + (pextlh gp r0 t9) + (m.ni vf3 gp) + (pextuh t9 r0 t9) + (m.ni vf4 t9) + (sll t2 t2 4) + (l.bu t0 a0 5) + (add t2 t2 a0) + (l.bu t1 a0 6) + (add.i s5 a3 -1) + (ctc2.ni vi11 a3) + (add s5 s5 t0) + (ctc2.ni vi12 t0) + (add s5 s5 t1) + (ctc2.ni vi13 t1) + (add.i t4 a1 32) + (l.vf vf27 t2) + (add.i t3 a1 192) + (callms 280) + (add.i v1 a1 448) + (l.hu s1 s4 (generic-work-offset in-buf merc ctrl header st-int-off)) + (pextlw t3 t3 t3) + (l.hu s2 s4 (generic-work-offset in-buf merc ctrl header st-int-scale)) + (pcpyld t3 t3 t3) + (l.vf vf19 s4 (generic-work-offset in-buf merc ctrl header)) + (pcpyh s1 s1) + (l.bu v0 a0 2) + (add.i t5 r0 0) + (l.bu at a0 1) + (add.i s4 r0 -1) + (lui a1 #x4b01) + (add.i v0 v0 3) + (add.i a1 a1 #x4780) + (and.i v0 v0 252) + (sll at at 2) + (sll v0 v0 2) + (l.q t8 a0 32) + (add v0 v0 a0) + (l.bu t0 a0 13) + (add at at a0) + (l.bu t2 a0 11) + (pextlw a1 a1 r0) + (b.z t0 mnm-convert-prologue :delay (pcpyld a1 a1 a1)) + (sll t2 t2 4) + (add.i a3 a0 14) + (b mnm-load-matrix-rows :delay (add t2 t2 a0)) + ;; Walk the fragment's mat-slot list until the zero terminator, handing each bone matrix to + ;; VU0 entry 303 with its destination slot in vi14. + (asm-block upload-next-matrix + (label mnm-upload-next-matrix) + (callms 303) + (m t0 t1)) + (asm-block load-matrix-rows + (label mnm-load-matrix-rows) + (l.bu t1 a3) + (add.i a3 a3 1) + (l.vf vf23 t2 64) + (nop!) + (l.vf vf24 t2 80) + (nop!) + (l.vf vf25 t2 96) + (nop!) + (l.vf vf26 t2 112) + (nop!) + (l.vf vf20 t2 16) + (nop!) + (l.vf vf21 t2 32) + (nop!) + (l.vf vf22 t2 48) + (add.i t2 t2 128) + (b.nz t1 mnm-upload-next-matrix :delay (ctc2.ni vi14 t0)) + (callms 303) + (nop!)) + ;; The software-pipelined ramp-up: four calls to entry 311 with progressively more of the loop + ;; body filled in behind them. + (asm-block convert-prologue + (label mnm-convert-prologue) + (l.q a2 v0) + (pextlb t9 r0 t8) + (mmi-nop!) + (pextlh gp r0 t9) + (mmi-nop!) + (pextlb a3 r0 a2) + (mmi-nop!) + (pextub a2 r0 a2) + (m.ni vf4 gp) + (pextlh t0 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 a3) + (pextlh t1 a1 a2) + (m.ni vf3 t1) + (pextuh t9 r0 t9) + (callms 311) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (l.q t2 v0 16) + (nop!) + (add.i at at -16) + (nop!) + (mmi-nop!) + (pextuh a2 a1 a2) + (mmi-nop!) + (pextlb t0 r0 t2) + (m.ni vf4 t9) + (pextlh t1 a1 t0) + (m.ni vf1 a2) + (pextuh t0 a1 t0) + (m.ni vf2 t1) + (pextub t9 r0 t8) + (m.ni vf3 t0) + (pextlh gp r0 t9) + (nop!) + (add.i v0 v0 -48) + (callms 311) + (nop!) + (add.i t6 r0 0) + (nop!) + (l.wl t6 v0 53) + (nop!) + (nop!) + (nop!) + (mmi-nop!) + (pextlb t6 r0 t6) + (mmi-nop!) + (pextlb t6 r0 t6) + (l.q a3 v0 80) + (add.w t6 t6 t3) + (nop!) + (nop!) + (mmi-nop!) + (pextub t2 r0 t2) + (mmi-nop!) + (pextlh t0 a1 t2) + (m.ni vf4 gp) + (pextuh t2 a1 t2) + (m.ni vf1 t0) + (pextlb t1 r0 a3) + (m.ni vf2 t2) + (pextlh a2 a1 t1) + (m.ni vf3 a2) + (pextuh t9 r0 t9) + (nop!) + (add.i v1 v1 -64) + (nop!) + (s.vf vf15 v1 64) + (callms 311) + (nop!) + (l.wr s3 v0 56) + (nop!) + (l.wl s3 v0 69) + (nop!) + (mmi-nop!) + (pcpyud t6 t6 r0) + (mmi-nop!) + (pextlb s3 r0 s3) + (nop!) + (dsllv s3 s3 s2) + (l.q t8 s6) + (add.h s3 s3 s1) + (l.bu t7 t6) + (srl32 t6 t6 0) + (mmi-nop!) + (pextuh t0 a1 t1) + (l.bu s0 t6) + (pextub a3 r0 a3) + (m.ni vf4 t9) + (pextlh t2 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 t2) + (pextlb t9 r0 t8) + (m.ni vf3 a3) + (pextlh gp r0 t9) + (nop!) + (add.i s6 s6 16) + (nop!) + (s.vf vf15 v1 96) + (callms 311) + (s.w s3 v1 76) + (l.wr t6 v0 64) + (add t7 t7 t4) + (l.wl t6 v0 77) + (add s0 s0 t4) + (b mnm-convert-loop-mid :delay (srl32 s3 s3 0))) + ;; Steady state, four vertices per pass. The packed record's bytes are widened with + ;; pextlb/pextlh and blended with 0x4b014780 so that bytes 2 and 3 arrive at VU0 already + ;; in float form - the exponent is supplied by the constant and the mantissa is the byte. + ;; + ;; The destination lookup is the trick worth reading: four destination bytes widened into + ;; four 32-bit lanes, one packed add against the replicated inverse-table base, and four + ;; pointers come out of one register through srl32 and pcpyud. The store order inside a + ;; vertex is forced by the 16-byte s.vf overwriting the texture coordinate. + (asm-block convert-loop + (label mnm-convert-loop) + (callms 311) + (s.w s3 v1 76) + (l.wr t6 v0 64) + (add t7 t7 t4) + (l.wl t6 v0 77) + (add s0 s0 t4) + (s.w a0 v1 60) + (srl32 s3 s3 0)) + (asm-block convert-loop-mid + (label mnm-convert-loop-mid) + (s.b t5 t7) + (pextlb t6 r0 t6) + (s.b t5 s0) + (pextlb t6 r0 t6) + (l.q a2 v0 96) + (add.w t6 t6 t3) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 16) + (pextlb a3 r0 a2) + (l.bu s0 t6) + (pextub a2 r0 a2) + (m.ni vf4 gp) + (pextlh t0 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 a3) + (pextlh t1 a1 a2) + (m.ni vf3 t1) + (pextuh t9 r0 t9) + (s.vf vf14 v1 80) + (add.i at at 16) + (add.i s4 s4 2) + (s.vf vf15 v1 128) + (b.eq t5 s5 mnm-convert-tail-2 :delay (add.i t5 t5 2)) + (callms 311) + (s.w s3 v1 108) + (l.wr s3 v0 80) + (add t7 t7 t4) + (l.wl s3 v0 93) + (add s0 s0 t4) + (s.w a0 v1 92) + (pcpyud t6 t6 r0) + (s.b s4 t7) + (pextlb s3 r0 s3) + (s.b s4 s0) + (dsllv s3 s3 s2) + (l.q t2 v0 112) + (add.h s3 s3 s1) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 4) + (pextuh a2 a1 a2) + (l.bu s0 t6) + (pextlb t0 r0 t2) + (m.ni vf4 t9) + (pextlh t1 a1 t0) + (m.ni vf1 a2) + (pextuh t0 a1 t0) + (m.ni vf2 t1) + (pextub t9 r0 t8) + (m.ni vf3 t0) + (pextlh gp r0 t9) + (s.vf vf14 v1 112) + (add.i v0 v0 48) + (b.eq s4 s5 mnm-convert-tail-3 :delay (s.vf vf15 v1 160)) + (callms 311) + (s.w s3 v1 140) + (l.wr t6 v0 40) + (add t7 t7 t4) + (l.wl t6 v0 53) + (add s0 s0 t4) + (s.w a0 v1 124) + (srl32 s3 s3 0) + (s.b t5 t7) + (pextlb t6 r0 t6) + (s.b t5 s0) + (pextlb t6 r0 t6) + (l.q a3 v0 80) + (add.w t6 t6 t3) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 8) + (pextub t2 r0 t2) + (l.bu s0 t6) + (pextlh t0 a1 t2) + (m.ni vf4 gp) + (pextuh t2 a1 t2) + (m.ni vf1 t0) + (pextlb t1 r0 a3) + (m.ni vf2 t2) + (pextlh a2 a1 t1) + (m.ni vf3 a2) + (pextuh t9 r0 t9) + (s.vf vf14 v1 144) + (add.i v1 v1 128) + (add.i s4 s4 2) + (s.vf vf15 v1 64) + (b.eq t5 s5 mnm-convert-done :delay (add.i t5 t5 2)) + (callms 311) + (s.w s3 v1 44) + (l.wr s3 v0 56) + (add t7 t7 t4) + (l.wl s3 v0 69) + (add s0 s0 t4) + (s.w a0 v1 28) + (pcpyud t6 t6 r0) + (s.b s4 t7) + (pextlb s3 r0 s3) + (s.b s4 s0) + (dsllv s3 s3 s2) + (l.q t8 s6) + (add.h s3 s3 s1) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 12) + (pextuh t0 a1 t1) + (l.bu s0 t6) + (pextub a3 r0 a3) + (m.ni vf4 t9) + (pextlh t2 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 t2) + (pextlb t9 r0 t8) + (m.ni vf3 a3) + (pextlh gp r0 t9) + (s.vf vf14 v1 48) + (add.i s6 s6 16) + (b.ne s4 s5 mnm-convert-loop :delay (s.vf vf15 v1 96)) + (b mnm-convert-done :delay (add.i v1 v1 32))) + ;; Three exits, one per partial group size, each fixing up the output cursor before the + ;; shared drain. + (asm-block convert-tail-2 + (label mnm-convert-tail-2) + (b mnm-convert-done :delay (add.i v1 v1 64))) + (asm-block convert-tail-3 + (label mnm-convert-tail-3) + (b mnm-convert-done :delay (add.i v1 v1 96))) + (asm-block convert-done + (label mnm-convert-done) + (s.w a0 v1 28) + (add.i v1 v1 32) + (add.i s5 s5 1) + (nop!) + (lui a3 #x7000) + (nop!) + (l.w a0 a3 (generic-work-offset in-buf merc shadow p-input)) + (nop!) + (l.w a1 a3 (generic-work-offset in-buf merc shadow indexed-output-base)) + (nop!) + (l.w a3 a3 #x1c90) + (nop!) + (l.bu s0 a0) + (add.i s4 r0 0) + (l.bu s1 a0 7) + (add.i t8 r0 0) + (l.bu s2 a0 8) + (sll s0 s0 2) + (add s0 s0 a0) + (sll s1 s1 2) + (b.z s1 mnm-crosscopy-setup :delay (add s1 s1 s0))) + ;; samecopy: a vertex drawn twice in this fragment needs only a second index entry. + (asm-block samecopy-loop + (label mnm-samecopy-loop) + (l.bu s3 s0) + (add s4 s4 t4) + (l.bu s6 s0 1) + (add t8 t8 t4) + (l.bu t9 s4) + (add s3 s3 t3) + (l.bu s4 s3) + (add s6 s6 t3) + (s.b t9 t8) + (add.i s0 s0 4) + (b.ne s0 s1 mnm-samecopy-loop :delay (l.bu t8 s6)) + (nop!) + (add s4 s4 t4) + (l.bu t9 s4) + (add t8 t8 t4) + (nop!) + (nop!) + (s.b t9 t8) + (nop!)) + ;; crosscopy: a vertex shared with the *previous* fragment has to be physically copied out + ;; of the other output buffer, because that buffer is about to be reused. + (asm-block crosscopy-setup + (label mnm-crosscopy-setup) + (sll s2 s2 2) + (add.i t5 a3 32) + (b.z s2 mnm-store-vtx-count :delay (add s2 s2 s1)) + (l.bu s3 s0) + (add.i t6 a3 192) + (l.bu s6 s0 1) + (add.i t7 a3 448) + (nop!) + (add s3 s3 t6) + (l.bu s3 s3) + (add s6 s6 t3) + (l.bu s6 s6) + (add.i v1 v1 -32) + (nop!) + (add s3 s3 t5) + (l.bu s4 s3) + (add s6 s6 t4) + (nop!) + (add.i s0 s0 4) + (s.b s5 s6) + (add.i s5 s5 1) + (b.eq s0 s2 mnm-crosscopy-drain :delay (nop!))) + (asm-block crosscopy-loop + (label mnm-crosscopy-loop) + (l.bu s3 s0) + (sll s4 s4 5) + (l.bu s6 s0 1) + (add s4 s4 t7) + (l.q t9 s4) + (add s3 s3 t6) + (l.bu s3 s3) + (add s6 s6 t3) + (l.bu s6 s6) + (add.i v1 v1 32) + (l.q gp s4 16) + (add s3 s3 t5) + (l.bu s4 s3) + (add s6 s6 t4) + (s.q t9 v1) + (add.i s0 s0 4) + (s.b s5 s6) + (add.i s5 s5 1) + (b.ne s0 s2 mnm-crosscopy-loop :delay (s.q gp v1 16))) + (asm-block crosscopy-drain + (label mnm-crosscopy-drain) + (nop!) + (sll s4 s4 5) + (nop!) + (add s4 s4 t7) + (l.q t9 s4) + (nop!) + (nop!) + (nop!) + (nop!) + (add.i v1 v1 32) + (l.q gp s4 16) + (nop!) + (nop!) + (nop!) + (s.q t9 v1) + (nop!) + (nop!) + (nop!) + (nop!) + (s.q gp v1 16)) + ;; Publish the vertex total the packet builder will unpack. + (asm-block store-vtx-count + (label mnm-store-vtx-count) + (s.h s5 a1 20) + (m v0 s5) + (l.q gp sp 112) + (l.q s5 sp 96) + (l.q s4 sp 80) + (l.q s3 sp 64) + (l.q s2 sp 48) + (l.q s1 sp 32) + (l.q s0 sp 16) + (jr ra :delay (add.i sp sp 128)) + (nop!) + (nop!) + (nop!)) + ) + + ;; The per-fragment driver, and simply the three stages above inlined back to back with no calls: + ;; shader list and inverse table, then the kick bits, then the matrices and vertices. Reached through + ;; a function pointer out of gm-shadow, which is why it saves its registers into the scratchpad work + ;; area rather than onto the stack. Worth reading against the three standalone copies, which carry + ;; the per-block explanations; the only differences here are register assignment and the dropped + ;; return value. + (defun mercneric-convert () + "Convert one packed MERC fragment into Generic matrix, shader, and expanded vertex streams while + overlapping EE work with VU0 conversion." + (declare (asm-func none) (allow-saved-regs)) + ;; The packed fragment is split into matrix references, shader/effect records, and vertex + ;; blocks. Build the lookup and shader portions first, then alternate VU0 conversion with EE + ;; copies so the next block is ready when the current microprogram finishes. The final Generic + ;; header records the expanded stream sizes and the next output cursor. + (lui at #x7000) + (s.q s0 at (generic-work-offset in-buf merc stack 0)) + (s.q s1 at (generic-work-offset in-buf merc stack 1)) + (s.q s2 at (generic-work-offset in-buf merc stack 2)) + (s.q s3 at (generic-work-offset in-buf merc stack 3)) + (s.q s4 at (generic-work-offset in-buf merc stack 4)) + (s.q s5 at (generic-work-offset in-buf merc stack 5)) + (s.q s6 at (generic-work-offset in-buf merc stack 6)) + (s.q t8 at (generic-work-offset in-buf merc stack 7)) + (s.q t9 at (generic-work-offset in-buf merc stack 8)) + (s.q gp at (generic-work-offset in-buf merc stack 9)) + (s.q sp at (generic-work-offset in-buf merc stack 10)) + (s.q fp at (generic-work-offset in-buf merc stack 11)) + (s.q ra at (generic-work-offset in-buf merc stack 12)) + (nop!) + (lui t2 #x7000) + (add.i a0 r0 0) + (l.bu a1 t2 (generic-work-offset in-buf merc ctrl effect envmap-usage)) + (add.i a2 r0 4) + (l.w t0 t2 (generic-work-offset in-buf merc shadow p-input)) + (nop!) + (l.w v1 t2 (generic-work-offset in-buf merc shadow gsf-buf)) + (sub t4 a1 r0) + (l.w a1 t2 (generic-work-offset saves cur-outbuf)) + (movn a0 a2 t4) + (l.bu t3 t2 (generic-work-offset in-buf merc ctrl header use-translucent)) + (add t1 a0 t2) + (l.w a0 t2 (generic-work-offset in-buf merc shadow indexed-input-base)) + (add.i a2 a1 128) + (l.bu a3 t0 10) + (sll t5 t3 1) + (l.bu t3 t0 9) + (add t1 t1 t5) + (l.bu t5 t1 #x2eb1) + (nop!) + (l.bu t6 t1 #x2eb0) + (nop!) + (l.w t1 t2 (generic-work-offset in-buf merc shadow other-input-base)) + (sll t7 a3 4) + (add t6 t6 t2) + (add t8 t5 t2) + (l.w a3 t2 (generic-work-offset in-buf merc shadow indexed-output-base)) + (add t5 t7 t0) + (s.w t5 t2 (generic-work-offset in-buf merc shadow p-fheader)) + (nop!) + (l.bu t0 t5 12) + (add.i t7 r0 0) + (l.q t8 t8 #x2e70) + (movn t7 r0 t4) + (l.q t4 t6 #x2e70) + (movz t7 r0 t3) + (s.q t8 a1 96) + (add.i t6 v1 #x1580) + (s.q t4 a1 112) + (add.i v1 v1 #x17a0) + (s.w t7 a1 124) + (movn v1 t6 t3) + (l.q gp t2 (generic-work-offset fx-buf work consts)) + (add.i t6 t5 16) + (l.q s4 t2 (generic-work-offset fx-buf work consts matrix vector 0)) + (add.i t8 r0 0) + (l.q s5 t2 (generic-work-offset fx-buf work consts matrix vector 1)) + (add.i t5 r0 0) + (l.q t9 t2 (generic-work-offset fx-buf work consts matrix vector 2)) + (add.i t4 r0 -3) + (l.q ra t2 (generic-work-offset fx-buf work consts matrix vector 3)) + (add.i t7 r0 6) + (l.q t2 t2 (generic-work-offset fx-buf work consts base-strgif)) + (nop!) + (s.q gp a1) + (movz t7 r0 t3) + (s.q s4 a1 16) + (add.i gp r0 0) + (s.q s5 a1 32) + (add.i s5 r0 -32768) + (s.q t9 a1 48) + (movz gp s5 t0) + (s.q ra a1 64) + (nop!) + (b.z t3 mnc-first-shader-from-fragment :delay (s.q t2 a1 80)) + (l.q t9 t1 2896) + (add gp t3 gp) + (l.q ra t1 2912) + (add.i s5 r0 -6) + (l.q t3 t1 2928) + (add.i t0 t0 1) + (l.q t2 t1 2944) + (add.i s4 t0 -3) + (b.le s4 r0 mnc-store-shader :delay (l.q t1 t1 2960)) + (add.i t8 t0 -5) + (nop!) + (b.le t8 r0 mnc-store-shader :delay (l.h t8 t6 172)) + (b mnc-store-shader :delay (l.h t5 t6 332)) + (asm-block first-shader-from-fragment + (label mnc-first-shader-from-fragment) + (add.i t1 t0 -3) + (nop!) + (b.le t1 r0 mnc-load-source-shader :delay (add.i t1 t0 -5)) + (b.le t1 r0 mnc-load-source-shader :delay (l.h t8 t6 252)) + (nop!) + (l.h t5 t6 412)) + (asm-block load-source-shader + (label mnc-load-source-shader) + (l.h s5 t6 12) + (nop!) + (l.h gp t6 28) + (nop!) + (l.q t9 t6) + (nop!) + (l.q ra t6 16) + (nop!) + (l.q t3 t6 32) + (nop!) + (l.q t2 t6 48) + (nop!) + (l.q t1 t6 64) + (add.i t6 t6 80)) + (asm-block store-shader + (label mnc-store-shader) + (s.q t9 a2) + (add s5 s5 t7) + (s.q ra a2 16) + (add.i t7 t7 2) + (s.q t3 a2 32) + (add t4 t4 gp) + (s.q t2 a2 48) + (add.i t4 t4 3) + (s.q t1 a2 64) + (and.i t4 t4 255) + (s.w s5 a2 12) + (add.i a2 a2 80) + (b.gt gp r0 mnc-load-source-shader :delay (s.w gp a2 -52)) + (s.w t4 a1 108) + (add.i a1 t8 7) + (s.q t9 a0 2896) + (add.i t5 t5 7) + (s.q ra a0 2912) + (sra a2 a1 4) + (s.q t3 a0 2928) + (sra t3 t5 4) + (s.q t2 a0 2944) + (add.i a1 a3 192) + (s.q t1 a0 2960) + (sll a0 t0 2) + (s.h t4 a3 18) + (add a0 a0 t0) + (s.b t0 a3 16) + (add.i a0 a0 7) + (sub a0 t3 a2) + (l.q t0 v1) + (nop!) + (nop!) + (add.i t1 a2 -3) + (l.q a3 v1 16) + (nop!) + (s.q t0 a1) + (b.z t1 mnc-inv-copy-b-1 :delay (l.q a2 v1 32)) + (add.i t0 t1 -1) + (s.q a3 a1 16) + (b.z t0 mnc-inv-copy-b-2 :delay (l.q a3 v1 48)) + (add.i t0 t0 -1) + (s.q a2 a1 32) + (b.z t0 mnc-inv-copy-b-3 :delay (l.q a2 v1 64)) + (add.i t0 t0 -1) + (s.q a3 a1 48) + (b.z t0 mnc-inv-copy-b-4 :delay (l.q a3 v1 80)) + (add.i t0 t0 -1) + (s.q a2 a1 64) + (b.z t0 mnc-inv-copy-b-5 :delay (l.q a2 v1 96)) + (add.i t0 t0 -1) + (s.q a3 a1 80) + (b.z t0 mnc-inv-copy-b-6 :delay (l.q a3 v1 112)) + (add.i t0 t0 -1) + (s.q a2 a1 96) + (b.z t0 mnc-inv-copy-b-7 :delay (l.q a2 v1 128)) + (add.i t0 t0 -1) + (s.q a3 a1 112) + (b.z t0 mnc-inv-copy-b-8 :delay (l.q a3 v1 144)) + (add.i t0 t0 -1) + (s.q a2 a1 128) + (b.z t0 mnc-inv-copy-b-9 :delay (l.q a2 v1 160)) + (add.i t0 t0 -1) + (s.q a3 a1 144) + (b.z t0 mnc-inv-copy-b-10 :delay (l.q a3 v1 176)) + (add.i a0 t0 -1) + (s.q a2 a1 160) + (b.z a0 mnc-inv-copy-b-11 :delay (l.q a0 v1 192)) + (nop!) + (s.q a3 a1 176) + (nop!) + (l.q v1 v1 208) + (nop!) + (s.q a0 a1 192) + (nop!) + (nop!) + (nop!) + (s.q v1 a1 208) + (b mnc-shaders-done :delay (nop!))) + (asm-block inv-copy-b-1 + (label mnc-inv-copy-b-1) + (add.i a0 a0 -1) + (s.q a3 a1 16) + (nop!) + (l.q a3 v1 224)) + (asm-block inv-copy-b-2 + (label mnc-inv-copy-b-2) + (add.i a0 a0 -1) + (s.q a2 a1 32) + (b.z a0 mnc-inv-copy-c-1 :delay (l.q a2 v1 240))) + (asm-block inv-copy-b-3 + (label mnc-inv-copy-b-3) + (add.i a0 a0 -1) + (s.q a3 a1 48) + (b.z a0 mnc-inv-copy-c-2 :delay (l.q a3 v1 256))) + (asm-block inv-copy-b-4 + (label mnc-inv-copy-b-4) + (add.i a0 a0 -1) + (s.q a2 a1 64) + (b.z a0 mnc-inv-copy-c-3 :delay (l.q a2 v1 272))) + (asm-block inv-copy-b-5 + (label mnc-inv-copy-b-5) + (add.i a0 a0 -1) + (s.q a3 a1 80) + (b.z a0 mnc-inv-copy-c-4 :delay (l.q a3 v1 288))) + (asm-block inv-copy-b-6 + (label mnc-inv-copy-b-6) + (add.i a0 a0 -1) + (s.q a2 a1 96) + (b.z a0 mnc-inv-copy-c-5 :delay (l.q a2 v1 304))) + (asm-block inv-copy-b-7 + (label mnc-inv-copy-b-7) + (add.i a0 a0 -1) + (s.q a3 a1 112) + (b.z a0 mnc-inv-copy-c-6 :delay (l.q a3 v1 320))) + (asm-block inv-copy-b-8 + (label mnc-inv-copy-b-8) + (add.i a0 a0 -1) + (s.q a2 a1 128) + (b.z a0 mnc-inv-copy-c-7 :delay (l.q a2 v1 336))) + (asm-block inv-copy-b-9 + (label mnc-inv-copy-b-9) + (add.i a0 a0 -1) + (s.q a3 a1 144) + (b.z a0 mnc-inv-copy-c-8 :delay (l.q a3 v1 352))) + (asm-block inv-copy-b-10 + (label mnc-inv-copy-b-10) + (add.i a0 a0 -1) + (s.q a2 a1 160) + (b.z a0 mnc-inv-copy-c-9 :delay (l.q a0 v1 368))) + (asm-block inv-copy-b-11 + (label mnc-inv-copy-b-11) + (nop!) + (s.q a3 a1 176) + (nop!) + (l.q v1 v1 384) + (nop!) + (s.q a0 a1 192) + (nop!) + (nop!) + (nop!) + (s.q v1 a1 208) + (b mnc-shaders-done :delay (nop!))) + (asm-block inv-copy-c-1 + (label mnc-inv-copy-c-1) + (nop!) + (s.q a3 a1 48) + (nop!) + (l.q a3 v1 400)) + (asm-block inv-copy-c-2 + (label mnc-inv-copy-c-2) + (nop!) + (s.q a2 a1 64) + (nop!) + (l.q a2 v1 416)) + (asm-block inv-copy-c-3 + (label mnc-inv-copy-c-3) + (nop!) + (s.q a3 a1 80) + (nop!) + (l.q a3 v1 432)) + (asm-block inv-copy-c-4 + (label mnc-inv-copy-c-4) + (nop!) + (s.q a2 a1 96) + (nop!) + (l.q a2 v1 448)) + (asm-block inv-copy-c-5 + (label mnc-inv-copy-c-5) + (nop!) + (s.q a3 a1 112) + (nop!) + (l.q a3 v1 464)) + (asm-block inv-copy-c-6 + (label mnc-inv-copy-c-6) + (nop!) + (s.q a2 a1 128) + (nop!) + (l.q a2 v1 480)) + (asm-block inv-copy-c-7 + (label mnc-inv-copy-c-7) + (nop!) + (s.q a3 a1 144) + (nop!) + (l.q a3 v1 496)) + (asm-block inv-copy-c-8 + (label mnc-inv-copy-c-8) + (nop!) + (s.q a2 a1 160) + (nop!) + (l.q a0 v1 512)) + (asm-block inv-copy-c-9 + (label mnc-inv-copy-c-9) + (nop!) + (s.q a3 a1 176) + (nop!) + (l.q v1 v1 528) + (nop!) + (s.q a0 a1 192) + (nop!) + (nop!) + (nop!) + (s.q v1 a1 208)) + (asm-block shaders-done + (label mnc-shaders-done) + (lui v1 #x7000) + (nop!) + (l.w a1 v1 (generic-work-offset in-buf merc shadow p-fheader)) + (nop!) + (l.w a0 v1 (generic-work-offset in-buf merc shadow indexed-output-base)) + (nop!) + (l.bu a2 a1 13) + (nop!) + (l.bu v1 a1 14) + (add.i a0 a0 32) + (l.bu a3 a1 15) + (add a1 a1 a2) + (mult3 a3 v1 a3) + (m a1 a1) + (m v1 v1) + (m a0 a0) + (add.i a2 r0 513) + (add.i t0 r0 257) + (sll t2 a2 18) + (sll t1 t0 16) + (or a2 a2 t2) + (or t0 t0 t1) + (dsll32 t2 a2 4) + (dsll32 t1 t0 0) + (or a2 a2 t2) + (or t0 t0 t1) + (pcpyld a2 a2 a2) + (mmi-nop!) + (pcpyld t0 t0 t0) + (mmi-nop!) + (add a3 a3 a1) + (mmi-nop!) + (l.hu t1 a1) + (add a1 a1 v1) + (nop!) + (mmi-nop!) + (nop!) + (pextlb t1 t1 t1) + (b mnc-expand-16-flags :delay (pextlb t2 t1 t1))) + (asm-block next-hword + (label mnc-next-hword) + (add a1 a1 v1) + (add.i a0 a0 32) + (pextlb t2 t2 t2) + (s.q t1 a0 -16)) + (asm-block expand-16-flags + (label mnc-expand-16-flags) + (pextlb t1 t2 t2) + (mmi-nop!) + (and.q t1 t1 a2) + (mmi-nop!) + (pceqb t1 t1 a2) + (mmi-nop!) + (and.q t1 t1 t0) + (mmi-nop!) + (pextlb t2 t1 r0) + (l.hu t3 a1) + (pextub t1 t1 r0) + (s.q t2 a0) + (b.ne a1 a3 mnc-next-hword :delay (pextlb t2 t3 t3)) + (nop!) + (s.q t1 a0 16) + (lui s4 #x7000) + (nop!) + (l.w a0 s4 (generic-work-offset in-buf merc shadow p-input)) + (nop!) + (l.w a1 s4 (generic-work-offset in-buf merc shadow indexed-output-base)) + (nop!) + (l.q t8 a0 16) + (add.i s6 a0 48) + (l.bu a3 a0 12) + (nop!) + (l.bu t2 a0 10) + (pextlb t9 r0 t8) + (ctc2.ni vi10 a3) + (pextlh gp r0 t9) + (m.ni vf1 gp) + (pextuh t9 r0 t9) + (m.ni vf2 t9) + (pextub t9 r0 t8) + (l.bu a3 a0 4) + (pextlh gp r0 t9) + (m.ni vf3 gp) + (pextuh t9 r0 t9) + (m.ni vf4 t9) + (sll t2 t2 4) + (l.bu t0 a0 5) + (add t2 t2 a0) + (l.bu t1 a0 6) + (add.i s5 a3 -1) + (ctc2.ni vi11 a3) + (add s5 s5 t0) + (ctc2.ni vi12 t0) + (add s5 s5 t1) + (ctc2.ni vi13 t1) + (add.i t4 a1 32) + (l.vf vf27 t2) + (add.i t3 a1 192) + (callms 280) + (add.i v1 a1 448) + (l.hu s1 s4 (generic-work-offset in-buf merc ctrl header st-int-off)) + (pextlw t3 t3 t3) + (l.hu s2 s4 (generic-work-offset in-buf merc ctrl header st-int-scale)) + (pcpyld t3 t3 t3) + (l.vf vf19 s4 (generic-work-offset in-buf merc ctrl header)) + (pcpyh s1 s1) + (l.bu v0 a0 2) + (add.i t5 r0 0) + (l.bu at a0 1) + (add.i s4 r0 -1) + (lui a1 #x4b01) + (add.i v0 v0 3) + (add.i a1 a1 #x4780) + (and.i v0 v0 252) + (sll at at 2) + (sll v0 v0 2) + (l.q t8 a0 32) + (add v0 v0 a0) + (l.bu t0 a0 13) + (add at at a0) + (l.bu t2 a0 11) + (pextlw a1 a1 r0) + (b.z t0 mnc-convert-prologue :delay (pcpyld a1 a1 a1)) + (sll t2 t2 4) + (add.i a3 a0 14) + (b mnc-load-matrix-rows :delay (add t2 t2 a0))) + (asm-block upload-next-matrix + (label mnc-upload-next-matrix) + (callms 303) + (m t0 t1)) + (asm-block load-matrix-rows + (label mnc-load-matrix-rows) + (l.bu t1 a3) + (add.i a3 a3 1) + (l.vf vf23 t2 64) + (nop!) + (l.vf vf24 t2 80) + (nop!) + (l.vf vf25 t2 96) + (nop!) + (l.vf vf26 t2 112) + (nop!) + (l.vf vf20 t2 16) + (nop!) + (l.vf vf21 t2 32) + (nop!) + (l.vf vf22 t2 48) + (add.i t2 t2 128) + (b.nz t1 mnc-upload-next-matrix :delay (ctc2.ni vi14 t0)) + (callms 303) + (nop!)) + (asm-block convert-prologue + (label mnc-convert-prologue) + (l.q a2 v0) + (pextlb t9 r0 t8) + (mmi-nop!) + (pextlh gp r0 t9) + (mmi-nop!) + (pextlb a3 r0 a2) + (mmi-nop!) + (pextub a2 r0 a2) + (m.ni vf4 gp) + (pextlh t0 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 a3) + (pextlh t1 a1 a2) + (m.ni vf3 t1) + (pextuh t9 r0 t9) + (callms 311) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (l.q t2 v0 16) + (nop!) + (add.i at at -16) + (nop!) + (mmi-nop!) + (pextuh a2 a1 a2) + (mmi-nop!) + (pextlb t0 r0 t2) + (m.ni vf4 t9) + (pextlh t1 a1 t0) + (m.ni vf1 a2) + (pextuh t0 a1 t0) + (m.ni vf2 t1) + (pextub t9 r0 t8) + (m.ni vf3 t0) + (pextlh gp r0 t9) + (nop!) + (add.i v0 v0 -48) + (callms 311) + (nop!) + (add.i t6 r0 0) + (nop!) + (l.wl t6 v0 53) + (nop!) + (nop!) + (nop!) + (mmi-nop!) + (pextlb t6 r0 t6) + (mmi-nop!) + (pextlb t6 r0 t6) + (l.q a3 v0 80) + (add.w t6 t6 t3) + (nop!) + (nop!) + (mmi-nop!) + (pextub t2 r0 t2) + (mmi-nop!) + (pextlh t0 a1 t2) + (m.ni vf4 gp) + (pextuh t2 a1 t2) + (m.ni vf1 t0) + (pextlb t1 r0 a3) + (m.ni vf2 t2) + (pextlh a2 a1 t1) + (m.ni vf3 a2) + (pextuh t9 r0 t9) + (nop!) + (add.i v1 v1 -64) + (nop!) + (s.vf vf15 v1 64) + (callms 311) + (nop!) + (l.wr s3 v0 56) + (nop!) + (l.wl s3 v0 69) + (nop!) + (mmi-nop!) + (pcpyud t6 t6 r0) + (mmi-nop!) + (pextlb s3 r0 s3) + (nop!) + (dsllv s3 s3 s2) + (l.q t8 s6) + (add.h s3 s3 s1) + (l.bu t7 t6) + (srl32 t6 t6 0) + (mmi-nop!) + (pextuh t0 a1 t1) + (l.bu s0 t6) + (pextub a3 r0 a3) + (m.ni vf4 t9) + (pextlh t2 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 t2) + (pextlb t9 r0 t8) + (m.ni vf3 a3) + (pextlh gp r0 t9) + (nop!) + (add.i s6 s6 16) + (nop!) + (s.vf vf15 v1 96) + (callms 311) + (s.w s3 v1 76) + (l.wr t6 v0 64) + (add t7 t7 t4) + (l.wl t6 v0 77) + (add s0 s0 t4) + (b mnc-convert-loop-mid :delay (srl32 s3 s3 0))) + (asm-block convert-loop + (label mnc-convert-loop) + (callms 311) + (s.w s3 v1 76) + (l.wr t6 v0 64) + (add t7 t7 t4) + (l.wl t6 v0 77) + (add s0 s0 t4) + (s.w a0 v1 60) + (srl32 s3 s3 0)) + (asm-block convert-loop-mid + (label mnc-convert-loop-mid) + (s.b t5 t7) + (pextlb t6 r0 t6) + (s.b t5 s0) + (pextlb t6 r0 t6) + (l.q a2 v0 96) + (add.w t6 t6 t3) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 16) + (pextlb a3 r0 a2) + (l.bu s0 t6) + (pextub a2 r0 a2) + (m.ni vf4 gp) + (pextlh t0 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 a3) + (pextlh t1 a1 a2) + (m.ni vf3 t1) + (pextuh t9 r0 t9) + (s.vf vf14 v1 80) + (add.i at at 16) + (add.i s4 s4 2) + (s.vf vf15 v1 128) + (b.eq t5 s5 mnc-convert-tail-2 :delay (add.i t5 t5 2)) + (callms 311) + (s.w s3 v1 108) + (l.wr s3 v0 80) + (add t7 t7 t4) + (l.wl s3 v0 93) + (add s0 s0 t4) + (s.w a0 v1 92) + (pcpyud t6 t6 r0) + (s.b s4 t7) + (pextlb s3 r0 s3) + (s.b s4 s0) + (dsllv s3 s3 s2) + (l.q t2 v0 112) + (add.h s3 s3 s1) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 4) + (pextuh a2 a1 a2) + (l.bu s0 t6) + (pextlb t0 r0 t2) + (m.ni vf4 t9) + (pextlh t1 a1 t0) + (m.ni vf1 a2) + (pextuh t0 a1 t0) + (m.ni vf2 t1) + (pextub t9 r0 t8) + (m.ni vf3 t0) + (pextlh gp r0 t9) + (s.vf vf14 v1 112) + (add.i v0 v0 48) + (b.eq s4 s5 mnc-convert-tail-3 :delay (s.vf vf15 v1 160)) + (callms 311) + (s.w s3 v1 140) + (l.wr t6 v0 40) + (add t7 t7 t4) + (l.wl t6 v0 53) + (add s0 s0 t4) + (s.w a0 v1 124) + (srl32 s3 s3 0) + (s.b t5 t7) + (pextlb t6 r0 t6) + (s.b t5 s0) + (pextlb t6 r0 t6) + (l.q a3 v0 80) + (add.w t6 t6 t3) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 8) + (pextub t2 r0 t2) + (l.bu s0 t6) + (pextlh t0 a1 t2) + (m.ni vf4 gp) + (pextuh t2 a1 t2) + (m.ni vf1 t0) + (pextlb t1 r0 a3) + (m.ni vf2 t2) + (pextlh a2 a1 t1) + (m.ni vf3 a2) + (pextuh t9 r0 t9) + (s.vf vf14 v1 144) + (add.i v1 v1 128) + (add.i s4 s4 2) + (s.vf vf15 v1 64) + (b.eq t5 s5 mnc-convert-done :delay (add.i t5 t5 2)) + (callms 311) + (s.w s3 v1 44) + (l.wr s3 v0 56) + (add t7 t7 t4) + (l.wl s3 v0 69) + (add s0 s0 t4) + (s.w a0 v1 28) + (pcpyud t6 t6 r0) + (s.b s4 t7) + (pextlb s3 r0 s3) + (s.b s4 s0) + (dsllv s3 s3 s2) + (l.q t8 s6) + (add.h s3 s3 s1) + (l.bu t7 t6) + (srl32 t6 t6 0) + (l.w a0 at 12) + (pextuh t0 a1 t1) + (l.bu s0 t6) + (pextub a3 r0 a3) + (m.ni vf4 t9) + (pextlh t2 a1 a3) + (m.ni vf1 t0) + (pextuh a3 a1 a3) + (m.ni vf2 t2) + (pextlb t9 r0 t8) + (m.ni vf3 a3) + (pextlh gp r0 t9) + (s.vf vf14 v1 48) + (add.i s6 s6 16) + (b.ne s4 s5 mnc-convert-loop :delay (s.vf vf15 v1 96)) + (b mnc-convert-done :delay (add.i v1 v1 32))) + (asm-block convert-tail-2 + (label mnc-convert-tail-2) + (b mnc-convert-done :delay (add.i v1 v1 64))) + (asm-block convert-tail-3 + (label mnc-convert-tail-3) + (b mnc-convert-done :delay (add.i v1 v1 96))) + (asm-block convert-done + (label mnc-convert-done) + (s.w a0 v1 28) + (add.i v1 v1 32) + (add.i s5 s5 1) + (nop!) + (lui a3 #x7000) + (nop!) + (l.w a0 a3 (generic-work-offset in-buf merc shadow p-input)) + (nop!) + (l.w a1 a3 (generic-work-offset in-buf merc shadow indexed-output-base)) + (nop!) + (l.w a3 a3 #x1c90) + (nop!) + (l.bu s0 a0) + (add.i s4 r0 0) + (l.bu s1 a0 7) + (add.i t8 r0 0) + (l.bu s2 a0 8) + (sll s0 s0 2) + (add s0 s0 a0) + (sll s1 s1 2) + (b.z s1 mnc-crosscopy-setup :delay (add s1 s1 s0))) + (asm-block samecopy-loop + (label mnc-samecopy-loop) + (l.bu s3 s0) + (add s4 s4 t4) + (l.bu s6 s0 1) + (add t8 t8 t4) + (l.bu t9 s4) + (add s3 s3 t3) + (l.bu s4 s3) + (add s6 s6 t3) + (s.b t9 t8) + (add.i s0 s0 4) + (b.ne s0 s1 mnc-samecopy-loop :delay (l.bu t8 s6)) + (nop!) + (add s4 s4 t4) + (l.bu t9 s4) + (add t8 t8 t4) + (nop!) + (nop!) + (s.b t9 t8) + (nop!)) + (asm-block crosscopy-setup + (label mnc-crosscopy-setup) + (sll s2 s2 2) + (add.i t5 a3 32) + (b.z s2 mnc-store-vtx-count :delay (add s2 s2 s1)) + (l.bu s3 s0) + (add.i t6 a3 192) + (l.bu s6 s0 1) + (add.i t7 a3 448) + (nop!) + (add s3 s3 t6) + (l.bu s3 s3) + (add s6 s6 t3) + (l.bu s6 s6) + (add.i v1 v1 -32) + (nop!) + (add s3 s3 t5) + (l.bu s4 s3) + (add s6 s6 t4) + (nop!) + (add.i s0 s0 4) + (s.b s5 s6) + (add.i s5 s5 1) + (b.eq s0 s2 mnc-crosscopy-drain :delay (nop!))) + (asm-block crosscopy-loop + (label mnc-crosscopy-loop) + (l.bu s3 s0) + (sll s4 s4 5) + (l.bu s6 s0 1) + (add s4 s4 t7) + (l.q t9 s4) + (add s3 s3 t6) + (l.bu s3 s3) + (add s6 s6 t3) + (l.bu s6 s6) + (add.i v1 v1 32) + (l.q gp s4 16) + (add s3 s3 t5) + (l.bu s4 s3) + (add s6 s6 t4) + (s.q t9 v1) + (add.i s0 s0 4) + (s.b s5 s6) + (add.i s5 s5 1) + (b.ne s0 s2 mnc-crosscopy-loop :delay (s.q gp v1 16))) + (asm-block crosscopy-drain + (label mnc-crosscopy-drain) + (nop!) + (sll s4 s4 5) + (nop!) + (add s4 s4 t7) + (l.q t9 s4) + (nop!) + (nop!) + (nop!) + (nop!) + (add.i v1 v1 32) + (l.q gp s4 16) + (nop!) + (nop!) + (nop!) + (s.q t9 v1) + (nop!) + (nop!) + (nop!) + (nop!) + (s.q gp v1 16)) + (asm-block store-vtx-count + (label mnc-store-vtx-count) + (s.h s5 a1 20) + (lui at #x7000) + (l.q s0 at (generic-work-offset in-buf merc stack 0)) + (l.q s1 at (generic-work-offset in-buf merc stack 1)) + (l.q s2 at (generic-work-offset in-buf merc stack 2)) + (l.q s3 at (generic-work-offset in-buf merc stack 3)) + (l.q s4 at (generic-work-offset in-buf merc stack 4)) + (l.q s5 at (generic-work-offset in-buf merc stack 5)) + (l.q s6 at (generic-work-offset in-buf merc stack 6)) + (l.q t8 at (generic-work-offset in-buf merc stack 7)) + (l.q t9 at (generic-work-offset in-buf merc stack 8)) + (l.q gp at (generic-work-offset in-buf merc stack 9)) + (l.q ra at (generic-work-offset in-buf merc stack 12)) + (l.q sp at (generic-work-offset in-buf merc stack 10)) + (jr ra :delay (l.q fp at (generic-work-offset in-buf merc stack 11))) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!)) + ) + + (defun high-speed-reject () + "Transform up to eight packed bounds vectors and reject the current MERC control when every + tested point lies outside a common camera-space boundary." + (declare (asm-func none) (allow-saved-regs)) + ;; VU0 entries 438 and 454 test two groups of four points against the same transformed + ;; boundaries. A fragment is rejected only when the accumulated flag masks identify a common + ;; outside half-space for all participating points; crossing or straddling points keep it. + ;; The result is written into the packed control byte consumed by the converter. + (lui v1 #x7000) + (l.w a0 v1 (generic-work-offset in-buf merc shadow indexed-output-base)) + (add.i a1 a0 448) + (l.hu a3 a0 20) + (vmaxw.xyzw vf9 vf0 vf0) + (l.vf vf10 v1 (generic-work-offset in-buf merc shadow hsr-xmult)) + (l.vf vf11 v1 (generic-work-offset in-buf merc shadow hsr-ymult)) + (l.vf vf1 a1) + (add.i a0 r0 240) + (l.vf vf2 a1 32) + (ctc2.i vi9 a0) + (l.vf vf3 a1 64) + (l.vf vf4 a1 96) + (add.i a0 r0 -1) + (nop!) + (callms 438) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (l.vf vf5 a1 128) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (nop!) + (l.vf vf6 a1 160) + (nop!) + (nop!) + (nop!) + (nop!) + (l.wu t0 a1 8) + (l.vf vf7 a1 192) + (nop!) + (nop!) + (cfc2.ni a2 vi1) + (l.vf vf8 a1 224) + (srl t0 t0 31) + (or t0 a2 t0) + (l.wu a2 a1 40) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (add.i a1 a1 128) + (add.i a3 a3 -1) + (b.z a3 hsr-all-points-outside :delay (cfc2.ni t0 vi2)) + (label hsr-test-next-group) + (callms 454) + (srl a2 a2 31) + (or a2 t0 a2) + (l.wu t0 a1 -56) + (b.z a2 hsr-done :delay (and a0 a0 a2)) + (l.vf vf1 a1 128) + (add.i a2 a3 -1) + (b.z a2 hsr-all-points-outside :delay (cfc2.ni a3 vi3)) + (add.i a2 a2 -1) + (srl t0 t0 31) + (or t0 a3 t0) + (l.wu a3 a1 -24) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi4)) + (l.vf vf2 a1 160) + (srl a3 a3 31) + (or t0 t0 a3) + (l.wu a3 a1 8) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (l.vf vf3 a1 192) + (add.i a2 a2 -1) + (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi5)) + (l.vf vf4 a1 224) + (srl a3 a3 31) + (or t0 t0 a3) + (l.wu a3 a1 40) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (add.i a1 a1 128) + (add.i a2 a2 -1) + (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi6)) + (callms 438) + (srl a3 a3 31) + (or t0 t0 a3) + (l.wu a3 a1 -56) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (l.vf vf5 a1 128) + (add.i a2 a2 -1) + (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi7)) + (add.i a2 a2 -1) + (srl a3 a3 31) + (or t0 t0 a3) + (l.wu a3 a1 -24) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi8)) + (l.vf vf6 a1 160) + (srl a3 a3 31) + (or a3 t0 a3) + (l.wu t0 a1 8) + (b.z a3 hsr-done :delay (and a0 a0 a3)) + (l.vf vf7 a1 192) + (add.i a3 a2 -1) + (b.z a3 hsr-all-points-outside :delay (cfc2.ni a2 vi1)) + (l.vf vf8 a1 224) + (srl t0 t0 31) + (or t0 a2 t0) + (l.wu a2 a1 40) + (b.z t0 hsr-done :delay (and a0 a0 t0)) + (add.i a1 a1 128) + (add.i a3 a3 -1) + (b.nz a3 hsr-test-next-group :delay (cfc2.ni t0 vi2)) + (label hsr-all-points-outside) + (b.z a0 hsr-done :delay (nop!)) + (s.b r0 v1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) + (label hsr-done) + (jr ra :delay (nop!)) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!) + + ) + + ;; The frame-level driver. It walks the chain of control blocks bones queued in main memory, pulling + ;; each one into a scratchpad input buffer with the toSPR channel in source-chain mode while the + ;; previous one is being converted, and alternating the two output halves of the GSF buffer in step. + ;; Per control block it patches the VU1 entry, the environment tint, the camera matrix and the light + ;; block; per fragment it converts, runs the cheap reject test, services the ripple query and the + ;; death sampler, then runs the effect processors into the scratchpad output buffer and hands that to + ;; the fromSPR channel for copy-out. + ;; + ;; Input arrives as a DMA chain with its tags transferred; output leaves as a flat block. That + ;; asymmetry is the clearest statement of the shape of the whole pass. Every spin loop charges a + ;; counter in generic-saves, which is how the renderer reports whether VU0 upload, toSPR or fromSPR + ;; was the frame's bottleneck. + (defun generic-merc-execute-asm () + "Consume queued MERC controls using double-buffered main-memory transfers, convert their + fragments, and append the resulting Generic packets to the output DMA chain." + (declare (asm-func none) (allow-saved-regs)) + ;; Input A is converted while DMA fills input B; the roles swap at each control record. Each + ;; completed output half is copied back to the global DMA buffer before its scratchpad storage + ;; is reused. The explicit DMA busy loops also charge wait counters, so the renderer can report + ;; whether VU0, main-to-scratchpad, or scratchpad-to-main transfers were the limiting stage. + (add.i sp sp -272) + (s.d ra sp) + (s.d fp sp 8) + (m fp t9) + (s.q s0 sp 160) + (s.q s1 sp 176) + (s.q s2 sp 192) + (s.q s3 sp 208) + (s.q s4 sp 224) + (s.q s5 sp 240) + (s.q gp sp 256) + (m! v1 *merc-globals*) + (l.wu s2 v1) + (add.i v1 r0 0) + (add.i gp r0 0) + (add.i s5 r0 0) + (m! s4 *gsf-buffer*) + (lui v1 #x1000) + (ori s3 v1 #xd400) + (add.i v1 r0 640) + (lui a0 #x1000) + (ori a0 a0 #xd400) + (lui a1 #x7000) + (ori a1 a1 104) + (l.w a2 a0) + (and.i a2 a2 DMA-CHCR-STR) + (b.z a2 gme-start-first-input-dma :delay (at-label gme-wait-spr-to-first (l.w a2 a1))) + (nop!) + (l.w a3 a0) + (nop!) + (and.i a3 a3 DMA-CHCR-STR) + (add.i a2 a2 1) + (b.nz a3 gme-wait-spr-to-first :delay (s.w a2 a1)) + (asm-block start-first-input-dma + (label gme-start-first-input-dma) + (m a0 r0) + (s.w v1 s3 128) + (s.w s2 s3 48) + (s.w r0 s3 32) + (sync.l) + (add.i v1 r0 324) + (s.w v1 s3) + (sync.l) + (b gme-chain-test :delay (nop!))) + ;; Two input buffers and two output halves, alternating together on buf-index. + (asm-block select-input-base + (label gme-select-input-base) + (b.nz gp gme-input-base-b :delay (nop!)) + (add.i v1 r0 640) + (lui a0 #x7000) + (add v1 v1 a0) + (b gme-other-input-base-a :delay (nop!))) + (asm-block input-base-b + (label gme-input-base-b) + (add.i v1 r0 3616) + (lui a0 #x7000) + (add v1 v1 a0)) + (asm-block other-input-base-a + (label gme-other-input-base-a) + (b.nz gp gme-other-input-base-b :delay (nop!)) + (add.i a0 r0 3616) + (lui a1 #x7000) + (add a0 a0 a1) + (b gme-unused-shader-addr-a :delay (nop!))) + (asm-block other-input-base-b + (label gme-other-input-base-b) + (add.i a0 r0 640) + (lui a1 #x7000) + (add a0 a0 a1)) + ;; Dead: both input buffers' shader slots are computed here and both values are overwritten + ;; before use. mercneric-shader-asm reaches them itself through other-input-base. + (asm-block unused-shader-addr-a + (label gme-unused-shader-addr-a) + (b.nz gp gme-unused-shader-addr-b :delay (nop!)) + (add.i a1 r0 3536) + (lui a2 #x7000) + (add a1 a1 a2) + (b gme-unused-shader-addr-c :delay (nop!))) + (asm-block unused-shader-addr-b + (label gme-unused-shader-addr-b) + (add.i a1 r0 #x1970) + (lui a2 #x7000) + (add a1 a1 a2)) + (asm-block unused-shader-addr-c + (label gme-unused-shader-addr-c) + (b.nz gp gme-unused-shader-addr-d :delay (nop!)) + (add.i a1 r0 #x1970) + (lui a2 #x7000) + (add a1 a1 a2) + (b gme-select-output-base :delay (nop!))) + (asm-block unused-shader-addr-d + (label gme-unused-shader-addr-d) + (add.i a1 r0 3536) + (lui a2 #x7000) + (add a1 a1 a2)) + (asm-block select-output-base + (label gme-select-output-base) + (b.nz gp gme-output-base-b :delay (nop!)) + (add s1 r0 s4) + (b gme-other-output-base-a :delay (nop!))) + (asm-block output-base-b + (label gme-output-base-b) + (add.i s1 s4 2752)) + (asm-block other-output-base-a + (label gme-other-output-base-a) + (b.nz gp gme-other-output-base-b :delay (nop!)) + (add.i a1 s4 2752) + (b gme-publish-shadow :delay (nop!))) + (asm-block other-output-base-b + (label gme-other-output-base-b) + (add a1 r0 s4)) + ;; Publish the buffer selection where the three stages can find it. + (asm-block publish-shadow + (label gme-publish-shadow) + (add.i a2 r0 #x1b60) + (lui a3 #x7000) + (add a2 a2 a3) + (s.w gp a2 280) + (s.w s5 a2 284) + (s.w v1 a2 292) + (s.w a0 a2 296) + (s.w s1 a2 300) + (s.w a1 a2 304) + (s.w s4 a2 312) + (m a1 s4) + (lui a1 #x1000) + (ori a1 a1 #xd400) + (lui a2 #x7000) + (ori a2 a2 104) + (l.w a3 a1) + (and.i a3 a3 DMA-CHCR-STR) + (b.z a3 gme-queue-next-input :delay (at-label gme-wait-spr-to (l.w a3 a2))) + (nop!) + (l.w t0 a1) + (nop!) + (and.i t0 t0 DMA-CHCR-STR) + (add.i a3 a3 1) + (b.nz t0 gme-wait-spr-to :delay (s.w a3 a2))) + (asm-block queue-next-input + (label gme-queue-next-input) + (m a1 r0) + (l.wu s2 v1 12) + (b.z s2 gme-per-control-setup :delay (m a1 s7)) + (add a0 r0 a0) + (and.i a0 a0 #xffff) + (s.w a0 s3 128) + (s.w s2 s3 48) + (s.w r0 s3 32) + (sync.l) + (add.i a0 r0 324) + (s.w a0 s3) + (sync.l) + (m a0 r0)) + ;; A new effect brings a control block: lights, header, and the effect record. Copy it into + ;; the work area, because the input buffer it arrived in is about to be reused. + (asm-block per-control-setup + (label gme-per-control-setup) + (b.nz s5 gme-convert-fragment :delay (m a0 s7)) + (add.i a0 r0 #x19c0) + (lui a1 #x7000) + (add a1 a0 a1) + (m a2 v1) + (l.wu a0 v1 8) + (nop!) + (add.i a3 a0 -4) + (m a1 a1) + (b.lt a3 r0 gme-copy-ctrl-tail :delay (m a2 a2))) + (asm-block copy-ctrl-quad + (label gme-copy-ctrl-quad) + (nop!) + (l.q t2 a2) + (nop!) + (l.q a3 a2 16) + (add.i a0 a0 -4) + (l.q t0 a2 32) + (add.i a1 a1 64) + (l.q t1 a2 48) + (add.i a2 a2 64) + (s.q t2 a1 -64) + (add.i t2 a0 -4) + (s.q a3 a1 -48) + (nop!) + (s.q t0 a1 -32) + (b.ge t2 r0 gme-copy-ctrl-quad :delay (s.q t1 a1 -16))) + (asm-block copy-ctrl-tail + (label gme-copy-ctrl-tail) + (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) + (add.i a2 a2 16) + (add.i a1 a1 16) + (add.i a0 a0 -1) + (s.q a3 a1 -16) + (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) + (add.i a2 a2 16) + (add.i a1 a1 16) + (add.i a0 a0 -1) + (s.q a3 a1 -16) + (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) + (add.i a2 a2 16) + (add.i a1 a1 16) + (add.i a0 a0 -1) + (s.q a3 a1 -16) + (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) + (add.i a2 a2 16) + (add.i a1 a1 16) + (add.i a0 a0 -1) + (s.q a3 a1 -16)) + (asm-block copy-ctrl-done + (label gme-copy-ctrl-done) + (m a0 r0) + (lui a0 #x7000) + (l.bu a0 a0 #x1a68) + (b.nz a0 gme-patch-mscal-direct :delay (nop!)) + (add.i a0 r0 8) + (b gme-mscal-patched :delay (nop!))) + ;; needs-clip picks the VU1 entry: the clipping one, or the direct one that skips the test. + (asm-block patch-mscal-direct + (label gme-patch-mscal-direct) + (add.i a0 r0 6)) + (asm-block mscal-patched + (label gme-mscal-patched) + (lui a1 #x7000) + (ori a1 a1 #x2ed0) + (s.h a0 a1) + (add.i a0 r0 #x2f10) + (lui a1 #x7000) + (add a0 a0 a1) + (lui a1 #x7000) + (l.wu a1 a1 #x1a60) + (s.w a1 a0) + (s.w a1 a0 4) + (s.w a1 a0 8) + (s.w a1 a0 12) + (lui a0 #x7000) + (l.bu a0 a0 #x1a69) + (b.z a0 gme-copy-camera-matrix :delay (nop!)) + (add.i a0 r0 #x2e20) + (lui a1 #x7000) + (add a0 a0 a1) + (add.i a1 r0 #x1ba0) + (lui a2 #x7000) + (add t0 a1 a2) + (l.q a1 t0) + (l.q a2 t0 16) + (l.q a3 t0 32) + (l.q t0 t0 48) + (s.q a1 a0) + (s.q a2 a0 16) + (s.q a3 a0 32) + (s.q t0 a0 48) + (b gme-camera-copied :delay (nop!))) + ;; use-isometric picks which of the two cached camera matrices this effect draws with. + (asm-block copy-camera-matrix + (label gme-copy-camera-matrix) + (add.i a0 r0 #x2e20) + (lui a1 #x7000) + (add a0 a0 a1) + (add.i a1 r0 #x1b60) + (lui a2 #x7000) + (add t0 a1 a2) + (l.q a1 t0) + (l.q a2 t0 16) + (l.q a3 t0 32) + (l.q t0 t0 48) + (s.q a1 a0) + (s.q a2 a0 16) + (s.q a3 a0 32) + (s.q t0 a0 48)) + (asm-block camera-copied + (label gme-camera-copied) + (lui a0 #x7000) + (l.bu a0 a0 #x1a6b) + (lui a1 #x7000) + (s.b a0 a1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) + (lui at #x7000) + (l.vf vf1 at (generic-work-offset in-buf merc ctrl lights direction 0)) + (l.vf vf2 at (generic-work-offset in-buf merc ctrl lights direction 1)) + (l.vf vf3 at (generic-work-offset in-buf merc ctrl lights direction 2)) + (l.vf vf4 at (generic-work-offset in-buf merc ctrl lights color 0)) + (l.vf vf5 at (generic-work-offset in-buf merc ctrl lights color 1)) + (l.vf vf6 at (generic-work-offset in-buf merc ctrl lights color 2)) + (l.vf vf7 at (generic-work-offset in-buf merc ctrl lights ambient)) + (s.vf vf1 at (generic-work-offset fx-buf work lights direction 0)) + (s.vf vf2 at (generic-work-offset fx-buf work lights direction 1)) + (s.vf vf3 at (generic-work-offset fx-buf work lights direction 2)) + (s.vf vf4 at (generic-work-offset fx-buf work lights color 0)) + (s.vf vf5 at (generic-work-offset fx-buf work lights color 1)) + (s.vf vf6 at (generic-work-offset fx-buf work lights color 2)) + (s.vf vf7 at (generic-work-offset fx-buf work lights ambient)) + (l.wu a0 v1 8) + (sll a0 a0 4) + (add v1 v1 a0) + (m a0 v1)) + ;; One fragment: expand it into the indexed output half. + (asm-block convert-fragment + (label gme-convert-fragment) + (add.i a0 s7 #t) + (b.eq s7 a0 gme-reject-test :delay (m a0 s7)) + (add.i a0 r0 #x1b60) + (lui a1 #x7000) + (add a0 a0 a1) + (add.i v1 v1 16) + (s.w v1 a0 308) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow mercneric-convert)) + (jalr ra t9 :delay (sll v0 ra 0))) + ;; A cheap inline reject on vertex zero alone. Only if that one point is behind the camera or + ;; outside a plane is the full eight-at-a-time test worth calling. + (asm-block reject-test + (label gme-reject-test) + (lui v1 #x7000) + (add.i a0 r0 1) + (l.bu a1 v1 #x1a68) + (add.i a2 s1 448) + (l.bu a3 v1 (generic-work-offset in-buf merc ctrl header display-triangles)) + (b.z a1 gme-reject-done :delay (l.vf vf1 a2)) + (b.z a3 gme-reject-done :delay (vmaxw.xyzw vf9 vf0 vf0)) + (s.b a0 v1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) + (l.vf vf10 v1 (generic-work-offset in-buf merc shadow hsr-xmult)) + (l.vf vf11 v1 (generic-work-offset in-buf merc shadow hsr-ymult)) + (vmulaz.xyzw acc vf9 vf1) + (l.w a0 a2 8) + (vmaddax.xyzw acc vf10 vf1) + (b.lt a0 r0 gme-call-reject :delay (l.w v1 v1 #x1cb4)) + (vmaddy.xyzw vf1 vf11 vf1) + (m a0 vf1) + (pcgtw a0 r0 a0) + (ppach a0 r0 a0) + (b.z a0 gme-reject-done :delay (at-label gme-call-reject (nop!))) + (jalr ra v1 :delay (nop!))) + (asm-block reject-done + (label gme-reject-done) + (lui v1 #x7000) + (l.wu v1 v1 #x1a64) + (b.z v1 gme-ripple-done :delay (m v1 s7)) + (lui v1 #x7000) + (l.wu s0 v1 (generic-work-offset in-buf merc ctrl header query)) + (l.hu v1 s1 20) + (s.q v1 sp 48) + (l.w v1 s0 24) + (s.q v1 sp 64) + (l.w v1 s0 20) + (s.q v1 sp 80) + (b.nz s5 gme-ripple-first-vertex :delay (m v1 s7)) + (add.i v1 r0 0) + (s.q v1 sp 80) + (l.w v1 s0 12) + (s.q v1 sp 64) + (l.q v1 sp 64)) + ;; The ripple query samples every Nth converted vertex back into world space, which is how + ;; water knows where a character is touching it. + (asm-block ripple-first-vertex + (label gme-ripple-first-vertex) + (b gme-ripple-writeback :delay (nop!))) + (asm-block ripple-next-vertex + (label gme-ripple-next-vertex) + (l.q v1 sp 80) + (l.w a0 s0) + (slt v1 v1 a0) + (b.z v1 gme-ripple-sample-done :delay (m v1 s7)) + (l.q v1 sp 80) + (sll v1 v1 4) + (add.i v1 v1 28) + (add a1 v1 s0) + (l.q v1 sp 64) + (sll v1 v1 5) + (add v1 v1 s1) + (l.s f0 v1 448) + (s.s f0 a1) + (l.q v1 sp 64) + (sll v1 v1 5) + (add v1 v1 s1) + (l.s f0 v1 452) + (s.s f0 a1 4) + (l.q v1 sp 64) + (sll v1 v1 5) + (add v1 v1 s1) + (l.s f0 v1 456) + (s.s f0 a1 8) + (l.s f0 fp generic-merc-one) + (s.s f0 a1 12) + (m! t9 vector-matrix*!) + (m a0 a1) + (add.i v1 r0 #x1be0) + (lui a2 #x7000) + (add a2 v1 a2) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.q v1 sp 80) + (add.i v1 v1 1) + (s.q v1 sp 80) + (l.q v1 sp 80)) + (asm-block ripple-sample-done + (label gme-ripple-sample-done) + (l.q v1 sp 64) + (l.w a0 s0 16) + (add v1 v1 a0) + (s.q v1 sp 64)) + (asm-block ripple-writeback + (label gme-ripple-writeback) + (l.q v1 sp 64) + (l.q a0 sp 48) + (slt v1 v1 a0) + (b.nz v1 gme-ripple-next-vertex :delay (nop!)) + (m v1 s7) + (l.q v1 sp 64) + (l.q a0 sp 48) + (sub v1 v1 a0) + (s.w v1 s0 24) + (l.q v1 sp 80) + (s.w v1 s0 20)) + (asm-block ripple-done + (label gme-ripple-done) + (add.i v1 s7 #t) + (b.eq s7 v1 gme-death-done :delay (m v1 s7)) + (lui v1 #x7000) + (l.hu v1 v1 #x1a6c) + (b.z v1 gme-death-done :delay (m a0 s7)) + (lui v1 #x7000) + (l.hu s0 v1 (generic-work-offset in-buf merc ctrl header death-start-vertex)) + (lui v1 #x7000) + (l.hu v1 v1 #x1a6c) + (s.q v1 sp 96) + (l.hu v1 s1 20) + (s.q v1 sp 112) + (add.i v1 sp 16) + (s.q v1 sp 128) + (l.q v1 sp 128) + (s.q r0 v1) + (add.i v1 sp 32) + (s.q v1 sp 144) + (l.q v1 sp 144) + (s.q r0 v1) + (b gme-death-spawned :delay (nop!))) + ;; The death effect spawns a particle from every Nth vertex, with its normal, both + ;; transformed back into world space. + (asm-block death-next-vertex + (label gme-death-next-vertex) + (sll v1 s0 5) + (add v1 v1 s1) + (l.s f0 v1 448) + (l.q v1 sp 128) + (s.s f0 v1) + (sll v1 s0 5) + (add v1 v1 s1) + (l.s f0 v1 452) + (l.q v1 sp 128) + (s.s f0 v1 4) + (sll v1 s0 5) + (add v1 v1 s1) + (l.s f0 v1 456) + (l.q v1 sp 128) + (s.s f0 v1 8) + (l.s f0 fp generic-merc-one) + (l.q v1 sp 128) + (s.s f0 v1 12) + (sll v1 s0 5) + (add v1 v1 s1) + (l.s f0 v1 464) + (l.q v1 sp 144) + (s.s f0 v1) + (sll v1 s0 5) + (add v1 v1 s1) + (l.s f0 v1 468) + (l.q v1 sp 144) + (s.s f0 v1 4) + (sll v1 s0 5) + (add v1 v1 s1) + (l.s f0 v1 472) + (l.q v1 sp 144) + (s.s f0 v1 8) + (m f0 r0) + (l.q v1 sp 144) + (s.s f0 v1 12) + (m! t9 vector-matrix*!) + (l.q a0 sp 128) + (l.q a1 sp 128) + (add.i v1 r0 #x1be0) + (lui a2 #x7000) + (add a2 v1 a2) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 vector-matrix*!) + (l.q a0 sp 144) + (l.q a1 sp 144) + (add.i v1 r0 #x1be0) + (lui a2 #x7000) + (add a2 v1 a2) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 merc-death-spawn) + (lui v1 #x7000) + (l.wu a0 v1 (generic-work-offset in-buf merc ctrl header death-effect)) + (l.q a1 sp 128) + (l.q a2 sp 144) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.q v1 sp 96) + (add s0 s0 v1)) + (asm-block death-spawned + (label gme-death-spawned) + (l.q v1 sp 112) + (sltu v1 s0 v1) + (b.nz v1 gme-death-next-vertex :delay (nop!)) + (m v1 s7) + (l.q v1 sp 112) + (sub a0 s0 v1) + (lui v1 #x7000) + (s.h a0 v1 (generic-work-offset in-buf merc ctrl header death-start-vertex))) + (asm-block death-done + (label gme-death-done) + (add.i v1 s7 #t) + (b.eq s7 v1 gme-next-fragment :delay (m v1 s7)) + (lui v1 #x7000) + (l.w v1 v1 #x1c80) + (lui a0 #x7000) + (l.wu a0 a0 76) + (sub v1 v1 a0) + (slt v1 r0 v1) + (b.nz v1 gme-fragment-visible :delay (m v1 s7)) + (lui v1 #x7000) + (s.b r0 v1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) + (lui v1 #x7000) + (s.b r0 v1 (generic-work-offset in-buf merc ctrl header display-triangles)) + (m v1 r0)) + ;; Two ways to drop the rest of an object: the DMA buffer is nearly full, or the fragment was + ;; already marked invisible. Clearing display-triangles as well abandons every later + ;; fragment of the control too. + (asm-block fragment-visible + (label gme-fragment-visible) + (lui v1 #x7000) + (l.bu v1 v1 #x1a75) + (b.z v1 gme-next-fragment :delay (m v1 s7)) + (add.i v1 r0 1) + (lui a0 #x7000) + (l.bu a0 a0 #x1aab) + (sub v1 v1 a0) + (add.i a0 s7 8) + (movn a0 s7 v1) + (b.eql s7 a0 gme-emit-envmap-translucent :delay (m v1 a0)) + (add.i v1 s7 8) + (lui a0 #x7000) + (l.bu a0 a0 #x1a74) + (movz v1 s7 a0)) + ;; Three emit variants selected by envmap-usage and use-translucent. The first two do the + ;; same work with different register assignment; only the third, without an environment + ;; map, differs - one packet header instead of two and no reflected-coordinate pass. + (asm-block emit-envmap-translucent + (label gme-emit-envmap-translucent) + (b.eq s7 v1 gme-emit-envmap :delay (nop!)) + (add.i v1 s7 #t) + (b.eq s7 v1 gme-emit-done-a :delay (m v1 s7)) + (lui at #x7000) + (add.i v1 at (generic-work-offset fx-buf work consts envmap shader)) + (lui a0 #x7000) + (l.bu a0 a0 #x1a6a) + (b.z a0 gme-envmap-translucent-shader-selected :delay (m a0 s7)) + (add.i v1 r0 #x1ab0) + (lui a0 #x7000) + (add v1 v1 a0) + (m v1 v1) + (m a0 v1)) + (asm-block envmap-translucent-shader-selected + (label gme-envmap-translucent-shader-selected) + (s.w r0 at (generic-work-offset saves ptr-shaders)) + (s.w v1 at (generic-work-offset saves ptr-env-shader)) + (s.w s1 at (generic-work-offset saves gsf-buf)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-prepare-dma-double)) + (jalr ra t9 :delay (sll v0 ra 0)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-light-proc)) + (jalr ra t9 :delay (sll v0 ra 0)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-envmap-proc)) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (nop!) + (lui at #x7000) + (lui a2 #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori a2 a2 #xd000) + (l.w t1 a2) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 gme-send-outbuf-a :delay (nop!)) + (m t1 a2) + (nop!)) + ;; Hand the finished packet to fromSPR and continue in the other output buffer. The wait is + ;; here, immediately before reuse, and nowhere else. + (asm-block wait-spr-from-a + (label gme-wait-spr-from-a) + (l.w t2 t0) + (nop!) + (l.w t3 t1) + (nop!) + (and.i t3 t3 DMA-CHCR-STR) + (add.i t2 t2 1) + (b.nz t3 gme-wait-spr-from-a :delay (s.w t2 t0)) + (m t0 r0)) + (asm-block send-outbuf-a + (label gme-send-outbuf-a) + (sll t0 a0 4) + (s.w a3 a2 128) + (nop!) + (s.w a1 a2 16) + (add.i a3 r0 DMA-CHCR-STR) + (s.w a0 a2 32) + (add a0 a1 t0) + (s.w a3 a2) + (nop!) + (s.w a0 at (generic-work-offset saves basep)) + (m a0 r0) + (xor.i v1 v1 GENERIC-OUTBUF-FLIP) + (s.w v1 at (generic-work-offset saves cur-outbuf))) + (asm-block emit-done-a + (label gme-emit-done-a) + (b gme-next-fragment :delay (nop!))) + (asm-block emit-envmap + (label gme-emit-envmap) + (lui v1 #x7000) + (l.bu v1 v1 #x1aab) + (b.z v1 gme-emit-plain :delay (nop!)) + (add.i v1 s7 #t) + (b.eq s7 v1 gme-emit-done-b :delay (m v1 s7)) + (lui at #x7000) + (m v1 s1) + (add.i a0 at (generic-work-offset fx-buf work consts envmap shader)) + (lui a1 #x7000) + (l.bu a1 a1 #x1a6a) + (b.z a1 gme-envmap-shader-selected :delay (m a1 s7)) + (add.i a0 r0 #x1ab0) + (lui a1 #x7000) + (add a0 a0 a1) + (m a0 a0) + (m a1 a0)) + (asm-block envmap-shader-selected + (label gme-envmap-shader-selected) + (s.w r0 at (generic-work-offset saves ptr-shaders)) + (s.w a0 at (generic-work-offset saves ptr-env-shader)) + (s.w v1 at (generic-work-offset saves gsf-buf)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-prepare-dma-double)) + (jalr ra t9 :delay (sll v0 ra 0)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-light-proc)) + (jalr ra t9 :delay (sll v0 ra 0)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-envmap-proc)) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (nop!) + (lui at #x7000) + (lui a2 #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori a2 a2 #xd000) + (l.w t1 a2) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 gme-send-outbuf-b :delay (nop!)) + (m t1 a2) + (nop!)) + (asm-block wait-spr-from-b + (label gme-wait-spr-from-b) + (l.w t2 t0) + (nop!) + (l.w t3 t1) + (nop!) + (and.i t3 t3 DMA-CHCR-STR) + (add.i t2 t2 1) + (b.nz t3 gme-wait-spr-from-b :delay (s.w t2 t0)) + (m t0 r0)) + (asm-block send-outbuf-b + (label gme-send-outbuf-b) + (sll t0 a0 4) + (s.w a3 a2 128) + (nop!) + (s.w a1 a2 16) + (add.i a3 r0 DMA-CHCR-STR) + (s.w a0 a2 32) + (add a0 a1 t0) + (s.w a3 a2) + (nop!) + (s.w a0 at (generic-work-offset saves basep)) + (m a0 r0) + (xor.i v1 v1 GENERIC-OUTBUF-FLIP) + (s.w v1 at (generic-work-offset saves cur-outbuf))) + (asm-block emit-done-b + (label gme-emit-done-b) + (b gme-next-fragment :delay (nop!))) + (asm-block emit-plain + (label gme-emit-plain) + (add.i v1 s7 #t) + (b.eq s7 v1 gme-next-fragment :delay (m v1 s7)) + (lui at #x7000) + (m v1 s1) + (s.w r0 at (generic-work-offset saves ptr-shaders)) + (s.w v1 at (generic-work-offset saves gsf-buf)) + (s.w r0 at (generic-work-offset saves is-envmap)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-prepare-dma-single)) + (jalr ra t9 :delay (sll v0 ra 0)) + (lui v1 #x7000) + (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-light-proc)) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (nop!) + (lui at #x7000) + (lui a2 #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori a2 a2 #xd000) + (l.w t1 a2) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 gme-send-outbuf-c :delay (nop!)) + (m t1 a2) + (nop!)) + (asm-block wait-spr-from-c + (label gme-wait-spr-from-c) + (l.w t2 t0) + (nop!) + (l.w t3 t1) + (nop!) + (and.i t3 t3 DMA-CHCR-STR) + (add.i t2 t2 1) + (b.nz t3 gme-wait-spr-from-c :delay (s.w t2 t0)) + (m t0 r0)) + (asm-block send-outbuf-c + (label gme-send-outbuf-c) + (sll t0 a0 4) + (s.w a3 a2 128) + (nop!) + (s.w a1 a2 16) + (add.i a3 r0 DMA-CHCR-STR) + (s.w a0 a2 32) + (add a0 a1 t0) + (s.w a3 a2) + (nop!) + (s.w a0 at (generic-work-offset saves basep)) + (m a0 r0) + (xor.i v1 v1 GENERIC-OUTBUF-FLIP) + (s.w v1 at (generic-work-offset saves cur-outbuf))) + ;; Next fragment, or next control block, or done. + (asm-block next-fragment + (label gme-next-fragment) + (add.i v1 r0 1) + (sub gp v1 gp) + (add.i s5 s5 1) + (lui v1 #x7000) + (l.hu v1 v1 #x1aa2) + (b.ne s5 v1 gme-frag-index-kept :delay (m v1 s7)) + (add.i s5 r0 0) + (m v1 s5)) + (asm-block frag-index-kept + (label gme-frag-index-kept) + (m v1 s2)) + (asm-block chain-test + (label gme-chain-test) + (b.nz s2 gme-select-input-base :delay (nop!)) + (m v1 s7) + (m! v1 *merc-globals*) + (s.w r0 v1) + (m v0 r0) + (l.d ra sp) + (l.d fp sp 8) + (l.q gp sp 256) + (l.q s5 sp 240) + (l.q s4 sp 224) + (l.q s3 sp 208) + (l.q s2 sp 192) + (l.q s1 sp 176) + (l.q s0 sp 160) + (jr ra :delay (add.i sp sp 272)) + (nop!) + (nop!) + (nop!)) + ) + + ) \ No newline at end of file diff --git a/goal_src/jak1/engine/gfx/generic/generic-merc.gc b/goal_src/jak1/engine/gfx/generic/generic-merc.gc index f20b51020d..55bf98f94c 100644 --- a/goal_src/jak1/engine/gfx/generic/generic-merc.gc +++ b/goal_src/jak1/engine/gfx/generic/generic-merc.gc @@ -8,235 +8,8 @@ ;; DECOMP BEGINS (#when PC_PORT - ;; MERC vertex conversion is implemented by the native renderer on PC. (define mercneric-vu0-block (new 'static 'vu-function :length #x0 :origin #x0 :qlength #x0))) -(#unless PC_PORT - ;; The program occupies addresses 280 through 469. Entry 280 initializes constant vectors and - ;; selects return addresses for one-, two-, or three-matrix skinning: three chained tests on the - ;; three counts, each skipping the assignment of that section's exit address when the section is - ;; empty, so the vertex loop can run straight through all three without a per-vertex branch on how - ;; many bones a vertex has. vi02, vi04 and vi06 end up holding the three section ends. - ;; - ;; Within a section, the reuse joins exist because MERC vertices frequently blend the same bones as - ;; the vertex before them; the ibeq on vi09 skips reloading the matrices and drops straight into the - ;; next section's stream. Entry 303 stores the seven - ;; current matrix vectors and primes scaled rows. Entry 311 expands weighted positions and - ;; normals: the packed indices select one to three matrix records, and the indirect return through - ;; vi01 advances through the matching pipeline without repeating common work. Entries 438 and 454 - ;; transform the first and second groups of four high-speed-reject test points and return their - ;; masked VU flag words in vi01 through vi08. - ;; - ;; vi08 is the output cursor during vertex conversion. vi10 walks packed control records, vi11 - ;; through vi13 hold matrix indices, and vi01 is the continuation address selected for the current - ;; influence count. vf08 through vf10 are the accumulated transform rows; vf11 through vf13 carry - ;; the converted vertex streams, vf16 holds the normalization sum, and vf17 supplies the fixed - ;; conversion scale. - (defvu0 mercneric-vu0-block - (vu-pair (0.003921569) (maxw.x vf17 vf00 vf00 :i)) ;; #x000 - (vu-pair (-65537.0) (maxi.y vf17 vf00 I :i)) ;; #x001 - (vu-pair (iaddiu vi10 vi10 0x88) (minii.z vf05 vf00 I)) ;; #x002 - (vu-pair (iaddiu vi08 vi00 0x8c) (minii.z vf06 vf00 I)) ;; #x003 - (vu-pair (sqi.xyzw vf01 vi08) (minii.z vf07 vf00 I)) ;; #x004 - (vu-pair (sqi.xyzw vf02 vi08) (minix.w vf05 vf00 vf27)) ;; #x005 - (vu-pair (sqi.xyzw vf03 vi08) (miniy.w vf06 vf00 vf27)) ;; #x006 - (vu-pair (sqi.xyzw vf04 vi08) (miniz.w vf07 vf00 vf27)) ;; #x007 - (vu-pair (ibne vi00 vi13 check-two-matrix-count) (nop)) ;; #x008 - (vu-pair (iaddiu vi05 vi00 0x182) (nop)) ;; #x009 - (vu-pair (iaddiu vi05 vi00 0x1ab) (nop)) ;; #x00a - (label check-two-matrix-count) - (vu-pair (ibne vi00 vi12 check-three-matrix-count) (nop)) ;; #x00b - (vu-pair (iaddiu vi03 vi00 0x146) (nop)) ;; #x00c - (vu-pair (ior vi03 vi05 vi00) (nop)) ;; #x00d - (label check-three-matrix-count) - (vu-pair (ibne vi00 vi11 stage-vertex-stream) (nop)) ;; #x00e - (vu-pair (iaddiu vi01 vi00 0x13a) (nop)) ;; #x00f - (vu-pair (ior vi01 vi03 vi00) (nop)) ;; #x010 - (label stage-vertex-stream) - (vu-pair (lqi.xyzw vf29 vi10) (nop)) ;; #x011 - (vu-pair (iadd vi02 vi08 vi11) (nop)) ;; #x012 - (vu-pair (iadd vi04 vi02 vi12) (nop)) ;; #x013 - (vu-pair (iadd vi06 vi04 vi13) (nop)) ;; #x014 - (vu-pair (mtir vi13 vf29.w) (itof0.xyzw vf18 vf29 :e)) ;; #x015 - (vu-pair (iaddiu vi09 vi00 0x7f) (nop)) ;; #x016 - (vu-pair (sq.xyzw vf23 vi14 3) (mulx.xyzw vf11 vf20 vf19)) ;; #x017 - (vu-pair (sq.xyzw vf24 vi14 4) (mulx.xyzw vf12 vf21 vf19)) ;; #x018 - (vu-pair (sq.xyzw vf25 vi14 5) (mulx.xyzw vf13 vf22 vf19)) ;; #x019 - (vu-pair (sq.xyzw vf26 vi14 6) (nop)) ;; #x01a - (vu-pair (sq.xyzw vf11 vi14 0) (nop)) ;; #x01b - (vu-pair (sq.xyzw vf12 vi14 1) (nop)) ;; #x01c - (vu-pair (sq.xyzw vf13 vi14 2) (nop :e)) ;; #x01d - (vu-pair (nop) (nop)) ;; #x01e - (vu-pair (mtir vi11 vf01.x) (maddz.xyzw vf11 vf26 vf10)) ;; #x01f - (vu-pair (jr vi01) (mul.xyzw vf14 vf13 Q)) ;; #x020 - (vu-pair (sqi.xyzw vf04 vi08) (mulaw.xyzw ACC vf20 vf08)) ;; #x021 - (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x022 - (vu-pair (lq.xyzw vf24 vi11 -124) (maddaw.xyzw ACC vf22 vf10)) ;; #x023 - (vu-pair (lq.xyzw vf25 vi11 -123) (maddw.xyzw vf15 vf23 vf00)) ;; #x024 - (vu-pair (lq.xyzw vf26 vi11 -122) (mul.xyzw vf16 vf11 vf11)) ;; #x025 - (vu-pair (lq.xyzw vf20 vi11 -128) (add.xyzw vf08 vf01 vf05)) ;; #x026 - (vu-pair (lq.xyzw vf21 vi11 -127) (add.xyzw vf09 vf02 vf06)) ;; #x027 - (vu-pair (lq.xyzw vf22 vi11 -126) (add.xyzw vf10 vf03 vf07)) ;; #x028 - (vu-pair (ibne vi08 vi02 skin-one-matrix-vertex) (adday.xyzw vf16 vf16)) ;; #x029 - (vu-pair (lq.xyzw vf23 vi11 -125) (maddz.xyzw vf16 vf17 vf16)) ;; #x02a - (vu-pair (ior vi01 vi03 vi00) (nop)) ;; #x02b - (label skin-one-matrix-vertex) - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x02c - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x02d - (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x02e - (vu-pair (mtir vi12 vf01.y) (maddaw.xyzw ACC vf22 vf10)) ;; #x02f - (vu-pair (iand vi11 vi11 vi09) (maddw.xyzw vf15 vf23 vf00)) ;; #x030 - (vu-pair (lq.xyzw vf19 vi11 4) (mul.xyzw vf16 vf11 vf11)) ;; #x031 - (vu-pair (ibeq vi09 vi11 skin-two-matrix-reuse) (add.xyzw vf08 vf01 vf05)) ;; #x032 - (vu-pair (iand vi12 vi12 vi09) (add.xyzw vf09 vf02 vf06)) ;; #x033 - (vu-pair (nop) (muly.xyzw vf18 vf18 vf17)) ;; #x034 - (vu-pair (lq.xyzw vf24 vi12 4) (add.xyzw vf10 vf03 vf07)) ;; #x035 - (vu-pair (lq.xyzw vf27 vi11 5) (adday.xyzw vf16 vf16)) ;; #x036 - (vu-pair (lq.xyzw vf25 vi12 5) (maddz.xyzw vf16 vf17 vf16)) ;; #x037 - (vu-pair (lq.xyzw vf28 vi11 6) (mulax.xyzw ACC vf19 vf18)) ;; #x038 - (vu-pair (lq.xyzw vf26 vi12 6) (maddy.xyzw vf24 vf24 vf18)) ;; #x039 - (vu-pair (lq.xyzw vf29 vi11 0) (mulax.xyzw ACC vf27 vf18)) ;; #x03a - (vu-pair (lq.xyzw vf20 vi12 0) (maddy.xyzw vf25 vf25 vf18)) ;; #x03b - (vu-pair (lq.xyzw vf19 vi11 1) (mulax.xyzw ACC vf28 vf18)) ;; #x03c - (vu-pair (lq.xyzw vf21 vi12 1) (maddy.xyzw vf26 vf26 vf18)) ;; #x03d - (vu-pair (lq.xyzw vf27 vi11 2) (mulax.xyzw ACC vf29 vf18)) ;; #x03e - (vu-pair (lq.xyzw vf22 vi12 2) (maddy.xyzw vf20 vf20 vf18)) ;; #x03f - (vu-pair (lq.xyzw vf28 vi11 3) (mulax.xyzw ACC vf19 vf18)) ;; #x040 - (vu-pair (lq.xyzw vf23 vi12 3) (maddy.xyzw vf21 vf21 vf18)) ;; #x041 - (vu-pair (nop) (mulax.xyzw ACC vf27 vf18)) ;; #x042 - (vu-pair (iaddiu vi01 vi00 0x161) (maddy.xyzw vf22 vf22 vf18)) ;; #x043 - (vu-pair (ibne vi08 vi04 skin-two-matrix-vertex) (mulax.xyzw ACC vf28 vf18)) ;; #x044 - (vu-pair (nop) (maddy.xyzw vf23 vf23 vf18)) ;; #x045 - (vu-pair (ior vi01 vi05 vi00) (nop)) ;; #x046 - (label skin-two-matrix-vertex) - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x047 - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x048 - (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x049 - (vu-pair (mtir vi12 vf01.y) (maddaw.xyzw ACC vf22 vf10)) ;; #x04a - (vu-pair (iand vi11 vi11 vi09) (maddw.xyzw vf15 vf23 vf00)) ;; #x04b - (vu-pair (lq.xyzw vf19 vi11 4) (mul.xyzw vf16 vf11 vf11)) ;; #x04c - (vu-pair (ibeq vi09 vi11 skin-two-matrix-reuse) (add.xyzw vf08 vf01 vf05)) ;; #x04d - (vu-pair (iand vi12 vi12 vi09) (add.xyzw vf09 vf02 vf06)) ;; #x04e - (vu-pair (lq.xyzw vf24 vi12 4) (add.xyzw vf10 vf03 vf07)) ;; #x04f - (vu-pair (lq.xyzw vf27 vi11 5) (adday.xyzw vf16 vf16)) ;; #x050 - (vu-pair (lq.xyzw vf25 vi12 5) (maddz.xyzw vf16 vf17 vf16)) ;; #x051 - (vu-pair (lq.xyzw vf28 vi11 6) (mulaz.xyzw ACC vf19 vf18)) ;; #x052 - (vu-pair (lq.xyzw vf26 vi12 6) (maddw.xyzw vf24 vf24 vf18)) ;; #x053 - (vu-pair (lq.xyzw vf29 vi11 0) (mulaz.xyzw ACC vf27 vf18)) ;; #x054 - (vu-pair (lq.xyzw vf20 vi12 0) (maddw.xyzw vf25 vf25 vf18)) ;; #x055 - (vu-pair (lq.xyzw vf19 vi11 1) (mulaz.xyzw ACC vf28 vf18)) ;; #x056 - (vu-pair (lq.xyzw vf21 vi12 1) (maddw.xyzw vf26 vf26 vf18)) ;; #x057 - (vu-pair (lq.xyzw vf27 vi11 2) (mulaz.xyzw ACC vf29 vf18)) ;; #x058 - (vu-pair (lq.xyzw vf22 vi12 2) (maddw.xyzw vf20 vf20 vf18)) ;; #x059 - (vu-pair (lq.xyzw vf28 vi11 3) (mulaz.xyzw ACC vf19 vf18)) ;; #x05a - (vu-pair (lq.xyzw vf23 vi12 3) (maddw.xyzw vf21 vf21 vf18)) ;; #x05b - (vu-pair (lqi.xyzw vf29 vi10) (mulaz.xyzw ACC vf27 vf18)) ;; #x05c - (vu-pair (iaddiu vi01 vi00 0x146) (maddw.xyzw vf22 vf22 vf18)) ;; #x05d - (vu-pair (ibne vi08 vi04 skin-two-matrix-reload) (mulaz.xyzw ACC vf28 vf18)) ;; #x05e - (vu-pair (nop) (maddw.xyzw vf23 vf23 vf18)) ;; #x05f - (vu-pair (ior vi01 vi05 vi00) (nop)) ;; #x060 - (label skin-two-matrix-reload) - (vu-pair (mtir vi13 vf29.w) (itof0.xyzw vf18 vf29)) ;; #x061 - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x062 - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x063 - (label skin-two-matrix-reuse) - (vu-pair (nop) (add.xyzw vf10 vf03 vf07)) ;; #x064 - (vu-pair (ibne vi08 vi04 skin-two-matrix-next) (adday.xyzw vf16 vf16)) ;; #x065 - (vu-pair (nop) (maddz.xyzw vf16 vf17 vf16)) ;; #x066 - (vu-pair (ior vi01 vi05 vi00) (nop)) ;; #x067 - (label skin-two-matrix-next) - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x068 - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x069 - (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x06a - (vu-pair (mtir vi12 vf01.y) (maddaw.xyzw ACC vf22 vf10)) ;; #x06b - (vu-pair (iand vi11 vi11 vi09) (maddw.xyzw vf15 vf23 vf00)) ;; #x06c - (vu-pair (lq.xyzw vf19 vi11 4) (mul.xyzw vf16 vf11 vf11)) ;; #x06d - (vu-pair (ibeq vi09 vi11 skin-three-matrix-reuse) (add.xyzw vf08 vf01 vf05)) ;; #x06e - (vu-pair (iand vi12 vi12 vi09) (add.xyzw vf09 vf02 vf06)) ;; #x06f - (vu-pair (nop) (muly.xyzw vf18 vf18 vf17)) ;; #x070 - (vu-pair (lq.xyzw vf27 vi12 4) (add.xyzw vf10 vf03 vf07)) ;; #x071 - (vu-pair (lq.xyzw vf24 vi13 4) (adday.xyzw vf16 vf16)) ;; #x072 - (vu-pair (lq.xyzw vf28 vi11 5) (maddz.xyzw vf16 vf17 vf16)) ;; #x073 - (vu-pair (lq.xyzw vf19 vi12 5) (mulax.xyzw ACC vf19 vf18)) ;; #x074 - (vu-pair (lq.xyzw vf25 vi13 5) (madday.xyzw ACC vf27 vf18)) ;; #x075 - (vu-pair (lq.xyzw vf27 vi11 6) (maddz.xyzw vf24 vf24 vf18)) ;; #x076 - (vu-pair (lq.xyzw vf28 vi12 6) (mulax.xyzw ACC vf28 vf18)) ;; #x077 - (vu-pair (lq.xyzw vf26 vi13 6) (madday.xyzw ACC vf19 vf18)) ;; #x078 - (vu-pair (lq.xyzw vf19 vi11 0) (maddz.xyzw vf25 vf25 vf18)) ;; #x079 - (vu-pair (lq.xyzw vf27 vi12 0) (mulax.xyzw ACC vf27 vf18)) ;; #x07a - (vu-pair (lq.xyzw vf20 vi13 0) (madday.xyzw ACC vf28 vf18)) ;; #x07b - (vu-pair (lq.xyzw vf28 vi11 1) (maddz.xyzw vf26 vf26 vf18)) ;; #x07c - (vu-pair (lq.xyzw vf19 vi12 1) (mulax.xyzw ACC vf19 vf18)) ;; #x07d - (vu-pair (lq.xyzw vf21 vi13 1) (madday.xyzw ACC vf27 vf18)) ;; #x07e - (vu-pair (lq.xyzw vf27 vi11 2) (maddz.xyzw vf20 vf20 vf18)) ;; #x07f - (vu-pair (lq.xyzw vf28 vi12 2) (mulax.xyzw ACC vf28 vf18)) ;; #x080 - (vu-pair (lq.xyzw vf22 vi13 2) (madday.xyzw ACC vf19 vf18)) ;; #x081 - (vu-pair (lq.xyzw vf19 vi11 3) (maddz.xyzw vf21 vf21 vf18)) ;; #x082 - (vu-pair (lq.xyzw vf27 vi12 3) (mulax.xyzw ACC vf27 vf18)) ;; #x083 - (vu-pair (lq.xyzw vf23 vi13 3) (madday.xyzw ACC vf28 vf18)) ;; #x084 - (vu-pair (lqi.xyzw vf29 vi10) (maddz.xyzw vf22 vf22 vf18)) ;; #x085 - (vu-pair (ibne vi08 vi06 skin-three-matrix-vertex) (mulax.xyzw ACC vf19 vf18)) ;; #x086 - (vu-pair (nop) (madday.xyzw ACC vf27 vf18)) ;; #x087 - (vu-pair (iaddiu vi01 vi00 0x1ab) (nop)) ;; #x088 - (label skin-three-matrix-vertex) - (vu-pair (nop) (maddz.xyzw vf23 vf23 vf18)) ;; #x089 - (vu-pair (mtir vi13 vf29.w) (itof0.xyz vf18 vf29)) ;; #x08a - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x08b - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x08c - (label skin-three-matrix-reuse) - (vu-pair (nop) (add.xyzw vf10 vf03 vf07)) ;; #x08d - (vu-pair (ibne vi08 vi06 skin-three-matrix-next) (adday.xyzw vf16 vf16)) ;; #x08e - (vu-pair (nop) (maddz.xyzw vf16 vf17 vf16)) ;; #x08f - (vu-pair (iaddiu vi01 vi00 0x1ab) (nop)) ;; #x090 - (label skin-three-matrix-next) - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x091 - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x092 - (vu-pair (rsqrt Q vf00.w vf16.x) (maddaw.xyzw ACC vf21 vf09)) ;; #x093 - (vu-pair (nop) (maddaw.xyzw ACC vf22 vf10)) ;; #x094 - (vu-pair (nop) (maddw.xyzw vf15 vf23 vf00)) ;; #x095 - (vu-pair (nop) (mul.xyzw vf16 vf11 vf11)) ;; #x096 - (vu-pair (nop) (add.xyzw vf08 vf01 vf05)) ;; #x097 - (vu-pair (nop) (add.xyzw vf09 vf02 vf06)) ;; #x098 - (vu-pair (nop) (add.xyzw vf10 vf03 vf07)) ;; #x099 - (vu-pair (nop) (adday.xyzw vf16 vf16)) ;; #x09a - (vu-pair (nop) (maddz.xyzw vf16 vf17 vf16)) ;; #x09b - (vu-pair (move.xyzw vf13 vf12) (mulaz.xyzw ACC vf24 vf08 :e)) ;; #x09c - (vu-pair (move.xyzw vf12 vf11) (maddaz.xyzw ACC vf25 vf09)) ;; #x09d - (vu-pair (nop) (mulaz.xyzw ACC vf09 vf01)) ;; #x09e - (vu-pair (nop) (maddax.xyzw ACC vf10 vf01)) ;; #x09f - (vu-pair (nop) (maddy.xyzw vf01 vf11 vf01)) ;; #x0a0 - (vu-pair (nop) (mulaz.xyzw ACC vf09 vf02)) ;; #x0a1 - (vu-pair (nop) (maddax.xyzw ACC vf10 vf02)) ;; #x0a2 - (vu-pair (nop) (maddy.xyzw vf02 vf11 vf02)) ;; #x0a3 - (vu-pair (fmand vi01 vi09) (mulaz.xyzw ACC vf09 vf03)) ;; #x0a4 - (vu-pair (nop) (maddax.xyzw ACC vf10 vf03)) ;; #x0a5 - (vu-pair (nop) (maddy.xyzw vf03 vf11 vf03)) ;; #x0a6 - (vu-pair (fmand vi02 vi09) (mulaz.xyzw ACC vf09 vf04)) ;; #x0a7 - (vu-pair (nop) (maddax.xyzw ACC vf10 vf04)) ;; #x0a8 - (vu-pair (nop) (maddy.xyzw vf04 vf11 vf04)) ;; #x0a9 - (vu-pair (fmand vi03 vi09) (nop)) ;; #x0aa - (vu-pair (nop) (nop)) ;; #x0ab - (vu-pair (nop) (nop :e)) ;; #x0ac - (vu-pair (fmand vi04 vi09) (nop)) ;; #x0ad - (vu-pair (nop) (mulaz.xyzw ACC vf09 vf05)) ;; #x0ae - (vu-pair (nop) (maddax.xyzw ACC vf10 vf05)) ;; #x0af - (vu-pair (nop) (maddy.xyzw vf05 vf11 vf05)) ;; #x0b0 - (vu-pair (nop) (mulaz.xyzw ACC vf09 vf06)) ;; #x0b1 - (vu-pair (nop) (maddax.xyzw ACC vf10 vf06)) ;; #x0b2 - (vu-pair (nop) (maddy.xyzw vf06 vf11 vf06)) ;; #x0b3 - (vu-pair (fmand vi05 vi09) (mulaz.xyzw ACC vf09 vf07)) ;; #x0b4 - (vu-pair (nop) (maddax.xyzw ACC vf10 vf07)) ;; #x0b5 - (vu-pair (nop) (maddy.xyzw vf07 vf11 vf07)) ;; #x0b6 - (vu-pair (fmand vi06 vi09) (mulaz.xyzw ACC vf09 vf08)) ;; #x0b7 - (vu-pair (nop) (maddax.xyzw ACC vf10 vf08)) ;; #x0b8 - (vu-pair (nop) (maddy.xyzw vf08 vf11 vf08)) ;; #x0b9 - (vu-pair (fmand vi07 vi09) (nop)) ;; #x0ba - (vu-pair (nop) (nop)) ;; #x0bb - (vu-pair (nop) (nop :e)) ;; #x0bc - (vu-pair (fmand vi08 vi09) (nop)) ;; #x0bd - ) - ) - (deftype invinitdata (structure) ((count uint8) (init-data uint8) @@ -280,2993 +53,6 @@ (def-mips2c high-speed-reject (function none)) (def-mips2c generic-merc-execute-asm (function none))) -(#unless PC_PORT - ;; These functions share the Generic renderer's fixed scratchpad layout. generic-merc-execute-asm - ;; overlaps main-memory DMA with conversion in two scratchpad input buffers. mercneric-convert - ;; builds the expanded Generic streams by invoking the bit-table, shader, and matrix stages; the - ;; matrix stage starts the VU0 program above while the EE prepares the next records. - (asm-data - (label generic-merc-one) - (word #x3f800000 0)) - (defun generic-merc-init-asm () - "Upload the MERC VU0 program at address 280, install the Generic conversion callbacks, copy - camera transforms and reject scales into scratchpad, and initialize both expanded-vertex work - buffers." - (declare (asm-func none) (allow-saved-regs)) - ;; The three camera matrices are copied because conversion runs entirely from scratchpad. - ;; hsr-xmult and hsr-ymult convert clip-space x and y into the sign tests used by - ;; high-speed-reject. The inverse tables map packed matrix references to expanded slots; both - ;; output buffers receive identical seeds so either half of the double buffer can start first. - (add.i sp sp -16) - (s.d ra sp) - (s.d fp sp 8) - (m fp t9) - (m! t9 upload-vu0-program) - (m! a0 mercneric-vu0-block) - (lui v1 #x7000) - (ori a1 v1 100) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! v1 mercneric-convert) - (lui a0 #x7000) - (s.w v1 a0 (generic-work-offset in-buf merc shadow mercneric-convert)) - (m! v1 generic-prepare-dma-single) - (lui a0 #x7000) - (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-prepare-dma-single)) - (m! v1 generic-prepare-dma-double) - (lui a0 #x7000) - (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-prepare-dma-double)) - (m! v1 generic-light-proc) - (lui a0 #x7000) - (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-light-proc)) - (m! v1 generic-envmap-proc) - (lui a0 #x7000) - (s.w v1 a0 (generic-work-offset in-buf merc shadow generic-envmap-proc)) - (m! v1 high-speed-reject) - (lui a0 #x7000) - (s.w v1 a0 (generic-work-offset in-buf merc shadow high-speed-reject)) - (m! v1 *math-camera*) - (add.i a0 r0 #x1b60) - (lui a1 #x7000) - (add a3 a0 a1) - (add.i t0 v1 156) - (l.q a0 t0) - (l.q a1 t0 16) - (l.q a2 t0 32) - (l.q t0 t0 48) - (s.q a0 a3) - (s.q a1 a3 16) - (s.q a2 a3 32) - (s.q t0 a3 48) - (add.i a0 r0 #x1ba0) - (lui a1 #x7000) - (add a3 a0 a1) - (add.i t0 v1 220) - (l.q a0 t0) - (l.q a1 t0 16) - (l.q a2 t0 32) - (l.q t0 t0 48) - (s.q a0 a3) - (s.q a1 a3 16) - (s.q a2 a3 32) - (s.q t0 a3 48) - (add.i a0 r0 #x1be0) - (lui a1 #x7000) - (add a3 a0 a1) - (add.i t0 v1 428) - (l.q a0 t0) - (l.q a1 t0 16) - (l.q a2 t0 32) - (l.q t0 t0 48) - (s.q a0 a3) - (s.q a1 a3 16) - (s.q a2 a3 32) - (s.q t0 a3 48) - (l.s f0 fp generic-merc-one) - (l.s f1 v1 12) - (div.s f1 f0 f1) - (l.s f0 fp generic-merc-one) - (l.s f2 v1 16) - (div.s f0 f0 f2) - (add.i v1 r0 #x1cc0) - (lui a0 #x7000) - (add a0 v1 a0) - (add.i v1 r0 #x1cd0) - (lui a1 #x7000) - (add v1 v1 a1) - (s.s f1 a0) - (neg.s f1 f1) - (s.s f1 a0 4) - (m f1 r0) - (s.s f1 a0 8) - (m f1 r0) - (s.s f1 a0 12) - (m f1 r0) - (s.s f1 v1) - (m f1 r0) - (s.s f1 v1 4) - (s.s f0 v1 8) - (neg.s f0 f0) - (s.s f0 v1 12) - (m! v1 *gsf-buffer*) - (add.i a0 v1 32) - (s.w a0 v1) - (add.i a0 v1 448) - (s.w a0 v1 4) - (add.i a0 v1 #x19d0) - (s.w a0 v1 8) - (add.i a0 v1 2784) - (s.w a0 v1 2752) - (add.i a0 v1 3200) - (s.w a0 v1 2756) - (add.i a0 v1 #x19d0) - (s.w a0 v1 2760) - (m! v1 *gsf-buffer*) - (add.i a0 v1 #x1580) - (m a0 a0) - (m! a1 *inv-init-table*) - (m a1 a1) - (s.q r0 a0) - (s.q r0 a0 544) - (s.q r0 a0 16) - (s.q r0 a0 560) - (s.q r0 a0 208) - (s.q r0 a0 752) - (s.q r0 a0 224) - (s.q r0 a0 768) - (s.q r0 a0 240) - (s.q r0 a0 784) - (s.q r0 a0 384) - (s.q r0 a0 800) - (s.q r0 a0 400) - (s.q r0 a0 944) - (s.q r0 a0 416) - (s.q r0 a0 960) - (s.q r0 a0 432) - (s.q r0 a0 976) - (s.q r0 a0 448) - (s.q r0 a0 992) - (s.q r0 a0 464) - (s.q r0 a0 1008) - (s.q r0 a0 480) - (s.q r0 a0 1024) - (s.q r0 a0 496) - (s.q r0 a0 1040) - (s.q r0 a0 512) - (s.q r0 a0 1056) - (s.q r0 a0 528) - (s.q r0 a0 1072) - (add.i a2 r0 8) - (nop!) - (label merc-init-seed-next-matrix-row) - (l.hu t1 a1 2) - (add.i a2 a2 -1) - (l.bu a3 a1) - (nop!) - (l.bu t0 a1 1) - (nop!) - (add t1 t1 a0) - (add.i a1 a1 4) - (label merc-init-write-inverse-pair) - (s.b t0 t1) - (add.i a3 a3 -1) - (s.b t0 t1 550) - (add.i t0 t0 2) - (add.i t1 t1 3) - (nop!) - (b.nz a3 merc-init-write-inverse-pair :delay (nop!)) - (b.nz a2 merc-init-seed-next-matrix-row :delay (nop!)) - (add a0 r0 v1) - (m a0 a0) - (s.q r0 a0 416) - (s.q r0 a0 432) - (add.i v1 v1 2752) - (m v1 v1) - (s.q r0 v1 416) - (s.q r0 v1 432) - (m v1 r0) - (lui v1 #x1000) - (ori v1 v1 #x8000) - (lui a0 #x7000) - (ori a0 a0 100) - (l.w a1 v1) - (and.i a1 a1 DMA-CHCR-STR) - (b.z a1 merc-init-done :delay (at-label merc-init-poll-vu0-upload (l.w a1 a0))) - (nop!) - (l.w a2 v1) - (nop!) - (and.i a2 a2 DMA-CHCR-STR) - (add.i a1 a1 1) - (b.nz a2 merc-init-poll-vu0-upload :delay (s.w a1 a0)) - (label merc-init-done) - (m v1 r0) - (m v0 r0) - (l.d ra sp) - (l.d fp sp 8) - (jr ra :delay (add.i sp sp 16)) - - ) - - (defun mercneric-bittable-asm () - "Expand the fragment's packed kick-suppression bits into the high byte of each gsf-ik record." - (declare (asm-func none) (allow-saved-regs)) - ;; MERC keeps one bit per draw point saying whether that point suppresses the strip kick, packed - ;; sixteen to a halfword at merc-fp-header.kick-info-offset. GENERIC wants a whole byte for it, in - ;; the second half of each gsf-ik record, so the bits have to be spread out by eight. - ;; - ;; Three chained pextlb replicate one byte into all sixteen lanes, an AND against a one-bit-per-lane - ;; ladder leaves a distinct bit alive in each, pceqb turns "set" into all ones and a second AND - ;; reduces that to 0x01. Widening the result to halfwords puts the flag where gsf-ik.no-kick lives - ;; and leaves gsf-ik.index at zero for the vertex stage to fill in - sixteen flags, thirty-two - ;; bytes of index/kick list, no branches inside the pass. - (nop!) - (lui v1 #x7000) - (nop!) - (l.w a1 v1 (generic-work-offset in-buf merc shadow p-fheader)) - (nop!) - (l.w a0 v1 (generic-work-offset in-buf merc shadow indexed-output-base)) - (nop!) - (l.bu a2 a1 13) - (nop!) - (l.bu v1 a1 14) - (add.i a0 a0 32) - (l.bu a3 a1 15) - (add a1 a1 a2) - (mult3 a3 v1 a3) - (m a1 a1) - (m v1 v1) - (m a0 a0) - (add.i a2 r0 513) - (add.i t0 r0 257) - (sll t2 a2 18) - (sll t1 t0 16) - (or a2 a2 t2) - (or t0 t0 t1) - (dsll32 t2 a2 4) - (dsll32 t1 t0 0) - (or a2 a2 t2) - (or t0 t0 t1) - (pcpyld a2 a2 a2) - (mmi-nop!) - (pcpyld t0 t0 t0) - (mmi-nop!) - (add a3 a3 a1) - (mmi-nop!) - (l.hu t1 a1) - (add a1 a1 v1) - (nop!) - (mmi-nop!) - (nop!) - (pextlb t1 t1 t1) - (b mnb-expand-16-flags :delay (pextlb t1 t1 t1)) - ;; Loop head: step the bitfield source by kick-info-step, the list by 32 bytes, run the third - ;; replication, and lazily store the previous pass's second quadword. - (asm-block next-hword - (label mnb-next-hword) - (add a1 a1 v1) - (add.i a0 a0 32) - (pextlb t1 t1 t1) - (s.q v0 a0 -16)) - ;; Sixteen flags out of one halfword: mask against the bit ladder, pceqb back to all-ones, - ;; reduce to 0x01, then widen to halfwords so each flag lands in gsf-ik.no-kick with the - ;; index byte left at zero for the vertex stage to fill in. - (asm-block expand-16-flags - (label mnb-expand-16-flags) - (pextlb t1 t1 t1) - (mmi-nop!) - (and.q t1 t1 a2) - (mmi-nop!) - (pceqb t1 t1 a2) - (mmi-nop!) - (and.q t3 t1 t0) - (mmi-nop!) - (pextlb t1 t3 r0) - (l.hu t2 a1) - (pextub v0 t3 r0) - (s.q t1 a0) - (b.ne a1 a3 mnb-next-hword :delay (pextlb t1 t2 t2)) - (nop!) - (s.q v0 a0 16) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!)) - ) - - ;; Builds the outgoing packet header and shader list for one fragment, and assembles the fragment's - ;; inverse table. The header is the 128-byte generic-texbuf prologue - DMA tag, four camera-matrix - ;; rows, the strip GIF tag, and two A+D quadwords whose GS registers are selected out of - ;; consts.adcmd-offsets by the envmap and translucency flags. Each shader is then copied across with - ;; its two software-owned words patched. A fragment that starts mid-strip prepends a copy of the - ;; previous fragment's shader, because a GENERIC packet cannot inherit GS state the way MERC's could. - (defun mercneric-shader-asm () - "Copy the current fragment shader records into the Generic input packet and patch their packet - addresses and effect references." - (declare (asm-func none) (allow-saved-regs)) - ;; These values cannot be baked into the level data: packet addresses depend on the current - ;; scratchpad half, while effect records select per-draw lighting, environment mapping, - ;; blending, and texture state. Copy the compact source records, expand their GIF/VIF fields, - ;; and link each effect to the packet data produced for this draw. - (add.i sp sp -64) - (s.d ra sp) - (s.q s4 sp 16) - (s.q s5 sp 32) - (s.q gp sp 48) - (lui t2 #x7000) - (add.i a0 r0 0) - (l.bu a1 t2 (generic-work-offset in-buf merc ctrl effect envmap-usage)) - (add.i a2 r0 4) - (l.w t0 t2 (generic-work-offset in-buf merc shadow p-input)) - (nop!) - (l.w v1 t2 (generic-work-offset in-buf merc shadow gsf-buf)) - (sub t4 a1 r0) - (l.w a1 t2 (generic-work-offset saves cur-outbuf)) - (movn a0 a2 t4) - (l.bu t3 t2 (generic-work-offset in-buf merc ctrl header use-translucent)) - (add t1 a0 t2) - (l.w a0 t2 (generic-work-offset in-buf merc shadow indexed-input-base)) - (add.i a2 a1 128) - (l.bu a3 t0 10) - (sll t5 t3 1) - (l.bu t3 t0 9) - (add t1 t1 t5) - (l.bu t5 t1 #x2eb1) - (nop!) - (l.bu t6 t1 #x2eb0) - (nop!) - (l.w t1 t2 (generic-work-offset in-buf merc shadow other-input-base)) - (sll t7 a3 4) - (add t6 t6 t2) - (add t8 t5 t2) - (l.w a3 t2 (generic-work-offset in-buf merc shadow indexed-output-base)) - (add t5 t7 t0) - (s.w t5 t2 (generic-work-offset in-buf merc shadow p-fheader)) - (nop!) - (l.bu t0 t5 12) - (add.i t7 r0 0) - (l.q t8 t8 #x2e70) - (movn t7 r0 t4) - (l.q t4 t6 #x2e70) - (movz t7 r0 t3) - (s.q t8 a1 96) - (add.i t6 v1 #x1580) - (s.q t4 a1 112) - (add.i v1 v1 #x17a0) - (s.w t7 a1 124) - (movn v1 t6 t3) - (l.q gp t2 (generic-work-offset fx-buf work consts)) - (add.i t6 t5 16) - (l.q s4 t2 (generic-work-offset fx-buf work consts matrix vector 0)) - (add.i t8 r0 0) - (l.q s5 t2 (generic-work-offset fx-buf work consts matrix vector 1)) - (add.i t5 r0 0) - (l.q t9 t2 (generic-work-offset fx-buf work consts matrix vector 2)) - (add.i t4 r0 -3) - (l.q ra t2 (generic-work-offset fx-buf work consts matrix vector 3)) - (add.i t7 r0 6) - (l.q t2 t2 (generic-work-offset fx-buf work consts base-strgif)) - (nop!) - (s.q gp a1) - (movz t7 r0 t3) - (s.q s4 a1 16) - (add.i gp r0 0) - (s.q s5 a1 32) - (add.i s5 r0 -32768) - (s.q t9 a1 48) - (movz gp s5 t0) - (s.q ra a1 64) - (nop!) - (b.z t3 mns-first-shader-from-fragment :delay (s.q t2 a1 80)) - (l.q t9 t1 2896) - (add gp t3 gp) - (l.q ra t1 2912) - (add.i s5 r0 -6) - (l.q t3 t1 2928) - (add.i t0 t0 1) - (l.q t2 t1 2944) - (add.i s4 t0 -3) - (b.le s4 r0 mns-store-shader :delay (l.q t1 t1 2960)) - (add.i t8 t0 -5) - (nop!) - (b.le t8 r0 mns-store-shader :delay (l.h t8 t6 172)) - (b mns-store-shader :delay (l.h t5 t6 332)) - ;; A fragment that starts a strip takes its first shader from its own data, so the two section - ;; boundaries come out of source shaders 3 and 5 rather than 2 and 4. - (asm-block first-shader-from-fragment - (label mns-first-shader-from-fragment) - (add.i t1 t0 -3) - (nop!) - (b.le t1 r0 mns-load-source-shader :delay (add.i t1 t0 -5)) - (b.le t1 r0 mns-load-source-shader :delay (l.h t8 t6 252)) - (nop!) - (l.h t5 t6 412)) - ;; One source shader: its kick offset, its strip length, and its five quadwords. - (asm-block load-source-shader - (label mns-load-source-shader) - (l.h s5 t6 12) - (nop!) - (l.h gp t6 28) - (nop!) - (l.q t9 t6) - (nop!) - (l.q ra t6 16) - (nop!) - (l.q t3 t6 32) - (nop!) - (l.q t2 t6 48) - (nop!) - (l.q t1 t6 64) - (add.i t6 t6 80)) - ;; Copy it out and patch the two words the software owns - the running kick offset in this - ;; record, and the strip length in the previous one. The draw-point total accumulates - ;; modulo 256 because that is the field width in the packet header. - (asm-block store-shader - (label mns-store-shader) - (s.q t9 a2) - (add s5 s5 t7) - (s.q ra a2 16) - (add.i t7 t7 2) - (s.q t3 a2 32) - (add t4 t4 gp) - (s.q t2 a2 48) - (add.i t4 t4 3) - (s.q t1 a2 64) - (and.i t4 t4 255) - (s.w s5 a2 12) - (add.i a2 a2 80) - (b.gt gp r0 mns-load-source-shader :delay (s.w gp a2 -52)) - (s.w t4 a1 108) - (add.i a1 t8 7) - (s.q t9 a0 2896) - (add.i t5 t5 7) - (s.q ra a0 2912) - (sra a2 a1 4) - (s.q t3 a0 2928) - (sra t3 t5 4) - (s.q t2 a0 2944) - (add.i a1 a3 192) - (s.q t1 a0 2960) - (sll a0 t0 2) - (s.h t4 a3 18) - (add a0 a0 t0) - (s.b t0 a3 16) - (add.i a0 a0 7) - (sub a0 t3 a2) - (l.q t0 v1) - (nop!) - (nop!) - (add.i t1 a2 -3) - (l.q a3 v1 16) - (nop!) - (s.q t0 a1) - (b.z t1 mns-inv-copy-b-1 :delay (l.q a2 v1 32)) - (add.i t0 t1 -1) - (s.q a3 a1 16) - (b.z t0 mns-inv-copy-b-2 :delay (l.q a3 v1 48)) - (add.i t0 t0 -1) - (s.q a2 a1 32) - (b.z t0 mns-inv-copy-b-3 :delay (l.q a2 v1 64)) - (add.i t0 t0 -1) - (s.q a3 a1 48) - (b.z t0 mns-inv-copy-b-4 :delay (l.q a3 v1 80)) - (add.i t0 t0 -1) - (s.q a2 a1 64) - (b.z t0 mns-inv-copy-b-5 :delay (l.q a2 v1 96)) - (add.i t0 t0 -1) - (s.q a3 a1 80) - (b.z t0 mns-inv-copy-b-6 :delay (l.q a3 v1 112)) - (add.i t0 t0 -1) - (s.q a2 a1 96) - (b.z t0 mns-inv-copy-b-7 :delay (l.q a2 v1 128)) - (add.i t0 t0 -1) - (s.q a3 a1 112) - (b.z t0 mns-inv-copy-b-8 :delay (l.q a3 v1 144)) - (add.i t0 t0 -1) - (s.q a2 a1 128) - (b.z t0 mns-inv-copy-b-9 :delay (l.q a2 v1 160)) - (add.i t0 t0 -1) - (s.q a3 a1 144) - (b.z t0 mns-inv-copy-b-10 :delay (l.q a3 v1 176)) - (add.i a0 t0 -1) - (s.q a2 a1 160) - (b.z a0 mns-inv-copy-b-11 :delay (l.q a0 v1 192)) - (nop!) - (s.q a3 a1 176) - (nop!) - (l.q v1 v1 208) - (nop!) - (s.q a0 a1 192) - (nop!) - (nop!) - (nop!) - (s.q v1 a1 208) - (b mns-shaders-done :delay (nop!))) - ;; The inverse table is gathered out of three regions of the template generic-merc-init-asm - ;; built: region A at +0, region B at +176, region C at +320. They are the same logical - ;; table with different gsf-ik bases, one per matrix-influence section, and the - ;; destination stays contiguous. The unrolled ladders below are one rung per quadword - ;; with a computed entry point, which is how a variable-length copy costs no branch. - (asm-block inv-copy-b-1 - (label mns-inv-copy-b-1) - (add.i a0 a0 -1) - (s.q a3 a1 16) - (nop!) - (l.q a3 v1 224)) - (asm-block inv-copy-b-2 - (label mns-inv-copy-b-2) - (add.i a0 a0 -1) - (s.q a2 a1 32) - (b.z a0 mns-inv-copy-c-1 :delay (l.q a2 v1 240))) - (asm-block inv-copy-b-3 - (label mns-inv-copy-b-3) - (add.i a0 a0 -1) - (s.q a3 a1 48) - (b.z a0 mns-inv-copy-c-2 :delay (l.q a3 v1 256))) - (asm-block inv-copy-b-4 - (label mns-inv-copy-b-4) - (add.i a0 a0 -1) - (s.q a2 a1 64) - (b.z a0 mns-inv-copy-c-3 :delay (l.q a2 v1 272))) - (asm-block inv-copy-b-5 - (label mns-inv-copy-b-5) - (add.i a0 a0 -1) - (s.q a3 a1 80) - (b.z a0 mns-inv-copy-c-4 :delay (l.q a3 v1 288))) - (asm-block inv-copy-b-6 - (label mns-inv-copy-b-6) - (add.i a0 a0 -1) - (s.q a2 a1 96) - (b.z a0 mns-inv-copy-c-5 :delay (l.q a2 v1 304))) - (asm-block inv-copy-b-7 - (label mns-inv-copy-b-7) - (add.i a0 a0 -1) - (s.q a3 a1 112) - (b.z a0 mns-inv-copy-c-6 :delay (l.q a3 v1 320))) - (asm-block inv-copy-b-8 - (label mns-inv-copy-b-8) - (add.i a0 a0 -1) - (s.q a2 a1 128) - (b.z a0 mns-inv-copy-c-7 :delay (l.q a2 v1 336))) - (asm-block inv-copy-b-9 - (label mns-inv-copy-b-9) - (add.i a0 a0 -1) - (s.q a3 a1 144) - (b.z a0 mns-inv-copy-c-8 :delay (l.q a3 v1 352))) - (asm-block inv-copy-b-10 - (label mns-inv-copy-b-10) - (add.i a0 a0 -1) - (s.q a2 a1 160) - (b.z a0 mns-inv-copy-c-9 :delay (l.q a0 v1 368))) - (asm-block inv-copy-b-11 - (label mns-inv-copy-b-11) - (nop!) - (s.q a3 a1 176) - (nop!) - (l.q v1 v1 384) - (nop!) - (s.q a0 a1 192) - (nop!) - (nop!) - (nop!) - (s.q v1 a1 208) - (b mns-shaders-done :delay (nop!))) - (asm-block inv-copy-c-1 - (label mns-inv-copy-c-1) - (nop!) - (s.q a3 a1 48) - (nop!) - (l.q a3 v1 400)) - (asm-block inv-copy-c-2 - (label mns-inv-copy-c-2) - (nop!) - (s.q a2 a1 64) - (nop!) - (l.q a2 v1 416)) - (asm-block inv-copy-c-3 - (label mns-inv-copy-c-3) - (nop!) - (s.q a3 a1 80) - (nop!) - (l.q a3 v1 432)) - (asm-block inv-copy-c-4 - (label mns-inv-copy-c-4) - (nop!) - (s.q a2 a1 96) - (nop!) - (l.q a2 v1 448)) - (asm-block inv-copy-c-5 - (label mns-inv-copy-c-5) - (nop!) - (s.q a3 a1 112) - (nop!) - (l.q a3 v1 464)) - (asm-block inv-copy-c-6 - (label mns-inv-copy-c-6) - (nop!) - (s.q a2 a1 128) - (nop!) - (l.q a2 v1 480)) - (asm-block inv-copy-c-7 - (label mns-inv-copy-c-7) - (nop!) - (s.q a3 a1 144) - (nop!) - (l.q a3 v1 496)) - (asm-block inv-copy-c-8 - (label mns-inv-copy-c-8) - (nop!) - (s.q a2 a1 160) - (nop!) - (l.q a0 v1 512)) - (asm-block inv-copy-c-9 - (label mns-inv-copy-c-9) - (nop!) - (s.q a3 a1 176) - (nop!) - (l.q v1 v1 528) - (nop!) - (s.q a0 a1 192) - (nop!) - (nop!) - (nop!) - (s.q v1 a1 208)) - ;; Shader list and inverse table complete. - (asm-block shaders-done - (label mns-shaders-done) - (m v0 r0) - (l.d ra sp) - (l.q gp sp 48) - (l.q s5 sp 32) - (l.q s4 sp 16) - (jr ra :delay (add.i sp sp 64)) - (nop!) - (nop!) - (nop!)) - ) - - ;; Despite the name, this is the whole vertex pipeline: matrix upload, then conversion, then the two - ;; vertex-sharing fixups. It sizes VU0's working set from the byte header, hands VU0 the fragment's - ;; position bias and the first sixteen bone-weight bytes and calls entry 280 to pick the one, two or - ;; three matrix continuation, uploads each bone matrix through entry 303, and then runs four packed - ;; vertices per pass through entry 311 while the EE converts texture coordinates, gathers palette - ;; colors and writes gsf-vertex records. samecopy and crosscopy afterwards handle vertices shared - ;; within this fragment and with the previous one. - (defun mercneric-matrix-asm () - "Expand the current fragment matrix data into the scratchpad table and run the MERC VU0 vertex - conversion entries." - (declare (asm-func none) (allow-saved-regs)) - ;; Matrix indices and weights arrive packed into the fragment header. The EE expands matrix - ;; rows into VU memory, starts entry 280 once to initialize the microprogram, and uses entry - ;; 303 for each matrix group. Entry 311 then consumes packed vertices. Its continuation - ;; address selects the one-, two-, or three-matrix path while the EE advances the source and - ;; destination cursors for the next group. - ;; - ;; The weight quadwords below are read from fixed +16, +32 and +48 of the byte header, which - ;; equals perc-off * 4 only when perc-off is 4 - and those bytes overlap mat-slot, declared ten - ;; long. Either a fragment never fills more than about three matrix slots, so the list's zero - ;; terminator doubles as the first weight byte, or the declared array length is too generous. - ;; The code alone does not decide it, so these three keep their numbers. - (add.i sp sp -128) - (s.q s0 sp 16) - (s.q s1 sp 32) - (s.q s2 sp 48) - (s.q s3 sp 64) - (s.q s4 sp 80) - (s.q s5 sp 96) - (s.q gp sp 112) - (nop!) - (lui s4 #x7000) - (nop!) - (l.w a0 s4 (generic-work-offset in-buf merc shadow p-input)) - (nop!) - (l.w a1 s4 (generic-work-offset in-buf merc shadow indexed-output-base)) - (nop!) - (l.q t8 a0 16) - (add.i s6 a0 48) - (l.bu a3 a0 12) - (nop!) - (l.bu t2 a0 10) - (pextlb t9 r0 t8) - (ctc2.ni vi10 a3) - (pextlh gp r0 t9) - (m.ni vf1 gp) - (pextuh t9 r0 t9) - (m.ni vf2 t9) - (pextub t9 r0 t8) - (l.bu a3 a0 4) - (pextlh gp r0 t9) - (m.ni vf3 gp) - (pextuh t9 r0 t9) - (m.ni vf4 t9) - (sll t2 t2 4) - (l.bu t0 a0 5) - (add t2 t2 a0) - (l.bu t1 a0 6) - (add.i s5 a3 -1) - (ctc2.ni vi11 a3) - (add s5 s5 t0) - (ctc2.ni vi12 t0) - (add s5 s5 t1) - (ctc2.ni vi13 t1) - (add.i t4 a1 32) - (l.vf vf27 t2) - (add.i t3 a1 192) - (callms 280) - (add.i v1 a1 448) - (l.hu s1 s4 (generic-work-offset in-buf merc ctrl header st-int-off)) - (pextlw t3 t3 t3) - (l.hu s2 s4 (generic-work-offset in-buf merc ctrl header st-int-scale)) - (pcpyld t3 t3 t3) - (l.vf vf19 s4 (generic-work-offset in-buf merc ctrl header)) - (pcpyh s1 s1) - (l.bu v0 a0 2) - (add.i t5 r0 0) - (l.bu at a0 1) - (add.i s4 r0 -1) - (lui a1 #x4b01) - (add.i v0 v0 3) - (add.i a1 a1 #x4780) - (and.i v0 v0 252) - (sll at at 2) - (sll v0 v0 2) - (l.q t8 a0 32) - (add v0 v0 a0) - (l.bu t0 a0 13) - (add at at a0) - (l.bu t2 a0 11) - (pextlw a1 a1 r0) - (b.z t0 mnm-convert-prologue :delay (pcpyld a1 a1 a1)) - (sll t2 t2 4) - (add.i a3 a0 14) - (b mnm-load-matrix-rows :delay (add t2 t2 a0)) - ;; Walk the fragment's mat-slot list until the zero terminator, handing each bone matrix to - ;; VU0 entry 303 with its destination slot in vi14. - (asm-block upload-next-matrix - (label mnm-upload-next-matrix) - (callms 303) - (m t0 t1)) - (asm-block load-matrix-rows - (label mnm-load-matrix-rows) - (l.bu t1 a3) - (add.i a3 a3 1) - (l.vf vf23 t2 64) - (nop!) - (l.vf vf24 t2 80) - (nop!) - (l.vf vf25 t2 96) - (nop!) - (l.vf vf26 t2 112) - (nop!) - (l.vf vf20 t2 16) - (nop!) - (l.vf vf21 t2 32) - (nop!) - (l.vf vf22 t2 48) - (add.i t2 t2 128) - (b.nz t1 mnm-upload-next-matrix :delay (ctc2.ni vi14 t0)) - (callms 303) - (nop!)) - ;; The software-pipelined ramp-up: four calls to entry 311 with progressively more of the loop - ;; body filled in behind them. - (asm-block convert-prologue - (label mnm-convert-prologue) - (l.q a2 v0) - (pextlb t9 r0 t8) - (mmi-nop!) - (pextlh gp r0 t9) - (mmi-nop!) - (pextlb a3 r0 a2) - (mmi-nop!) - (pextub a2 r0 a2) - (m.ni vf4 gp) - (pextlh t0 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 a3) - (pextlh t1 a1 a2) - (m.ni vf3 t1) - (pextuh t9 r0 t9) - (callms 311) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (l.q t2 v0 16) - (nop!) - (add.i at at -16) - (nop!) - (mmi-nop!) - (pextuh a2 a1 a2) - (mmi-nop!) - (pextlb t0 r0 t2) - (m.ni vf4 t9) - (pextlh t1 a1 t0) - (m.ni vf1 a2) - (pextuh t0 a1 t0) - (m.ni vf2 t1) - (pextub t9 r0 t8) - (m.ni vf3 t0) - (pextlh gp r0 t9) - (nop!) - (add.i v0 v0 -48) - (callms 311) - (nop!) - (add.i t6 r0 0) - (nop!) - (l.wl t6 v0 53) - (nop!) - (nop!) - (nop!) - (mmi-nop!) - (pextlb t6 r0 t6) - (mmi-nop!) - (pextlb t6 r0 t6) - (l.q a3 v0 80) - (add.w t6 t6 t3) - (nop!) - (nop!) - (mmi-nop!) - (pextub t2 r0 t2) - (mmi-nop!) - (pextlh t0 a1 t2) - (m.ni vf4 gp) - (pextuh t2 a1 t2) - (m.ni vf1 t0) - (pextlb t1 r0 a3) - (m.ni vf2 t2) - (pextlh a2 a1 t1) - (m.ni vf3 a2) - (pextuh t9 r0 t9) - (nop!) - (add.i v1 v1 -64) - (nop!) - (s.vf vf15 v1 64) - (callms 311) - (nop!) - (l.wr s3 v0 56) - (nop!) - (l.wl s3 v0 69) - (nop!) - (mmi-nop!) - (pcpyud t6 t6 r0) - (mmi-nop!) - (pextlb s3 r0 s3) - (nop!) - (dsllv s3 s3 s2) - (l.q t8 s6) - (add.h s3 s3 s1) - (l.bu t7 t6) - (srl32 t6 t6 0) - (mmi-nop!) - (pextuh t0 a1 t1) - (l.bu s0 t6) - (pextub a3 r0 a3) - (m.ni vf4 t9) - (pextlh t2 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 t2) - (pextlb t9 r0 t8) - (m.ni vf3 a3) - (pextlh gp r0 t9) - (nop!) - (add.i s6 s6 16) - (nop!) - (s.vf vf15 v1 96) - (callms 311) - (s.w s3 v1 76) - (l.wr t6 v0 64) - (add t7 t7 t4) - (l.wl t6 v0 77) - (add s0 s0 t4) - (b mnm-convert-loop-mid :delay (srl32 s3 s3 0))) - ;; Steady state, four vertices per pass. The packed record's bytes are widened with - ;; pextlb/pextlh and blended with 0x4b014780 so that bytes 2 and 3 arrive at VU0 already - ;; in float form - the exponent is supplied by the constant and the mantissa is the byte. - ;; - ;; The destination lookup is the trick worth reading: four destination bytes widened into - ;; four 32-bit lanes, one packed add against the replicated inverse-table base, and four - ;; pointers come out of one register through srl32 and pcpyud. The store order inside a - ;; vertex is forced by the 16-byte s.vf overwriting the texture coordinate. - (asm-block convert-loop - (label mnm-convert-loop) - (callms 311) - (s.w s3 v1 76) - (l.wr t6 v0 64) - (add t7 t7 t4) - (l.wl t6 v0 77) - (add s0 s0 t4) - (s.w a0 v1 60) - (srl32 s3 s3 0)) - (asm-block convert-loop-mid - (label mnm-convert-loop-mid) - (s.b t5 t7) - (pextlb t6 r0 t6) - (s.b t5 s0) - (pextlb t6 r0 t6) - (l.q a2 v0 96) - (add.w t6 t6 t3) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 16) - (pextlb a3 r0 a2) - (l.bu s0 t6) - (pextub a2 r0 a2) - (m.ni vf4 gp) - (pextlh t0 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 a3) - (pextlh t1 a1 a2) - (m.ni vf3 t1) - (pextuh t9 r0 t9) - (s.vf vf14 v1 80) - (add.i at at 16) - (add.i s4 s4 2) - (s.vf vf15 v1 128) - (b.eq t5 s5 mnm-convert-tail-2 :delay (add.i t5 t5 2)) - (callms 311) - (s.w s3 v1 108) - (l.wr s3 v0 80) - (add t7 t7 t4) - (l.wl s3 v0 93) - (add s0 s0 t4) - (s.w a0 v1 92) - (pcpyud t6 t6 r0) - (s.b s4 t7) - (pextlb s3 r0 s3) - (s.b s4 s0) - (dsllv s3 s3 s2) - (l.q t2 v0 112) - (add.h s3 s3 s1) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 4) - (pextuh a2 a1 a2) - (l.bu s0 t6) - (pextlb t0 r0 t2) - (m.ni vf4 t9) - (pextlh t1 a1 t0) - (m.ni vf1 a2) - (pextuh t0 a1 t0) - (m.ni vf2 t1) - (pextub t9 r0 t8) - (m.ni vf3 t0) - (pextlh gp r0 t9) - (s.vf vf14 v1 112) - (add.i v0 v0 48) - (b.eq s4 s5 mnm-convert-tail-3 :delay (s.vf vf15 v1 160)) - (callms 311) - (s.w s3 v1 140) - (l.wr t6 v0 40) - (add t7 t7 t4) - (l.wl t6 v0 53) - (add s0 s0 t4) - (s.w a0 v1 124) - (srl32 s3 s3 0) - (s.b t5 t7) - (pextlb t6 r0 t6) - (s.b t5 s0) - (pextlb t6 r0 t6) - (l.q a3 v0 80) - (add.w t6 t6 t3) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 8) - (pextub t2 r0 t2) - (l.bu s0 t6) - (pextlh t0 a1 t2) - (m.ni vf4 gp) - (pextuh t2 a1 t2) - (m.ni vf1 t0) - (pextlb t1 r0 a3) - (m.ni vf2 t2) - (pextlh a2 a1 t1) - (m.ni vf3 a2) - (pextuh t9 r0 t9) - (s.vf vf14 v1 144) - (add.i v1 v1 128) - (add.i s4 s4 2) - (s.vf vf15 v1 64) - (b.eq t5 s5 mnm-convert-done :delay (add.i t5 t5 2)) - (callms 311) - (s.w s3 v1 44) - (l.wr s3 v0 56) - (add t7 t7 t4) - (l.wl s3 v0 69) - (add s0 s0 t4) - (s.w a0 v1 28) - (pcpyud t6 t6 r0) - (s.b s4 t7) - (pextlb s3 r0 s3) - (s.b s4 s0) - (dsllv s3 s3 s2) - (l.q t8 s6) - (add.h s3 s3 s1) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 12) - (pextuh t0 a1 t1) - (l.bu s0 t6) - (pextub a3 r0 a3) - (m.ni vf4 t9) - (pextlh t2 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 t2) - (pextlb t9 r0 t8) - (m.ni vf3 a3) - (pextlh gp r0 t9) - (s.vf vf14 v1 48) - (add.i s6 s6 16) - (b.ne s4 s5 mnm-convert-loop :delay (s.vf vf15 v1 96)) - (b mnm-convert-done :delay (add.i v1 v1 32))) - ;; Three exits, one per partial group size, each fixing up the output cursor before the - ;; shared drain. - (asm-block convert-tail-2 - (label mnm-convert-tail-2) - (b mnm-convert-done :delay (add.i v1 v1 64))) - (asm-block convert-tail-3 - (label mnm-convert-tail-3) - (b mnm-convert-done :delay (add.i v1 v1 96))) - (asm-block convert-done - (label mnm-convert-done) - (s.w a0 v1 28) - (add.i v1 v1 32) - (add.i s5 s5 1) - (nop!) - (lui a3 #x7000) - (nop!) - (l.w a0 a3 (generic-work-offset in-buf merc shadow p-input)) - (nop!) - (l.w a1 a3 (generic-work-offset in-buf merc shadow indexed-output-base)) - (nop!) - (l.w a3 a3 #x1c90) - (nop!) - (l.bu s0 a0) - (add.i s4 r0 0) - (l.bu s1 a0 7) - (add.i t8 r0 0) - (l.bu s2 a0 8) - (sll s0 s0 2) - (add s0 s0 a0) - (sll s1 s1 2) - (b.z s1 mnm-crosscopy-setup :delay (add s1 s1 s0))) - ;; samecopy: a vertex drawn twice in this fragment needs only a second index entry. - (asm-block samecopy-loop - (label mnm-samecopy-loop) - (l.bu s3 s0) - (add s4 s4 t4) - (l.bu s6 s0 1) - (add t8 t8 t4) - (l.bu t9 s4) - (add s3 s3 t3) - (l.bu s4 s3) - (add s6 s6 t3) - (s.b t9 t8) - (add.i s0 s0 4) - (b.ne s0 s1 mnm-samecopy-loop :delay (l.bu t8 s6)) - (nop!) - (add s4 s4 t4) - (l.bu t9 s4) - (add t8 t8 t4) - (nop!) - (nop!) - (s.b t9 t8) - (nop!)) - ;; crosscopy: a vertex shared with the *previous* fragment has to be physically copied out - ;; of the other output buffer, because that buffer is about to be reused. - (asm-block crosscopy-setup - (label mnm-crosscopy-setup) - (sll s2 s2 2) - (add.i t5 a3 32) - (b.z s2 mnm-store-vtx-count :delay (add s2 s2 s1)) - (l.bu s3 s0) - (add.i t6 a3 192) - (l.bu s6 s0 1) - (add.i t7 a3 448) - (nop!) - (add s3 s3 t6) - (l.bu s3 s3) - (add s6 s6 t3) - (l.bu s6 s6) - (add.i v1 v1 -32) - (nop!) - (add s3 s3 t5) - (l.bu s4 s3) - (add s6 s6 t4) - (nop!) - (add.i s0 s0 4) - (s.b s5 s6) - (add.i s5 s5 1) - (b.eq s0 s2 mnm-crosscopy-drain :delay (nop!))) - (asm-block crosscopy-loop - (label mnm-crosscopy-loop) - (l.bu s3 s0) - (sll s4 s4 5) - (l.bu s6 s0 1) - (add s4 s4 t7) - (l.q t9 s4) - (add s3 s3 t6) - (l.bu s3 s3) - (add s6 s6 t3) - (l.bu s6 s6) - (add.i v1 v1 32) - (l.q gp s4 16) - (add s3 s3 t5) - (l.bu s4 s3) - (add s6 s6 t4) - (s.q t9 v1) - (add.i s0 s0 4) - (s.b s5 s6) - (add.i s5 s5 1) - (b.ne s0 s2 mnm-crosscopy-loop :delay (s.q gp v1 16))) - (asm-block crosscopy-drain - (label mnm-crosscopy-drain) - (nop!) - (sll s4 s4 5) - (nop!) - (add s4 s4 t7) - (l.q t9 s4) - (nop!) - (nop!) - (nop!) - (nop!) - (add.i v1 v1 32) - (l.q gp s4 16) - (nop!) - (nop!) - (nop!) - (s.q t9 v1) - (nop!) - (nop!) - (nop!) - (nop!) - (s.q gp v1 16)) - ;; Publish the vertex total the packet builder will unpack. - (asm-block store-vtx-count - (label mnm-store-vtx-count) - (s.h s5 a1 20) - (m v0 s5) - (l.q gp sp 112) - (l.q s5 sp 96) - (l.q s4 sp 80) - (l.q s3 sp 64) - (l.q s2 sp 48) - (l.q s1 sp 32) - (l.q s0 sp 16) - (jr ra :delay (add.i sp sp 128)) - (nop!) - (nop!) - (nop!)) - ) - - ;; The per-fragment driver, and simply the three stages above inlined back to back with no calls: - ;; shader list and inverse table, then the kick bits, then the matrices and vertices. Reached through - ;; a function pointer out of gm-shadow, which is why it saves its registers into the scratchpad work - ;; area rather than onto the stack. Worth reading against the three standalone copies, which carry - ;; the per-block explanations; the only differences here are register assignment and the dropped - ;; return value. - (defun mercneric-convert () - "Convert one packed MERC fragment into Generic matrix, shader, and expanded vertex streams while - overlapping EE work with VU0 conversion." - (declare (asm-func none) (allow-saved-regs)) - ;; The packed fragment is split into matrix references, shader/effect records, and vertex - ;; blocks. Build the lookup and shader portions first, then alternate VU0 conversion with EE - ;; copies so the next block is ready when the current microprogram finishes. The final Generic - ;; header records the expanded stream sizes and the next output cursor. - (lui at #x7000) - (s.q s0 at (generic-work-offset in-buf merc stack 0)) - (s.q s1 at (generic-work-offset in-buf merc stack 1)) - (s.q s2 at (generic-work-offset in-buf merc stack 2)) - (s.q s3 at (generic-work-offset in-buf merc stack 3)) - (s.q s4 at (generic-work-offset in-buf merc stack 4)) - (s.q s5 at (generic-work-offset in-buf merc stack 5)) - (s.q s6 at (generic-work-offset in-buf merc stack 6)) - (s.q t8 at (generic-work-offset in-buf merc stack 7)) - (s.q t9 at (generic-work-offset in-buf merc stack 8)) - (s.q gp at (generic-work-offset in-buf merc stack 9)) - (s.q sp at (generic-work-offset in-buf merc stack 10)) - (s.q fp at (generic-work-offset in-buf merc stack 11)) - (s.q ra at (generic-work-offset in-buf merc stack 12)) - (nop!) - (lui t2 #x7000) - (add.i a0 r0 0) - (l.bu a1 t2 (generic-work-offset in-buf merc ctrl effect envmap-usage)) - (add.i a2 r0 4) - (l.w t0 t2 (generic-work-offset in-buf merc shadow p-input)) - (nop!) - (l.w v1 t2 (generic-work-offset in-buf merc shadow gsf-buf)) - (sub t4 a1 r0) - (l.w a1 t2 (generic-work-offset saves cur-outbuf)) - (movn a0 a2 t4) - (l.bu t3 t2 (generic-work-offset in-buf merc ctrl header use-translucent)) - (add t1 a0 t2) - (l.w a0 t2 (generic-work-offset in-buf merc shadow indexed-input-base)) - (add.i a2 a1 128) - (l.bu a3 t0 10) - (sll t5 t3 1) - (l.bu t3 t0 9) - (add t1 t1 t5) - (l.bu t5 t1 #x2eb1) - (nop!) - (l.bu t6 t1 #x2eb0) - (nop!) - (l.w t1 t2 (generic-work-offset in-buf merc shadow other-input-base)) - (sll t7 a3 4) - (add t6 t6 t2) - (add t8 t5 t2) - (l.w a3 t2 (generic-work-offset in-buf merc shadow indexed-output-base)) - (add t5 t7 t0) - (s.w t5 t2 (generic-work-offset in-buf merc shadow p-fheader)) - (nop!) - (l.bu t0 t5 12) - (add.i t7 r0 0) - (l.q t8 t8 #x2e70) - (movn t7 r0 t4) - (l.q t4 t6 #x2e70) - (movz t7 r0 t3) - (s.q t8 a1 96) - (add.i t6 v1 #x1580) - (s.q t4 a1 112) - (add.i v1 v1 #x17a0) - (s.w t7 a1 124) - (movn v1 t6 t3) - (l.q gp t2 (generic-work-offset fx-buf work consts)) - (add.i t6 t5 16) - (l.q s4 t2 (generic-work-offset fx-buf work consts matrix vector 0)) - (add.i t8 r0 0) - (l.q s5 t2 (generic-work-offset fx-buf work consts matrix vector 1)) - (add.i t5 r0 0) - (l.q t9 t2 (generic-work-offset fx-buf work consts matrix vector 2)) - (add.i t4 r0 -3) - (l.q ra t2 (generic-work-offset fx-buf work consts matrix vector 3)) - (add.i t7 r0 6) - (l.q t2 t2 (generic-work-offset fx-buf work consts base-strgif)) - (nop!) - (s.q gp a1) - (movz t7 r0 t3) - (s.q s4 a1 16) - (add.i gp r0 0) - (s.q s5 a1 32) - (add.i s5 r0 -32768) - (s.q t9 a1 48) - (movz gp s5 t0) - (s.q ra a1 64) - (nop!) - (b.z t3 mnc-first-shader-from-fragment :delay (s.q t2 a1 80)) - (l.q t9 t1 2896) - (add gp t3 gp) - (l.q ra t1 2912) - (add.i s5 r0 -6) - (l.q t3 t1 2928) - (add.i t0 t0 1) - (l.q t2 t1 2944) - (add.i s4 t0 -3) - (b.le s4 r0 mnc-store-shader :delay (l.q t1 t1 2960)) - (add.i t8 t0 -5) - (nop!) - (b.le t8 r0 mnc-store-shader :delay (l.h t8 t6 172)) - (b mnc-store-shader :delay (l.h t5 t6 332)) - (asm-block first-shader-from-fragment - (label mnc-first-shader-from-fragment) - (add.i t1 t0 -3) - (nop!) - (b.le t1 r0 mnc-load-source-shader :delay (add.i t1 t0 -5)) - (b.le t1 r0 mnc-load-source-shader :delay (l.h t8 t6 252)) - (nop!) - (l.h t5 t6 412)) - (asm-block load-source-shader - (label mnc-load-source-shader) - (l.h s5 t6 12) - (nop!) - (l.h gp t6 28) - (nop!) - (l.q t9 t6) - (nop!) - (l.q ra t6 16) - (nop!) - (l.q t3 t6 32) - (nop!) - (l.q t2 t6 48) - (nop!) - (l.q t1 t6 64) - (add.i t6 t6 80)) - (asm-block store-shader - (label mnc-store-shader) - (s.q t9 a2) - (add s5 s5 t7) - (s.q ra a2 16) - (add.i t7 t7 2) - (s.q t3 a2 32) - (add t4 t4 gp) - (s.q t2 a2 48) - (add.i t4 t4 3) - (s.q t1 a2 64) - (and.i t4 t4 255) - (s.w s5 a2 12) - (add.i a2 a2 80) - (b.gt gp r0 mnc-load-source-shader :delay (s.w gp a2 -52)) - (s.w t4 a1 108) - (add.i a1 t8 7) - (s.q t9 a0 2896) - (add.i t5 t5 7) - (s.q ra a0 2912) - (sra a2 a1 4) - (s.q t3 a0 2928) - (sra t3 t5 4) - (s.q t2 a0 2944) - (add.i a1 a3 192) - (s.q t1 a0 2960) - (sll a0 t0 2) - (s.h t4 a3 18) - (add a0 a0 t0) - (s.b t0 a3 16) - (add.i a0 a0 7) - (sub a0 t3 a2) - (l.q t0 v1) - (nop!) - (nop!) - (add.i t1 a2 -3) - (l.q a3 v1 16) - (nop!) - (s.q t0 a1) - (b.z t1 mnc-inv-copy-b-1 :delay (l.q a2 v1 32)) - (add.i t0 t1 -1) - (s.q a3 a1 16) - (b.z t0 mnc-inv-copy-b-2 :delay (l.q a3 v1 48)) - (add.i t0 t0 -1) - (s.q a2 a1 32) - (b.z t0 mnc-inv-copy-b-3 :delay (l.q a2 v1 64)) - (add.i t0 t0 -1) - (s.q a3 a1 48) - (b.z t0 mnc-inv-copy-b-4 :delay (l.q a3 v1 80)) - (add.i t0 t0 -1) - (s.q a2 a1 64) - (b.z t0 mnc-inv-copy-b-5 :delay (l.q a2 v1 96)) - (add.i t0 t0 -1) - (s.q a3 a1 80) - (b.z t0 mnc-inv-copy-b-6 :delay (l.q a3 v1 112)) - (add.i t0 t0 -1) - (s.q a2 a1 96) - (b.z t0 mnc-inv-copy-b-7 :delay (l.q a2 v1 128)) - (add.i t0 t0 -1) - (s.q a3 a1 112) - (b.z t0 mnc-inv-copy-b-8 :delay (l.q a3 v1 144)) - (add.i t0 t0 -1) - (s.q a2 a1 128) - (b.z t0 mnc-inv-copy-b-9 :delay (l.q a2 v1 160)) - (add.i t0 t0 -1) - (s.q a3 a1 144) - (b.z t0 mnc-inv-copy-b-10 :delay (l.q a3 v1 176)) - (add.i a0 t0 -1) - (s.q a2 a1 160) - (b.z a0 mnc-inv-copy-b-11 :delay (l.q a0 v1 192)) - (nop!) - (s.q a3 a1 176) - (nop!) - (l.q v1 v1 208) - (nop!) - (s.q a0 a1 192) - (nop!) - (nop!) - (nop!) - (s.q v1 a1 208) - (b mnc-shaders-done :delay (nop!))) - (asm-block inv-copy-b-1 - (label mnc-inv-copy-b-1) - (add.i a0 a0 -1) - (s.q a3 a1 16) - (nop!) - (l.q a3 v1 224)) - (asm-block inv-copy-b-2 - (label mnc-inv-copy-b-2) - (add.i a0 a0 -1) - (s.q a2 a1 32) - (b.z a0 mnc-inv-copy-c-1 :delay (l.q a2 v1 240))) - (asm-block inv-copy-b-3 - (label mnc-inv-copy-b-3) - (add.i a0 a0 -1) - (s.q a3 a1 48) - (b.z a0 mnc-inv-copy-c-2 :delay (l.q a3 v1 256))) - (asm-block inv-copy-b-4 - (label mnc-inv-copy-b-4) - (add.i a0 a0 -1) - (s.q a2 a1 64) - (b.z a0 mnc-inv-copy-c-3 :delay (l.q a2 v1 272))) - (asm-block inv-copy-b-5 - (label mnc-inv-copy-b-5) - (add.i a0 a0 -1) - (s.q a3 a1 80) - (b.z a0 mnc-inv-copy-c-4 :delay (l.q a3 v1 288))) - (asm-block inv-copy-b-6 - (label mnc-inv-copy-b-6) - (add.i a0 a0 -1) - (s.q a2 a1 96) - (b.z a0 mnc-inv-copy-c-5 :delay (l.q a2 v1 304))) - (asm-block inv-copy-b-7 - (label mnc-inv-copy-b-7) - (add.i a0 a0 -1) - (s.q a3 a1 112) - (b.z a0 mnc-inv-copy-c-6 :delay (l.q a3 v1 320))) - (asm-block inv-copy-b-8 - (label mnc-inv-copy-b-8) - (add.i a0 a0 -1) - (s.q a2 a1 128) - (b.z a0 mnc-inv-copy-c-7 :delay (l.q a2 v1 336))) - (asm-block inv-copy-b-9 - (label mnc-inv-copy-b-9) - (add.i a0 a0 -1) - (s.q a3 a1 144) - (b.z a0 mnc-inv-copy-c-8 :delay (l.q a3 v1 352))) - (asm-block inv-copy-b-10 - (label mnc-inv-copy-b-10) - (add.i a0 a0 -1) - (s.q a2 a1 160) - (b.z a0 mnc-inv-copy-c-9 :delay (l.q a0 v1 368))) - (asm-block inv-copy-b-11 - (label mnc-inv-copy-b-11) - (nop!) - (s.q a3 a1 176) - (nop!) - (l.q v1 v1 384) - (nop!) - (s.q a0 a1 192) - (nop!) - (nop!) - (nop!) - (s.q v1 a1 208) - (b mnc-shaders-done :delay (nop!))) - (asm-block inv-copy-c-1 - (label mnc-inv-copy-c-1) - (nop!) - (s.q a3 a1 48) - (nop!) - (l.q a3 v1 400)) - (asm-block inv-copy-c-2 - (label mnc-inv-copy-c-2) - (nop!) - (s.q a2 a1 64) - (nop!) - (l.q a2 v1 416)) - (asm-block inv-copy-c-3 - (label mnc-inv-copy-c-3) - (nop!) - (s.q a3 a1 80) - (nop!) - (l.q a3 v1 432)) - (asm-block inv-copy-c-4 - (label mnc-inv-copy-c-4) - (nop!) - (s.q a2 a1 96) - (nop!) - (l.q a2 v1 448)) - (asm-block inv-copy-c-5 - (label mnc-inv-copy-c-5) - (nop!) - (s.q a3 a1 112) - (nop!) - (l.q a3 v1 464)) - (asm-block inv-copy-c-6 - (label mnc-inv-copy-c-6) - (nop!) - (s.q a2 a1 128) - (nop!) - (l.q a2 v1 480)) - (asm-block inv-copy-c-7 - (label mnc-inv-copy-c-7) - (nop!) - (s.q a3 a1 144) - (nop!) - (l.q a3 v1 496)) - (asm-block inv-copy-c-8 - (label mnc-inv-copy-c-8) - (nop!) - (s.q a2 a1 160) - (nop!) - (l.q a0 v1 512)) - (asm-block inv-copy-c-9 - (label mnc-inv-copy-c-9) - (nop!) - (s.q a3 a1 176) - (nop!) - (l.q v1 v1 528) - (nop!) - (s.q a0 a1 192) - (nop!) - (nop!) - (nop!) - (s.q v1 a1 208)) - (asm-block shaders-done - (label mnc-shaders-done) - (lui v1 #x7000) - (nop!) - (l.w a1 v1 (generic-work-offset in-buf merc shadow p-fheader)) - (nop!) - (l.w a0 v1 (generic-work-offset in-buf merc shadow indexed-output-base)) - (nop!) - (l.bu a2 a1 13) - (nop!) - (l.bu v1 a1 14) - (add.i a0 a0 32) - (l.bu a3 a1 15) - (add a1 a1 a2) - (mult3 a3 v1 a3) - (m a1 a1) - (m v1 v1) - (m a0 a0) - (add.i a2 r0 513) - (add.i t0 r0 257) - (sll t2 a2 18) - (sll t1 t0 16) - (or a2 a2 t2) - (or t0 t0 t1) - (dsll32 t2 a2 4) - (dsll32 t1 t0 0) - (or a2 a2 t2) - (or t0 t0 t1) - (pcpyld a2 a2 a2) - (mmi-nop!) - (pcpyld t0 t0 t0) - (mmi-nop!) - (add a3 a3 a1) - (mmi-nop!) - (l.hu t1 a1) - (add a1 a1 v1) - (nop!) - (mmi-nop!) - (nop!) - (pextlb t1 t1 t1) - (b mnc-expand-16-flags :delay (pextlb t2 t1 t1))) - (asm-block next-hword - (label mnc-next-hword) - (add a1 a1 v1) - (add.i a0 a0 32) - (pextlb t2 t2 t2) - (s.q t1 a0 -16)) - (asm-block expand-16-flags - (label mnc-expand-16-flags) - (pextlb t1 t2 t2) - (mmi-nop!) - (and.q t1 t1 a2) - (mmi-nop!) - (pceqb t1 t1 a2) - (mmi-nop!) - (and.q t1 t1 t0) - (mmi-nop!) - (pextlb t2 t1 r0) - (l.hu t3 a1) - (pextub t1 t1 r0) - (s.q t2 a0) - (b.ne a1 a3 mnc-next-hword :delay (pextlb t2 t3 t3)) - (nop!) - (s.q t1 a0 16) - (lui s4 #x7000) - (nop!) - (l.w a0 s4 (generic-work-offset in-buf merc shadow p-input)) - (nop!) - (l.w a1 s4 (generic-work-offset in-buf merc shadow indexed-output-base)) - (nop!) - (l.q t8 a0 16) - (add.i s6 a0 48) - (l.bu a3 a0 12) - (nop!) - (l.bu t2 a0 10) - (pextlb t9 r0 t8) - (ctc2.ni vi10 a3) - (pextlh gp r0 t9) - (m.ni vf1 gp) - (pextuh t9 r0 t9) - (m.ni vf2 t9) - (pextub t9 r0 t8) - (l.bu a3 a0 4) - (pextlh gp r0 t9) - (m.ni vf3 gp) - (pextuh t9 r0 t9) - (m.ni vf4 t9) - (sll t2 t2 4) - (l.bu t0 a0 5) - (add t2 t2 a0) - (l.bu t1 a0 6) - (add.i s5 a3 -1) - (ctc2.ni vi11 a3) - (add s5 s5 t0) - (ctc2.ni vi12 t0) - (add s5 s5 t1) - (ctc2.ni vi13 t1) - (add.i t4 a1 32) - (l.vf vf27 t2) - (add.i t3 a1 192) - (callms 280) - (add.i v1 a1 448) - (l.hu s1 s4 (generic-work-offset in-buf merc ctrl header st-int-off)) - (pextlw t3 t3 t3) - (l.hu s2 s4 (generic-work-offset in-buf merc ctrl header st-int-scale)) - (pcpyld t3 t3 t3) - (l.vf vf19 s4 (generic-work-offset in-buf merc ctrl header)) - (pcpyh s1 s1) - (l.bu v0 a0 2) - (add.i t5 r0 0) - (l.bu at a0 1) - (add.i s4 r0 -1) - (lui a1 #x4b01) - (add.i v0 v0 3) - (add.i a1 a1 #x4780) - (and.i v0 v0 252) - (sll at at 2) - (sll v0 v0 2) - (l.q t8 a0 32) - (add v0 v0 a0) - (l.bu t0 a0 13) - (add at at a0) - (l.bu t2 a0 11) - (pextlw a1 a1 r0) - (b.z t0 mnc-convert-prologue :delay (pcpyld a1 a1 a1)) - (sll t2 t2 4) - (add.i a3 a0 14) - (b mnc-load-matrix-rows :delay (add t2 t2 a0))) - (asm-block upload-next-matrix - (label mnc-upload-next-matrix) - (callms 303) - (m t0 t1)) - (asm-block load-matrix-rows - (label mnc-load-matrix-rows) - (l.bu t1 a3) - (add.i a3 a3 1) - (l.vf vf23 t2 64) - (nop!) - (l.vf vf24 t2 80) - (nop!) - (l.vf vf25 t2 96) - (nop!) - (l.vf vf26 t2 112) - (nop!) - (l.vf vf20 t2 16) - (nop!) - (l.vf vf21 t2 32) - (nop!) - (l.vf vf22 t2 48) - (add.i t2 t2 128) - (b.nz t1 mnc-upload-next-matrix :delay (ctc2.ni vi14 t0)) - (callms 303) - (nop!)) - (asm-block convert-prologue - (label mnc-convert-prologue) - (l.q a2 v0) - (pextlb t9 r0 t8) - (mmi-nop!) - (pextlh gp r0 t9) - (mmi-nop!) - (pextlb a3 r0 a2) - (mmi-nop!) - (pextub a2 r0 a2) - (m.ni vf4 gp) - (pextlh t0 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 a3) - (pextlh t1 a1 a2) - (m.ni vf3 t1) - (pextuh t9 r0 t9) - (callms 311) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (l.q t2 v0 16) - (nop!) - (add.i at at -16) - (nop!) - (mmi-nop!) - (pextuh a2 a1 a2) - (mmi-nop!) - (pextlb t0 r0 t2) - (m.ni vf4 t9) - (pextlh t1 a1 t0) - (m.ni vf1 a2) - (pextuh t0 a1 t0) - (m.ni vf2 t1) - (pextub t9 r0 t8) - (m.ni vf3 t0) - (pextlh gp r0 t9) - (nop!) - (add.i v0 v0 -48) - (callms 311) - (nop!) - (add.i t6 r0 0) - (nop!) - (l.wl t6 v0 53) - (nop!) - (nop!) - (nop!) - (mmi-nop!) - (pextlb t6 r0 t6) - (mmi-nop!) - (pextlb t6 r0 t6) - (l.q a3 v0 80) - (add.w t6 t6 t3) - (nop!) - (nop!) - (mmi-nop!) - (pextub t2 r0 t2) - (mmi-nop!) - (pextlh t0 a1 t2) - (m.ni vf4 gp) - (pextuh t2 a1 t2) - (m.ni vf1 t0) - (pextlb t1 r0 a3) - (m.ni vf2 t2) - (pextlh a2 a1 t1) - (m.ni vf3 a2) - (pextuh t9 r0 t9) - (nop!) - (add.i v1 v1 -64) - (nop!) - (s.vf vf15 v1 64) - (callms 311) - (nop!) - (l.wr s3 v0 56) - (nop!) - (l.wl s3 v0 69) - (nop!) - (mmi-nop!) - (pcpyud t6 t6 r0) - (mmi-nop!) - (pextlb s3 r0 s3) - (nop!) - (dsllv s3 s3 s2) - (l.q t8 s6) - (add.h s3 s3 s1) - (l.bu t7 t6) - (srl32 t6 t6 0) - (mmi-nop!) - (pextuh t0 a1 t1) - (l.bu s0 t6) - (pextub a3 r0 a3) - (m.ni vf4 t9) - (pextlh t2 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 t2) - (pextlb t9 r0 t8) - (m.ni vf3 a3) - (pextlh gp r0 t9) - (nop!) - (add.i s6 s6 16) - (nop!) - (s.vf vf15 v1 96) - (callms 311) - (s.w s3 v1 76) - (l.wr t6 v0 64) - (add t7 t7 t4) - (l.wl t6 v0 77) - (add s0 s0 t4) - (b mnc-convert-loop-mid :delay (srl32 s3 s3 0))) - (asm-block convert-loop - (label mnc-convert-loop) - (callms 311) - (s.w s3 v1 76) - (l.wr t6 v0 64) - (add t7 t7 t4) - (l.wl t6 v0 77) - (add s0 s0 t4) - (s.w a0 v1 60) - (srl32 s3 s3 0)) - (asm-block convert-loop-mid - (label mnc-convert-loop-mid) - (s.b t5 t7) - (pextlb t6 r0 t6) - (s.b t5 s0) - (pextlb t6 r0 t6) - (l.q a2 v0 96) - (add.w t6 t6 t3) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 16) - (pextlb a3 r0 a2) - (l.bu s0 t6) - (pextub a2 r0 a2) - (m.ni vf4 gp) - (pextlh t0 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 a3) - (pextlh t1 a1 a2) - (m.ni vf3 t1) - (pextuh t9 r0 t9) - (s.vf vf14 v1 80) - (add.i at at 16) - (add.i s4 s4 2) - (s.vf vf15 v1 128) - (b.eq t5 s5 mnc-convert-tail-2 :delay (add.i t5 t5 2)) - (callms 311) - (s.w s3 v1 108) - (l.wr s3 v0 80) - (add t7 t7 t4) - (l.wl s3 v0 93) - (add s0 s0 t4) - (s.w a0 v1 92) - (pcpyud t6 t6 r0) - (s.b s4 t7) - (pextlb s3 r0 s3) - (s.b s4 s0) - (dsllv s3 s3 s2) - (l.q t2 v0 112) - (add.h s3 s3 s1) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 4) - (pextuh a2 a1 a2) - (l.bu s0 t6) - (pextlb t0 r0 t2) - (m.ni vf4 t9) - (pextlh t1 a1 t0) - (m.ni vf1 a2) - (pextuh t0 a1 t0) - (m.ni vf2 t1) - (pextub t9 r0 t8) - (m.ni vf3 t0) - (pextlh gp r0 t9) - (s.vf vf14 v1 112) - (add.i v0 v0 48) - (b.eq s4 s5 mnc-convert-tail-3 :delay (s.vf vf15 v1 160)) - (callms 311) - (s.w s3 v1 140) - (l.wr t6 v0 40) - (add t7 t7 t4) - (l.wl t6 v0 53) - (add s0 s0 t4) - (s.w a0 v1 124) - (srl32 s3 s3 0) - (s.b t5 t7) - (pextlb t6 r0 t6) - (s.b t5 s0) - (pextlb t6 r0 t6) - (l.q a3 v0 80) - (add.w t6 t6 t3) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 8) - (pextub t2 r0 t2) - (l.bu s0 t6) - (pextlh t0 a1 t2) - (m.ni vf4 gp) - (pextuh t2 a1 t2) - (m.ni vf1 t0) - (pextlb t1 r0 a3) - (m.ni vf2 t2) - (pextlh a2 a1 t1) - (m.ni vf3 a2) - (pextuh t9 r0 t9) - (s.vf vf14 v1 144) - (add.i v1 v1 128) - (add.i s4 s4 2) - (s.vf vf15 v1 64) - (b.eq t5 s5 mnc-convert-done :delay (add.i t5 t5 2)) - (callms 311) - (s.w s3 v1 44) - (l.wr s3 v0 56) - (add t7 t7 t4) - (l.wl s3 v0 69) - (add s0 s0 t4) - (s.w a0 v1 28) - (pcpyud t6 t6 r0) - (s.b s4 t7) - (pextlb s3 r0 s3) - (s.b s4 s0) - (dsllv s3 s3 s2) - (l.q t8 s6) - (add.h s3 s3 s1) - (l.bu t7 t6) - (srl32 t6 t6 0) - (l.w a0 at 12) - (pextuh t0 a1 t1) - (l.bu s0 t6) - (pextub a3 r0 a3) - (m.ni vf4 t9) - (pextlh t2 a1 a3) - (m.ni vf1 t0) - (pextuh a3 a1 a3) - (m.ni vf2 t2) - (pextlb t9 r0 t8) - (m.ni vf3 a3) - (pextlh gp r0 t9) - (s.vf vf14 v1 48) - (add.i s6 s6 16) - (b.ne s4 s5 mnc-convert-loop :delay (s.vf vf15 v1 96)) - (b mnc-convert-done :delay (add.i v1 v1 32))) - (asm-block convert-tail-2 - (label mnc-convert-tail-2) - (b mnc-convert-done :delay (add.i v1 v1 64))) - (asm-block convert-tail-3 - (label mnc-convert-tail-3) - (b mnc-convert-done :delay (add.i v1 v1 96))) - (asm-block convert-done - (label mnc-convert-done) - (s.w a0 v1 28) - (add.i v1 v1 32) - (add.i s5 s5 1) - (nop!) - (lui a3 #x7000) - (nop!) - (l.w a0 a3 (generic-work-offset in-buf merc shadow p-input)) - (nop!) - (l.w a1 a3 (generic-work-offset in-buf merc shadow indexed-output-base)) - (nop!) - (l.w a3 a3 #x1c90) - (nop!) - (l.bu s0 a0) - (add.i s4 r0 0) - (l.bu s1 a0 7) - (add.i t8 r0 0) - (l.bu s2 a0 8) - (sll s0 s0 2) - (add s0 s0 a0) - (sll s1 s1 2) - (b.z s1 mnc-crosscopy-setup :delay (add s1 s1 s0))) - (asm-block samecopy-loop - (label mnc-samecopy-loop) - (l.bu s3 s0) - (add s4 s4 t4) - (l.bu s6 s0 1) - (add t8 t8 t4) - (l.bu t9 s4) - (add s3 s3 t3) - (l.bu s4 s3) - (add s6 s6 t3) - (s.b t9 t8) - (add.i s0 s0 4) - (b.ne s0 s1 mnc-samecopy-loop :delay (l.bu t8 s6)) - (nop!) - (add s4 s4 t4) - (l.bu t9 s4) - (add t8 t8 t4) - (nop!) - (nop!) - (s.b t9 t8) - (nop!)) - (asm-block crosscopy-setup - (label mnc-crosscopy-setup) - (sll s2 s2 2) - (add.i t5 a3 32) - (b.z s2 mnc-store-vtx-count :delay (add s2 s2 s1)) - (l.bu s3 s0) - (add.i t6 a3 192) - (l.bu s6 s0 1) - (add.i t7 a3 448) - (nop!) - (add s3 s3 t6) - (l.bu s3 s3) - (add s6 s6 t3) - (l.bu s6 s6) - (add.i v1 v1 -32) - (nop!) - (add s3 s3 t5) - (l.bu s4 s3) - (add s6 s6 t4) - (nop!) - (add.i s0 s0 4) - (s.b s5 s6) - (add.i s5 s5 1) - (b.eq s0 s2 mnc-crosscopy-drain :delay (nop!))) - (asm-block crosscopy-loop - (label mnc-crosscopy-loop) - (l.bu s3 s0) - (sll s4 s4 5) - (l.bu s6 s0 1) - (add s4 s4 t7) - (l.q t9 s4) - (add s3 s3 t6) - (l.bu s3 s3) - (add s6 s6 t3) - (l.bu s6 s6) - (add.i v1 v1 32) - (l.q gp s4 16) - (add s3 s3 t5) - (l.bu s4 s3) - (add s6 s6 t4) - (s.q t9 v1) - (add.i s0 s0 4) - (s.b s5 s6) - (add.i s5 s5 1) - (b.ne s0 s2 mnc-crosscopy-loop :delay (s.q gp v1 16))) - (asm-block crosscopy-drain - (label mnc-crosscopy-drain) - (nop!) - (sll s4 s4 5) - (nop!) - (add s4 s4 t7) - (l.q t9 s4) - (nop!) - (nop!) - (nop!) - (nop!) - (add.i v1 v1 32) - (l.q gp s4 16) - (nop!) - (nop!) - (nop!) - (s.q t9 v1) - (nop!) - (nop!) - (nop!) - (nop!) - (s.q gp v1 16)) - (asm-block store-vtx-count - (label mnc-store-vtx-count) - (s.h s5 a1 20) - (lui at #x7000) - (l.q s0 at (generic-work-offset in-buf merc stack 0)) - (l.q s1 at (generic-work-offset in-buf merc stack 1)) - (l.q s2 at (generic-work-offset in-buf merc stack 2)) - (l.q s3 at (generic-work-offset in-buf merc stack 3)) - (l.q s4 at (generic-work-offset in-buf merc stack 4)) - (l.q s5 at (generic-work-offset in-buf merc stack 5)) - (l.q s6 at (generic-work-offset in-buf merc stack 6)) - (l.q t8 at (generic-work-offset in-buf merc stack 7)) - (l.q t9 at (generic-work-offset in-buf merc stack 8)) - (l.q gp at (generic-work-offset in-buf merc stack 9)) - (l.q ra at (generic-work-offset in-buf merc stack 12)) - (l.q sp at (generic-work-offset in-buf merc stack 10)) - (jr ra :delay (l.q fp at (generic-work-offset in-buf merc stack 11))) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!)) - ) - - (defun high-speed-reject () - "Transform up to eight packed bounds vectors and reject the current MERC control when every - tested point lies outside a common camera-space boundary." - (declare (asm-func none) (allow-saved-regs)) - ;; VU0 entries 438 and 454 test two groups of four points against the same transformed - ;; boundaries. A fragment is rejected only when the accumulated flag masks identify a common - ;; outside half-space for all participating points; crossing or straddling points keep it. - ;; The result is written into the packed control byte consumed by the converter. - (lui v1 #x7000) - (l.w a0 v1 (generic-work-offset in-buf merc shadow indexed-output-base)) - (add.i a1 a0 448) - (l.hu a3 a0 20) - (vmaxw.xyzw vf9 vf0 vf0) - (l.vf vf10 v1 (generic-work-offset in-buf merc shadow hsr-xmult)) - (l.vf vf11 v1 (generic-work-offset in-buf merc shadow hsr-ymult)) - (l.vf vf1 a1) - (add.i a0 r0 240) - (l.vf vf2 a1 32) - (ctc2.i vi9 a0) - (l.vf vf3 a1 64) - (l.vf vf4 a1 96) - (add.i a0 r0 -1) - (nop!) - (callms 438) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (l.vf vf5 a1 128) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (nop!) - (l.vf vf6 a1 160) - (nop!) - (nop!) - (nop!) - (nop!) - (l.wu t0 a1 8) - (l.vf vf7 a1 192) - (nop!) - (nop!) - (cfc2.ni a2 vi1) - (l.vf vf8 a1 224) - (srl t0 t0 31) - (or t0 a2 t0) - (l.wu a2 a1 40) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (add.i a1 a1 128) - (add.i a3 a3 -1) - (b.z a3 hsr-all-points-outside :delay (cfc2.ni t0 vi2)) - (label hsr-test-next-group) - (callms 454) - (srl a2 a2 31) - (or a2 t0 a2) - (l.wu t0 a1 -56) - (b.z a2 hsr-done :delay (and a0 a0 a2)) - (l.vf vf1 a1 128) - (add.i a2 a3 -1) - (b.z a2 hsr-all-points-outside :delay (cfc2.ni a3 vi3)) - (add.i a2 a2 -1) - (srl t0 t0 31) - (or t0 a3 t0) - (l.wu a3 a1 -24) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi4)) - (l.vf vf2 a1 160) - (srl a3 a3 31) - (or t0 t0 a3) - (l.wu a3 a1 8) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (l.vf vf3 a1 192) - (add.i a2 a2 -1) - (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi5)) - (l.vf vf4 a1 224) - (srl a3 a3 31) - (or t0 t0 a3) - (l.wu a3 a1 40) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (add.i a1 a1 128) - (add.i a2 a2 -1) - (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi6)) - (callms 438) - (srl a3 a3 31) - (or t0 t0 a3) - (l.wu a3 a1 -56) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (l.vf vf5 a1 128) - (add.i a2 a2 -1) - (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi7)) - (add.i a2 a2 -1) - (srl a3 a3 31) - (or t0 t0 a3) - (l.wu a3 a1 -24) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (b.z a2 hsr-all-points-outside :delay (cfc2.ni t0 vi8)) - (l.vf vf6 a1 160) - (srl a3 a3 31) - (or a3 t0 a3) - (l.wu t0 a1 8) - (b.z a3 hsr-done :delay (and a0 a0 a3)) - (l.vf vf7 a1 192) - (add.i a3 a2 -1) - (b.z a3 hsr-all-points-outside :delay (cfc2.ni a2 vi1)) - (l.vf vf8 a1 224) - (srl t0 t0 31) - (or t0 a2 t0) - (l.wu a2 a1 40) - (b.z t0 hsr-done :delay (and a0 a0 t0)) - (add.i a1 a1 128) - (add.i a3 a3 -1) - (b.nz a3 hsr-test-next-group :delay (cfc2.ni t0 vi2)) - (label hsr-all-points-outside) - (b.z a0 hsr-done :delay (nop!)) - (s.b r0 v1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) - (label hsr-done) - (jr ra :delay (nop!)) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!) - - ) - - ;; The frame-level driver. It walks the chain of control blocks bones queued in main memory, pulling - ;; each one into a scratchpad input buffer with the toSPR channel in source-chain mode while the - ;; previous one is being converted, and alternating the two output halves of the GSF buffer in step. - ;; Per control block it patches the VU1 entry, the environment tint, the camera matrix and the light - ;; block; per fragment it converts, runs the cheap reject test, services the ripple query and the - ;; death sampler, then runs the effect processors into the scratchpad output buffer and hands that to - ;; the fromSPR channel for copy-out. - ;; - ;; Input arrives as a DMA chain with its tags transferred; output leaves as a flat block. That - ;; asymmetry is the clearest statement of the shape of the whole pass. Every spin loop charges a - ;; counter in generic-saves, which is how the renderer reports whether VU0 upload, toSPR or fromSPR - ;; was the frame's bottleneck. - (defun generic-merc-execute-asm () - "Consume queued MERC controls using double-buffered main-memory transfers, convert their - fragments, and append the resulting Generic packets to the output DMA chain." - (declare (asm-func none) (allow-saved-regs)) - ;; Input A is converted while DMA fills input B; the roles swap at each control record. Each - ;; completed output half is copied back to the global DMA buffer before its scratchpad storage - ;; is reused. The explicit DMA busy loops also charge wait counters, so the renderer can report - ;; whether VU0, main-to-scratchpad, or scratchpad-to-main transfers were the limiting stage. - (add.i sp sp -272) - (s.d ra sp) - (s.d fp sp 8) - (m fp t9) - (s.q s0 sp 160) - (s.q s1 sp 176) - (s.q s2 sp 192) - (s.q s3 sp 208) - (s.q s4 sp 224) - (s.q s5 sp 240) - (s.q gp sp 256) - (m! v1 *merc-globals*) - (l.wu s2 v1) - (add.i v1 r0 0) - (add.i gp r0 0) - (add.i s5 r0 0) - (m! s4 *gsf-buffer*) - (lui v1 #x1000) - (ori s3 v1 #xd400) - (add.i v1 r0 640) - (lui a0 #x1000) - (ori a0 a0 #xd400) - (lui a1 #x7000) - (ori a1 a1 104) - (l.w a2 a0) - (and.i a2 a2 DMA-CHCR-STR) - (b.z a2 gme-start-first-input-dma :delay (at-label gme-wait-spr-to-first (l.w a2 a1))) - (nop!) - (l.w a3 a0) - (nop!) - (and.i a3 a3 DMA-CHCR-STR) - (add.i a2 a2 1) - (b.nz a3 gme-wait-spr-to-first :delay (s.w a2 a1)) - (asm-block start-first-input-dma - (label gme-start-first-input-dma) - (m a0 r0) - (s.w v1 s3 128) - (s.w s2 s3 48) - (s.w r0 s3 32) - (sync.l) - (add.i v1 r0 324) - (s.w v1 s3) - (sync.l) - (b gme-chain-test :delay (nop!))) - ;; Two input buffers and two output halves, alternating together on buf-index. - (asm-block select-input-base - (label gme-select-input-base) - (b.nz gp gme-input-base-b :delay (nop!)) - (add.i v1 r0 640) - (lui a0 #x7000) - (add v1 v1 a0) - (b gme-other-input-base-a :delay (nop!))) - (asm-block input-base-b - (label gme-input-base-b) - (add.i v1 r0 3616) - (lui a0 #x7000) - (add v1 v1 a0)) - (asm-block other-input-base-a - (label gme-other-input-base-a) - (b.nz gp gme-other-input-base-b :delay (nop!)) - (add.i a0 r0 3616) - (lui a1 #x7000) - (add a0 a0 a1) - (b gme-unused-shader-addr-a :delay (nop!))) - (asm-block other-input-base-b - (label gme-other-input-base-b) - (add.i a0 r0 640) - (lui a1 #x7000) - (add a0 a0 a1)) - ;; Dead: both input buffers' shader slots are computed here and both values are overwritten - ;; before use. mercneric-shader-asm reaches them itself through other-input-base. - (asm-block unused-shader-addr-a - (label gme-unused-shader-addr-a) - (b.nz gp gme-unused-shader-addr-b :delay (nop!)) - (add.i a1 r0 3536) - (lui a2 #x7000) - (add a1 a1 a2) - (b gme-unused-shader-addr-c :delay (nop!))) - (asm-block unused-shader-addr-b - (label gme-unused-shader-addr-b) - (add.i a1 r0 #x1970) - (lui a2 #x7000) - (add a1 a1 a2)) - (asm-block unused-shader-addr-c - (label gme-unused-shader-addr-c) - (b.nz gp gme-unused-shader-addr-d :delay (nop!)) - (add.i a1 r0 #x1970) - (lui a2 #x7000) - (add a1 a1 a2) - (b gme-select-output-base :delay (nop!))) - (asm-block unused-shader-addr-d - (label gme-unused-shader-addr-d) - (add.i a1 r0 3536) - (lui a2 #x7000) - (add a1 a1 a2)) - (asm-block select-output-base - (label gme-select-output-base) - (b.nz gp gme-output-base-b :delay (nop!)) - (add s1 r0 s4) - (b gme-other-output-base-a :delay (nop!))) - (asm-block output-base-b - (label gme-output-base-b) - (add.i s1 s4 2752)) - (asm-block other-output-base-a - (label gme-other-output-base-a) - (b.nz gp gme-other-output-base-b :delay (nop!)) - (add.i a1 s4 2752) - (b gme-publish-shadow :delay (nop!))) - (asm-block other-output-base-b - (label gme-other-output-base-b) - (add a1 r0 s4)) - ;; Publish the buffer selection where the three stages can find it. - (asm-block publish-shadow - (label gme-publish-shadow) - (add.i a2 r0 #x1b60) - (lui a3 #x7000) - (add a2 a2 a3) - (s.w gp a2 280) - (s.w s5 a2 284) - (s.w v1 a2 292) - (s.w a0 a2 296) - (s.w s1 a2 300) - (s.w a1 a2 304) - (s.w s4 a2 312) - (m a1 s4) - (lui a1 #x1000) - (ori a1 a1 #xd400) - (lui a2 #x7000) - (ori a2 a2 104) - (l.w a3 a1) - (and.i a3 a3 DMA-CHCR-STR) - (b.z a3 gme-queue-next-input :delay (at-label gme-wait-spr-to (l.w a3 a2))) - (nop!) - (l.w t0 a1) - (nop!) - (and.i t0 t0 DMA-CHCR-STR) - (add.i a3 a3 1) - (b.nz t0 gme-wait-spr-to :delay (s.w a3 a2))) - (asm-block queue-next-input - (label gme-queue-next-input) - (m a1 r0) - (l.wu s2 v1 12) - (b.z s2 gme-per-control-setup :delay (m a1 s7)) - (add a0 r0 a0) - (and.i a0 a0 #xffff) - (s.w a0 s3 128) - (s.w s2 s3 48) - (s.w r0 s3 32) - (sync.l) - (add.i a0 r0 324) - (s.w a0 s3) - (sync.l) - (m a0 r0)) - ;; A new effect brings a control block: lights, header, and the effect record. Copy it into - ;; the work area, because the input buffer it arrived in is about to be reused. - (asm-block per-control-setup - (label gme-per-control-setup) - (b.nz s5 gme-convert-fragment :delay (m a0 s7)) - (add.i a0 r0 #x19c0) - (lui a1 #x7000) - (add a1 a0 a1) - (m a2 v1) - (l.wu a0 v1 8) - (nop!) - (add.i a3 a0 -4) - (m a1 a1) - (b.lt a3 r0 gme-copy-ctrl-tail :delay (m a2 a2))) - (asm-block copy-ctrl-quad - (label gme-copy-ctrl-quad) - (nop!) - (l.q t2 a2) - (nop!) - (l.q a3 a2 16) - (add.i a0 a0 -4) - (l.q t0 a2 32) - (add.i a1 a1 64) - (l.q t1 a2 48) - (add.i a2 a2 64) - (s.q t2 a1 -64) - (add.i t2 a0 -4) - (s.q a3 a1 -48) - (nop!) - (s.q t0 a1 -32) - (b.ge t2 r0 gme-copy-ctrl-quad :delay (s.q t1 a1 -16))) - (asm-block copy-ctrl-tail - (label gme-copy-ctrl-tail) - (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) - (add.i a2 a2 16) - (add.i a1 a1 16) - (add.i a0 a0 -1) - (s.q a3 a1 -16) - (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) - (add.i a2 a2 16) - (add.i a1 a1 16) - (add.i a0 a0 -1) - (s.q a3 a1 -16) - (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) - (add.i a2 a2 16) - (add.i a1 a1 16) - (add.i a0 a0 -1) - (s.q a3 a1 -16) - (b.z a0 gme-copy-ctrl-done :delay (l.q a3 a2)) - (add.i a2 a2 16) - (add.i a1 a1 16) - (add.i a0 a0 -1) - (s.q a3 a1 -16)) - (asm-block copy-ctrl-done - (label gme-copy-ctrl-done) - (m a0 r0) - (lui a0 #x7000) - (l.bu a0 a0 #x1a68) - (b.nz a0 gme-patch-mscal-direct :delay (nop!)) - (add.i a0 r0 8) - (b gme-mscal-patched :delay (nop!))) - ;; needs-clip picks the VU1 entry: the clipping one, or the direct one that skips the test. - (asm-block patch-mscal-direct - (label gme-patch-mscal-direct) - (add.i a0 r0 6)) - (asm-block mscal-patched - (label gme-mscal-patched) - (lui a1 #x7000) - (ori a1 a1 #x2ed0) - (s.h a0 a1) - (add.i a0 r0 #x2f10) - (lui a1 #x7000) - (add a0 a0 a1) - (lui a1 #x7000) - (l.wu a1 a1 #x1a60) - (s.w a1 a0) - (s.w a1 a0 4) - (s.w a1 a0 8) - (s.w a1 a0 12) - (lui a0 #x7000) - (l.bu a0 a0 #x1a69) - (b.z a0 gme-copy-camera-matrix :delay (nop!)) - (add.i a0 r0 #x2e20) - (lui a1 #x7000) - (add a0 a0 a1) - (add.i a1 r0 #x1ba0) - (lui a2 #x7000) - (add t0 a1 a2) - (l.q a1 t0) - (l.q a2 t0 16) - (l.q a3 t0 32) - (l.q t0 t0 48) - (s.q a1 a0) - (s.q a2 a0 16) - (s.q a3 a0 32) - (s.q t0 a0 48) - (b gme-camera-copied :delay (nop!))) - ;; use-isometric picks which of the two cached camera matrices this effect draws with. - (asm-block copy-camera-matrix - (label gme-copy-camera-matrix) - (add.i a0 r0 #x2e20) - (lui a1 #x7000) - (add a0 a0 a1) - (add.i a1 r0 #x1b60) - (lui a2 #x7000) - (add t0 a1 a2) - (l.q a1 t0) - (l.q a2 t0 16) - (l.q a3 t0 32) - (l.q t0 t0 48) - (s.q a1 a0) - (s.q a2 a0 16) - (s.q a3 a0 32) - (s.q t0 a0 48)) - (asm-block camera-copied - (label gme-camera-copied) - (lui a0 #x7000) - (l.bu a0 a0 #x1a6b) - (lui a1 #x7000) - (s.b a0 a1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) - (lui at #x7000) - (l.vf vf1 at (generic-work-offset in-buf merc ctrl lights direction 0)) - (l.vf vf2 at (generic-work-offset in-buf merc ctrl lights direction 1)) - (l.vf vf3 at (generic-work-offset in-buf merc ctrl lights direction 2)) - (l.vf vf4 at (generic-work-offset in-buf merc ctrl lights color 0)) - (l.vf vf5 at (generic-work-offset in-buf merc ctrl lights color 1)) - (l.vf vf6 at (generic-work-offset in-buf merc ctrl lights color 2)) - (l.vf vf7 at (generic-work-offset in-buf merc ctrl lights ambient)) - (s.vf vf1 at (generic-work-offset fx-buf work lights direction 0)) - (s.vf vf2 at (generic-work-offset fx-buf work lights direction 1)) - (s.vf vf3 at (generic-work-offset fx-buf work lights direction 2)) - (s.vf vf4 at (generic-work-offset fx-buf work lights color 0)) - (s.vf vf5 at (generic-work-offset fx-buf work lights color 1)) - (s.vf vf6 at (generic-work-offset fx-buf work lights color 2)) - (s.vf vf7 at (generic-work-offset fx-buf work lights ambient)) - (l.wu a0 v1 8) - (sll a0 a0 4) - (add v1 v1 a0) - (m a0 v1)) - ;; One fragment: expand it into the indexed output half. - (asm-block convert-fragment - (label gme-convert-fragment) - (add.i a0 s7 #t) - (b.eq s7 a0 gme-reject-test :delay (m a0 s7)) - (add.i a0 r0 #x1b60) - (lui a1 #x7000) - (add a0 a0 a1) - (add.i v1 v1 16) - (s.w v1 a0 308) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow mercneric-convert)) - (jalr ra t9 :delay (sll v0 ra 0))) - ;; A cheap inline reject on vertex zero alone. Only if that one point is behind the camera or - ;; outside a plane is the full eight-at-a-time test worth calling. - (asm-block reject-test - (label gme-reject-test) - (lui v1 #x7000) - (add.i a0 r0 1) - (l.bu a1 v1 #x1a68) - (add.i a2 s1 448) - (l.bu a3 v1 (generic-work-offset in-buf merc ctrl header display-triangles)) - (b.z a1 gme-reject-done :delay (l.vf vf1 a2)) - (b.z a3 gme-reject-done :delay (vmaxw.xyzw vf9 vf0 vf0)) - (s.b a0 v1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) - (l.vf vf10 v1 (generic-work-offset in-buf merc shadow hsr-xmult)) - (l.vf vf11 v1 (generic-work-offset in-buf merc shadow hsr-ymult)) - (vmulaz.xyzw acc vf9 vf1) - (l.w a0 a2 8) - (vmaddax.xyzw acc vf10 vf1) - (b.lt a0 r0 gme-call-reject :delay (l.w v1 v1 #x1cb4)) - (vmaddy.xyzw vf1 vf11 vf1) - (m a0 vf1) - (pcgtw a0 r0 a0) - (ppach a0 r0 a0) - (b.z a0 gme-reject-done :delay (at-label gme-call-reject (nop!))) - (jalr ra v1 :delay (nop!))) - (asm-block reject-done - (label gme-reject-done) - (lui v1 #x7000) - (l.wu v1 v1 #x1a64) - (b.z v1 gme-ripple-done :delay (m v1 s7)) - (lui v1 #x7000) - (l.wu s0 v1 (generic-work-offset in-buf merc ctrl header query)) - (l.hu v1 s1 20) - (s.q v1 sp 48) - (l.w v1 s0 24) - (s.q v1 sp 64) - (l.w v1 s0 20) - (s.q v1 sp 80) - (b.nz s5 gme-ripple-first-vertex :delay (m v1 s7)) - (add.i v1 r0 0) - (s.q v1 sp 80) - (l.w v1 s0 12) - (s.q v1 sp 64) - (l.q v1 sp 64)) - ;; The ripple query samples every Nth converted vertex back into world space, which is how - ;; water knows where a character is touching it. - (asm-block ripple-first-vertex - (label gme-ripple-first-vertex) - (b gme-ripple-writeback :delay (nop!))) - (asm-block ripple-next-vertex - (label gme-ripple-next-vertex) - (l.q v1 sp 80) - (l.w a0 s0) - (slt v1 v1 a0) - (b.z v1 gme-ripple-sample-done :delay (m v1 s7)) - (l.q v1 sp 80) - (sll v1 v1 4) - (add.i v1 v1 28) - (add a1 v1 s0) - (l.q v1 sp 64) - (sll v1 v1 5) - (add v1 v1 s1) - (l.s f0 v1 448) - (s.s f0 a1) - (l.q v1 sp 64) - (sll v1 v1 5) - (add v1 v1 s1) - (l.s f0 v1 452) - (s.s f0 a1 4) - (l.q v1 sp 64) - (sll v1 v1 5) - (add v1 v1 s1) - (l.s f0 v1 456) - (s.s f0 a1 8) - (l.s f0 fp generic-merc-one) - (s.s f0 a1 12) - (m! t9 vector-matrix*!) - (m a0 a1) - (add.i v1 r0 #x1be0) - (lui a2 #x7000) - (add a2 v1 a2) - (jalr ra t9 :delay (sll v0 ra 0)) - (l.q v1 sp 80) - (add.i v1 v1 1) - (s.q v1 sp 80) - (l.q v1 sp 80)) - (asm-block ripple-sample-done - (label gme-ripple-sample-done) - (l.q v1 sp 64) - (l.w a0 s0 16) - (add v1 v1 a0) - (s.q v1 sp 64)) - (asm-block ripple-writeback - (label gme-ripple-writeback) - (l.q v1 sp 64) - (l.q a0 sp 48) - (slt v1 v1 a0) - (b.nz v1 gme-ripple-next-vertex :delay (nop!)) - (m v1 s7) - (l.q v1 sp 64) - (l.q a0 sp 48) - (sub v1 v1 a0) - (s.w v1 s0 24) - (l.q v1 sp 80) - (s.w v1 s0 20)) - (asm-block ripple-done - (label gme-ripple-done) - (add.i v1 s7 #t) - (b.eq s7 v1 gme-death-done :delay (m v1 s7)) - (lui v1 #x7000) - (l.hu v1 v1 #x1a6c) - (b.z v1 gme-death-done :delay (m a0 s7)) - (lui v1 #x7000) - (l.hu s0 v1 (generic-work-offset in-buf merc ctrl header death-start-vertex)) - (lui v1 #x7000) - (l.hu v1 v1 #x1a6c) - (s.q v1 sp 96) - (l.hu v1 s1 20) - (s.q v1 sp 112) - (add.i v1 sp 16) - (s.q v1 sp 128) - (l.q v1 sp 128) - (s.q r0 v1) - (add.i v1 sp 32) - (s.q v1 sp 144) - (l.q v1 sp 144) - (s.q r0 v1) - (b gme-death-spawned :delay (nop!))) - ;; The death effect spawns a particle from every Nth vertex, with its normal, both - ;; transformed back into world space. - (asm-block death-next-vertex - (label gme-death-next-vertex) - (sll v1 s0 5) - (add v1 v1 s1) - (l.s f0 v1 448) - (l.q v1 sp 128) - (s.s f0 v1) - (sll v1 s0 5) - (add v1 v1 s1) - (l.s f0 v1 452) - (l.q v1 sp 128) - (s.s f0 v1 4) - (sll v1 s0 5) - (add v1 v1 s1) - (l.s f0 v1 456) - (l.q v1 sp 128) - (s.s f0 v1 8) - (l.s f0 fp generic-merc-one) - (l.q v1 sp 128) - (s.s f0 v1 12) - (sll v1 s0 5) - (add v1 v1 s1) - (l.s f0 v1 464) - (l.q v1 sp 144) - (s.s f0 v1) - (sll v1 s0 5) - (add v1 v1 s1) - (l.s f0 v1 468) - (l.q v1 sp 144) - (s.s f0 v1 4) - (sll v1 s0 5) - (add v1 v1 s1) - (l.s f0 v1 472) - (l.q v1 sp 144) - (s.s f0 v1 8) - (m f0 r0) - (l.q v1 sp 144) - (s.s f0 v1 12) - (m! t9 vector-matrix*!) - (l.q a0 sp 128) - (l.q a1 sp 128) - (add.i v1 r0 #x1be0) - (lui a2 #x7000) - (add a2 v1 a2) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 vector-matrix*!) - (l.q a0 sp 144) - (l.q a1 sp 144) - (add.i v1 r0 #x1be0) - (lui a2 #x7000) - (add a2 v1 a2) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 merc-death-spawn) - (lui v1 #x7000) - (l.wu a0 v1 (generic-work-offset in-buf merc ctrl header death-effect)) - (l.q a1 sp 128) - (l.q a2 sp 144) - (jalr ra t9 :delay (sll v0 ra 0)) - (l.q v1 sp 96) - (add s0 s0 v1)) - (asm-block death-spawned - (label gme-death-spawned) - (l.q v1 sp 112) - (sltu v1 s0 v1) - (b.nz v1 gme-death-next-vertex :delay (nop!)) - (m v1 s7) - (l.q v1 sp 112) - (sub a0 s0 v1) - (lui v1 #x7000) - (s.h a0 v1 (generic-work-offset in-buf merc ctrl header death-start-vertex))) - (asm-block death-done - (label gme-death-done) - (add.i v1 s7 #t) - (b.eq s7 v1 gme-next-fragment :delay (m v1 s7)) - (lui v1 #x7000) - (l.w v1 v1 #x1c80) - (lui a0 #x7000) - (l.wu a0 a0 76) - (sub v1 v1 a0) - (slt v1 r0 v1) - (b.nz v1 gme-fragment-visible :delay (m v1 s7)) - (lui v1 #x7000) - (s.b r0 v1 (generic-work-offset in-buf merc ctrl header display-this-fragment)) - (lui v1 #x7000) - (s.b r0 v1 (generic-work-offset in-buf merc ctrl header display-triangles)) - (m v1 r0)) - ;; Two ways to drop the rest of an object: the DMA buffer is nearly full, or the fragment was - ;; already marked invisible. Clearing display-triangles as well abandons every later - ;; fragment of the control too. - (asm-block fragment-visible - (label gme-fragment-visible) - (lui v1 #x7000) - (l.bu v1 v1 #x1a75) - (b.z v1 gme-next-fragment :delay (m v1 s7)) - (add.i v1 r0 1) - (lui a0 #x7000) - (l.bu a0 a0 #x1aab) - (sub v1 v1 a0) - (add.i a0 s7 8) - (movn a0 s7 v1) - (b.eql s7 a0 gme-emit-envmap-translucent :delay (m v1 a0)) - (add.i v1 s7 8) - (lui a0 #x7000) - (l.bu a0 a0 #x1a74) - (movz v1 s7 a0)) - ;; Three emit variants selected by envmap-usage and use-translucent. The first two do the - ;; same work with different register assignment; only the third, without an environment - ;; map, differs - one packet header instead of two and no reflected-coordinate pass. - (asm-block emit-envmap-translucent - (label gme-emit-envmap-translucent) - (b.eq s7 v1 gme-emit-envmap :delay (nop!)) - (add.i v1 s7 #t) - (b.eq s7 v1 gme-emit-done-a :delay (m v1 s7)) - (lui at #x7000) - (add.i v1 at (generic-work-offset fx-buf work consts envmap shader)) - (lui a0 #x7000) - (l.bu a0 a0 #x1a6a) - (b.z a0 gme-envmap-translucent-shader-selected :delay (m a0 s7)) - (add.i v1 r0 #x1ab0) - (lui a0 #x7000) - (add v1 v1 a0) - (m v1 v1) - (m a0 v1)) - (asm-block envmap-translucent-shader-selected - (label gme-envmap-translucent-shader-selected) - (s.w r0 at (generic-work-offset saves ptr-shaders)) - (s.w v1 at (generic-work-offset saves ptr-env-shader)) - (s.w s1 at (generic-work-offset saves gsf-buf)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-prepare-dma-double)) - (jalr ra t9 :delay (sll v0 ra 0)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-light-proc)) - (jalr ra t9 :delay (sll v0 ra 0)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-envmap-proc)) - (jalr ra t9 :delay (sll v0 ra 0)) - (l.w v1 at (generic-work-offset saves cur-outbuf)) - (l.w a0 at (generic-work-offset saves qwc)) - (m a3 v1) - (nop!) - (lui at #x7000) - (lui a2 #x1000) - (l.wu a1 at (generic-work-offset saves basep)) - (ori a2 a2 #xd000) - (l.w t1 a2) - (nop!) - (add.i t0 at (generic-work-offset saves from-spr-waits)) - (and.i a3 a3 #x3fff) - (and.i t1 t1 DMA-CHCR-STR) - (nop!) - (b.z t1 gme-send-outbuf-a :delay (nop!)) - (m t1 a2) - (nop!)) - ;; Hand the finished packet to fromSPR and continue in the other output buffer. The wait is - ;; here, immediately before reuse, and nowhere else. - (asm-block wait-spr-from-a - (label gme-wait-spr-from-a) - (l.w t2 t0) - (nop!) - (l.w t3 t1) - (nop!) - (and.i t3 t3 DMA-CHCR-STR) - (add.i t2 t2 1) - (b.nz t3 gme-wait-spr-from-a :delay (s.w t2 t0)) - (m t0 r0)) - (asm-block send-outbuf-a - (label gme-send-outbuf-a) - (sll t0 a0 4) - (s.w a3 a2 128) - (nop!) - (s.w a1 a2 16) - (add.i a3 r0 DMA-CHCR-STR) - (s.w a0 a2 32) - (add a0 a1 t0) - (s.w a3 a2) - (nop!) - (s.w a0 at (generic-work-offset saves basep)) - (m a0 r0) - (xor.i v1 v1 GENERIC-OUTBUF-FLIP) - (s.w v1 at (generic-work-offset saves cur-outbuf))) - (asm-block emit-done-a - (label gme-emit-done-a) - (b gme-next-fragment :delay (nop!))) - (asm-block emit-envmap - (label gme-emit-envmap) - (lui v1 #x7000) - (l.bu v1 v1 #x1aab) - (b.z v1 gme-emit-plain :delay (nop!)) - (add.i v1 s7 #t) - (b.eq s7 v1 gme-emit-done-b :delay (m v1 s7)) - (lui at #x7000) - (m v1 s1) - (add.i a0 at (generic-work-offset fx-buf work consts envmap shader)) - (lui a1 #x7000) - (l.bu a1 a1 #x1a6a) - (b.z a1 gme-envmap-shader-selected :delay (m a1 s7)) - (add.i a0 r0 #x1ab0) - (lui a1 #x7000) - (add a0 a0 a1) - (m a0 a0) - (m a1 a0)) - (asm-block envmap-shader-selected - (label gme-envmap-shader-selected) - (s.w r0 at (generic-work-offset saves ptr-shaders)) - (s.w a0 at (generic-work-offset saves ptr-env-shader)) - (s.w v1 at (generic-work-offset saves gsf-buf)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-prepare-dma-double)) - (jalr ra t9 :delay (sll v0 ra 0)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-light-proc)) - (jalr ra t9 :delay (sll v0 ra 0)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-envmap-proc)) - (jalr ra t9 :delay (sll v0 ra 0)) - (l.w v1 at (generic-work-offset saves cur-outbuf)) - (l.w a0 at (generic-work-offset saves qwc)) - (m a3 v1) - (nop!) - (lui at #x7000) - (lui a2 #x1000) - (l.wu a1 at (generic-work-offset saves basep)) - (ori a2 a2 #xd000) - (l.w t1 a2) - (nop!) - (add.i t0 at (generic-work-offset saves from-spr-waits)) - (and.i a3 a3 #x3fff) - (and.i t1 t1 DMA-CHCR-STR) - (nop!) - (b.z t1 gme-send-outbuf-b :delay (nop!)) - (m t1 a2) - (nop!)) - (asm-block wait-spr-from-b - (label gme-wait-spr-from-b) - (l.w t2 t0) - (nop!) - (l.w t3 t1) - (nop!) - (and.i t3 t3 DMA-CHCR-STR) - (add.i t2 t2 1) - (b.nz t3 gme-wait-spr-from-b :delay (s.w t2 t0)) - (m t0 r0)) - (asm-block send-outbuf-b - (label gme-send-outbuf-b) - (sll t0 a0 4) - (s.w a3 a2 128) - (nop!) - (s.w a1 a2 16) - (add.i a3 r0 DMA-CHCR-STR) - (s.w a0 a2 32) - (add a0 a1 t0) - (s.w a3 a2) - (nop!) - (s.w a0 at (generic-work-offset saves basep)) - (m a0 r0) - (xor.i v1 v1 GENERIC-OUTBUF-FLIP) - (s.w v1 at (generic-work-offset saves cur-outbuf))) - (asm-block emit-done-b - (label gme-emit-done-b) - (b gme-next-fragment :delay (nop!))) - (asm-block emit-plain - (label gme-emit-plain) - (add.i v1 s7 #t) - (b.eq s7 v1 gme-next-fragment :delay (m v1 s7)) - (lui at #x7000) - (m v1 s1) - (s.w r0 at (generic-work-offset saves ptr-shaders)) - (s.w v1 at (generic-work-offset saves gsf-buf)) - (s.w r0 at (generic-work-offset saves is-envmap)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-prepare-dma-single)) - (jalr ra t9 :delay (sll v0 ra 0)) - (lui v1 #x7000) - (l.wu t9 v1 (generic-work-offset in-buf merc shadow generic-light-proc)) - (jalr ra t9 :delay (sll v0 ra 0)) - (l.w v1 at (generic-work-offset saves cur-outbuf)) - (l.w a0 at (generic-work-offset saves qwc)) - (m a3 v1) - (nop!) - (lui at #x7000) - (lui a2 #x1000) - (l.wu a1 at (generic-work-offset saves basep)) - (ori a2 a2 #xd000) - (l.w t1 a2) - (nop!) - (add.i t0 at (generic-work-offset saves from-spr-waits)) - (and.i a3 a3 #x3fff) - (and.i t1 t1 DMA-CHCR-STR) - (nop!) - (b.z t1 gme-send-outbuf-c :delay (nop!)) - (m t1 a2) - (nop!)) - (asm-block wait-spr-from-c - (label gme-wait-spr-from-c) - (l.w t2 t0) - (nop!) - (l.w t3 t1) - (nop!) - (and.i t3 t3 DMA-CHCR-STR) - (add.i t2 t2 1) - (b.nz t3 gme-wait-spr-from-c :delay (s.w t2 t0)) - (m t0 r0)) - (asm-block send-outbuf-c - (label gme-send-outbuf-c) - (sll t0 a0 4) - (s.w a3 a2 128) - (nop!) - (s.w a1 a2 16) - (add.i a3 r0 DMA-CHCR-STR) - (s.w a0 a2 32) - (add a0 a1 t0) - (s.w a3 a2) - (nop!) - (s.w a0 at (generic-work-offset saves basep)) - (m a0 r0) - (xor.i v1 v1 GENERIC-OUTBUF-FLIP) - (s.w v1 at (generic-work-offset saves cur-outbuf))) - ;; Next fragment, or next control block, or done. - (asm-block next-fragment - (label gme-next-fragment) - (add.i v1 r0 1) - (sub gp v1 gp) - (add.i s5 s5 1) - (lui v1 #x7000) - (l.hu v1 v1 #x1aa2) - (b.ne s5 v1 gme-frag-index-kept :delay (m v1 s7)) - (add.i s5 r0 0) - (m v1 s5)) - (asm-block frag-index-kept - (label gme-frag-index-kept) - (m v1 s2)) - (asm-block chain-test - (label gme-chain-test) - (b.nz s2 gme-select-input-base :delay (nop!)) - (m v1 s7) - (m! v1 *merc-globals*) - (s.w r0 v1) - (m v0 r0) - (l.d ra sp) - (l.d fp sp 8) - (l.q gp sp 256) - (l.q s5 sp 240) - (l.q s4 sp 224) - (l.q s3 sp 208) - (l.q s2 sp 192) - (l.q s1 sp 176) - (l.q s0 sp 160) - (jr ra :delay (add.i sp sp 272)) - (nop!) - (nop!) - (nop!)) - ) - - ) (defun generic-merc-add-to-cue ((sink generic-dma-foreground-sink)) "Append sink to the current frame's MERC cue and advance the insertion cursor." (set! (-> *merc-globals* sink) sink) @@ -3278,109 +64,76 @@ "Initialize Generic MERC conversion, convert every cued sink into the frame's global DMA buffer, insert each completed chain in its foreground bucket, and update wait and DMA-memory statistics." (local-vars (a0-26 int) (a0-28 int)) - (when (nonzero? (-> *merc-global-array* count)) - (when *generic-effect-stats* - (set! (-> *generic-effect-debug-stats-data* light-vertices) 0) - (set! (-> *generic-effect-debug-stats-data* envmap-vertices) 0) - (set! (-> *generic-effect-debug-stats-data* prepare-single-calls) 0) - (set! (-> *generic-effect-debug-stats-data* prepare-double-calls) 0) - (stopwatch-init (-> *generic-effect-debug-stats-data* timer)) - (stopwatch-start (-> *generic-effect-debug-stats-data* timer))) - (let ((global-buffer-start (-> *display* frames (-> *display* on-screen) frame global-buf base))) - ;; set up performance stats - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) - (reset! (-> *perf-stats* data 1)) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves to-vu0-waits) (the-as uint 0)) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves to-spr-waits) (the-as uint 0)) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves from-spr-waits) (the-as uint 0)) - (flush-cache 0) - ;; first, initialize the generic renderer. - (generic-initialize-without-sink (-> *math-camera* perspective) *default-lights*) - ;; next, initialize the merc-specific stuff. - ;; this give us function pointers to - ;; - mercneric-convert - ;; - generic-prepare-dma-single - ;; - generic-prepare-dma-double - ;; - generic-light-proc - ;; - generic-envmap-proc - ;; - high-speed-reject - ;; and also loads the mercneric-vu0-block block with an offset of 280. - (generic-merc-init-asm) - ;; Publish the GOAL implementations. MIPS2C calls these addresses through its ordinary - ;; indirect-call bridge. - (let ((calls (-> (scratchpad-object terrain-context) work foreground generic-work in-buf merc shadow))) - (set! (-> calls generic-prepare-dma-single) generic-prepare-dma-single-new) - (set! (-> calls generic-prepare-dma-double) generic-prepare-dma-double-new) - (set! (-> calls generic-light-proc) generic-light-proc-new) - (set! (-> calls generic-envmap-proc) generic-envmap-proc-new)) - ;; set a limit, so we don't write off the end of the dma buffer. - (set! (-> (scratchpad-object terrain-context) work foreground generic-work in-buf merc shadow write-limit) - (&+ (-> dma-buf end) -65536)) - (dotimes (i (the-as int (-> *merc-global-array* count))) - (set! *merc-globals* (-> *merc-global-array* globals i)) - (let ((sink (-> *merc-globals* sink))) - (when (nonzero? (-> *merc-globals* first)) - ;; todo: this part might be important... - ; (let ((v1-36 (the-as object #x1000d000)) - ; (a0-19 (the-as object #x7000006c)) - ; ) - ; (b! (zero? (logand (-> (the-as terrain-context v1-36) bsp lev-index) 256)) cfg-9 :delay (nop!)) - ; (let ((a1-6 (-> (the-as generic-envmap-saves a0-19) index-mask x))) - ; (nop!) - ; (let ((a2-1 (-> (the-as (pointer int32) v1-36) 0))) - ; (nop!) - ; (let ((a2-2 (logand a2-1 256)) - ; (a1-7 (+ a1-6 1)) - ; ) - ; (b! (nonzero? a2-2) cfg-8 :delay (s.w! (the-as int a0-19) a1-7)) - ; ) - ; ) - ; ) - ; ) - ; (label cfg-9) - ; 0 - (with-dma-buffer-add-bucket ((global-buf (-> *display* frames (-> *display* on-screen) frame global-buf)) (-> sink bucket)) :bucket-group (-> *display* frames (-> *display* on-screen) frame bucket-group) (generic-work-init sink) (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves basep) - (the-as uint (-> global-buf base))) (generic-merc-execute-asm) (set! (-> global-buf base) - (the-as pointer - (-> (scratchpad-object terrain-context) work foreground generic-work saves basep)))) - ; (let ((v1-44 (-> dma-buf base))) - ; (.sync.l) - ; (.cache dxwbin v1-44 0) - ; (.sync.l) - ; (.cache dxwbin v1-44 1) - ; ) - ; (.sync.l) - 0))) - (read! (-> *perf-stats* data 1)) - 0 - (update-wait-stats (-> *perf-stats* data 1) - (-> (scratchpad-object terrain-context) work foreground generic-work saves to-vu0-waits) - (-> (scratchpad-object terrain-context) work foreground generic-work saves to-spr-waits) - (-> (scratchpad-object terrain-context) work foreground generic-work saves from-spr-waits)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #xff :g #xff :b #xff :a #x80))) - (let ((dma-usage *dma-mem-usage*)) - (when (nonzero? dma-usage) - (set! (-> dma-usage length) (max 87 (-> dma-usage length))) - (set! (-> dma-usage data 86 name) "pris-generic") - (+! (-> dma-usage data 86 count) 1) - (+! (-> dma-usage data 86 used) - (&- (-> *display* frames (-> *display* on-screen) frame global-buf base) - (the-as uint global-buffer-start))) - (set! (-> dma-usage data 86 total) (-> dma-usage data 86 used))))) - (when *generic-effect-stats* - (stopwatch-stop (-> *generic-effect-debug-stats-data* timer)) - (format *stdcon* - "generic: ~D us, light ~D verts, envmap ~D verts, prepare single ~D, double ~D~%" - (the int (* 1000000.0 - (stopwatch-elapsed-seconds (-> *generic-effect-debug-stats-data* timer)))) - (-> *generic-effect-debug-stats-data* light-vertices) - (-> *generic-effect-debug-stats-data* envmap-vertices) - (-> *generic-effect-debug-stats-data* prepare-single-calls) - (-> *generic-effect-debug-stats-data* prepare-double-calls)))) - (none)) + (slet (spad terrain-context) + (when (nonzero? (-> *merc-global-array* count)) + ;; og:preserve-this + (when *generic-effect-stats* + (set! (-> *generic-effect-debug-stats-data* light-vertices) 0) + (set! (-> *generic-effect-debug-stats-data* envmap-vertices) 0) + (set! (-> *generic-effect-debug-stats-data* prepare-single-calls) 0) + (set! (-> *generic-effect-debug-stats-data* prepare-double-calls) 0) + (stopwatch-init (-> *generic-effect-debug-stats-data* timer)) + (stopwatch-start (-> *generic-effect-debug-stats-data* timer))) + + (let ((global-buffer-start (-> (current-frame) global-buf base))) + (add-profile-frame! #x40 0 #x40 #x80 'draw) + (reset! (-> *perf-stats* data 1)) + (set! (-> spad work foreground generic-work saves to-vu0-waits) (the-as uint 0)) + (set! (-> spad work foreground generic-work saves to-spr-waits) (the-as uint 0)) + (set! (-> spad work foreground generic-work saves from-spr-waits) (the-as uint 0)) + (flush-cache 0) + (generic-initialize-without-sink (-> *math-camera* perspective) *default-lights*) + (generic-merc-init-asm) + (set! (-> spad work foreground generic-work in-buf merc shadow write-limit) (&+ (-> dma-buf end) -65536)) + + ;; og:preserve-this + ;; install new PC version of effect processors + (let ((calls (-> (scratchpad-object terrain-context) work foreground generic-work in-buf merc shadow))) + (set! (-> calls generic-prepare-dma-single) generic-prepare-dma-single-new) + (set! (-> calls generic-prepare-dma-double) generic-prepare-dma-double-new) + (set! (-> calls generic-light-proc) generic-light-proc-new) + (set! (-> calls generic-envmap-proc) generic-envmap-proc-new)) + + (dotimes (i (the-as int (-> *merc-global-array* count))) + (set! *merc-globals* (-> *merc-global-array* globals i)) + (let ((sink (-> *merc-globals* sink))) + (when (nonzero? (-> *merc-globals* first)) + (with-dma-buffer-add-bucket ((global-buf (-> (current-frame) global-buf)) (-> sink bucket)) + (generic-work-init sink) + (set! (-> spad work foreground generic-work saves basep) (the-as uint (-> global-buf base))) + (generic-merc-execute-asm) + (set! (-> global-buf base) (the-as pointer (-> spad work foreground generic-work saves basep))) + ;; og:preserve-this DMA busy loop removed + ;; (let ((v1-36 (the-as object #x1000d000)) + ;; (a0-19 (the-as object (&+ spad 108)))) + ;; (b! (not (logtest? (-> (the-as (pointer int32) v1-36) 0) 256)) cfg-9 :delay (nop!)) + ;; (let ((a1-6 (-> (the-as (pointer int32) a0-19) 0))) + ;; (nop!) + ;; (let ((a2-1 (-> (the-as (pointer int32) v1-36) 0))) + ;; (nop!) + ;; (let ((a2-2 (logand a2-1 256)) + ;; (a1-7 (+ a1-6 1))) + ;; (b! (nonzero? a2-2) cfg-8 :delay (set! (-> (the-as (pointer int32) a0-19) 0) a1-7)))))) + ;; (label cfg-9) + 0) + (invalidate-cache-line (-> dma-buf base))))) + (read! (-> *perf-stats* data 1)) + (update-wait-stats (-> *perf-stats* data 1) + (-> spad work foreground generic-work saves to-vu0-waits) + (-> spad work foreground generic-work saves to-spr-waits) + (-> spad work foreground generic-work saves from-spr-waits)) + (add-profile-frame! #xff #xff #xff #x80 'draw) + (dma-mem-usage-add! pris-generic 1 (&- (-> (current-frame) global-buf base) (the-as uint global-buffer-start))) + ;; og:preserve-this + (when *generic-effect-stats* + (stopwatch-stop (-> *generic-effect-debug-stats-data* timer)) + (format *stdcon* + "generic: ~D us, light ~D verts, envmap ~D verts, prepare single ~D, double ~D~%" + (the int (* 1000000.0 + (stopwatch-elapsed-seconds (-> *generic-effect-debug-stats-data* timer)))) + (-> *generic-effect-debug-stats-data* light-vertices) + (-> *generic-effect-debug-stats-data* envmap-vertices) + (-> *generic-effect-debug-stats-data* prepare-single-calls) + (-> *generic-effect-debug-stats-data* prepare-double-calls))))) + (none))) + diff --git a/goal_src/jak1/engine/gfx/generic/generic-tie-ee-asm.gc b/goal_src/jak1/engine/gfx/generic/generic-tie-ee-asm.gc new file mode 100644 index 0000000000..4506db7ad8 --- /dev/null +++ b/goal_src/jak1/engine/gfx/generic/generic-tie-ee-asm.gc @@ -0,0 +1,3272 @@ +;; Generic TIE -- the conversion pass that gets TIE geometry into GENERIC. +;; +;; TIE's own VU1 program is fast because it is rigid: a fixed strip template, a palette lookup, and no +;; per-vertex lighting. Anything that needs more than that -- in Jak 1 that means environment mapping -- +;; cannot use it. So a prototype can be flagged for Generic instead, and then TIE's prototype pass +;; stops building VU1 packets for it and emits a compact chain instead: which fragments were selected, +;; which instances chose each one, and the palette each needs. +;; +;; This file walks that chain. It runs entirely out of scratchpad, alternating two 3248-byte input +;; buffers so a main-memory chain transfer can be fetching the next block while the EE expands the +;; current one, and it hands finished packets to the GENERIC double-buffer builder. Expansion means +;; turning TIE's packed 16-byte base points and 24-byte interpolated points back into full quadwords, +;; gathering the normal, kick, color-index, and matrix tables that go with them, and then dispatching +;; whichever effect processor the fragment header asked for. Nineteen calls into the Generic VU0 +;; library do the vector work; see generic-tie-h.gc for the stream formats and generic-effect.gc for +;; the processors. +;; +;; Two costs are worth knowing about. Every vertex is touched by the EE here, where ordinary TIE never +;; touches one at all, and the compact stream has to be decompressed before any of it can be used -- +;; which is why Generic prototypes are rare and why the statistics block counts instances and +;; quadwords separately. + +;; What one entry of the chain brings with it. The prototype pass writes this into shadow.tie-type +;; before the transfer starts, so the converter knows how to parse a block it has not looked at yet. +(defconstant GENERIC-TIE-ENTRY-MODEL-AND-PALETTE 0) +(defconstant GENERIC-TIE-ENTRY-MODEL-ONLY 1) +(defconstant GENERIC-TIE-ENTRY-INSTANCE-ONLY 2) + +;; Alternating the two input buffers is one xor because input-a starts at scratchpad 768 and the buffers +;; are one generic-tie-input apart: 768 and 3248 have no bits in common, so xor and add agree. The same +;; is true of the instance buffer, whose flip is cached in shadow.inst-xor because it depends on which +;; input buffer is current. +(defconstant GENERIC-TIE-INPUT-FLIP (type-size generic-tie-input)) + +(#unless PC_PORT + (defun generic-tie-convert () + "Walk the compact TIE chain from the start of the input buffer to the end of the chain, expanding + each fragment into the GSF buffer, building a GENERIC packet from it, and returning that packet + to the frame's DMA buffer. Stops early if the DMA buffer is nearly full." + (declare (asm-func none) (allow-saved-regs)) + ;; The whole pass, and three things overlap inside it: the toSPR channel fetches the next chain + ;; entry while the EE expands this one, the fromSPR channel returns the previous packet while the + ;; EE builds this one, and VU0 transforms the previous group of four vertices while the EE stages + ;; the next. Each of those has its own pair of buffers and the loop only ever waits at the point + ;; where it is about to reuse one. + ;; + ;; One pass looks like: wait for the input block, parse its DMA tags into generic-tie-control, + ;; check there is still room in the output, start the next block's transfer, expand this + ;; fragment, run the four effect processors over it, and hand the finished packet to fromSPR. + ;; + ;; The expansion in the middle is generic-tie-convert-proc inlined verbatim, register for + ;; register - it exists as its own function too, and the two are worth reading together. What is + ;; only here is the chain walk at either end. + ;; The saves go into the scratchpad work area rather than onto the stack, so the prologue and + ;; epilogue cost scratchpad accesses. That does mean the page register has to already hold the + ;; scratchpad base on entry, before the loop head loads it: it does, because the caller's last + ;; act is a store into this same page and the compiler leaves the page selector behind in the + ;; assembler temporary. The stack is still reserved, and still unused. + (asm-block save-registers + (add.i sp sp -128) + (s.d ra at (generic-work-offset storage data 0)) + (s.q s0 at (generic-work-offset storage data 1)) + (s.q s1 at (generic-work-offset storage data 2)) + (s.q s2 at (generic-work-offset storage data 3)) + (s.q s3 at (generic-work-offset storage data 4)) + (s.q s4 at (generic-work-offset storage data 5)) + (s.q s5 at (generic-work-offset storage data 6)) + (s.q gp at (generic-work-offset storage data 7))) + ;; One chain entry per pass around this loop, until the parser reports the end of it. + (rlet ((to-spr :reg v1 :type dma-bank-spr)) + (asm-block address-the-channel + (label tie-generic-next-chain-entry) + (lui to-spr #x1000) + (lui at #x7000) + (ori to-spr to-spr #xd400) + (nop!))) + ;; The transfer this pass is about to read was started at the end of the previous pass, which + ;; is the whole point of the two input buffers: expanding one fragment pays for fetching the + ;; next. By the time we get here it has usually already landed. + ;; + ;; The counter this poll bumps is never stored anywhere - gp is reused as a temporary a few + ;; hundred instructions later - so toSPR stalls do not reach the timing display, unlike the + ;; fromSPR stalls counted below. + (rlet ((to-spr :reg v1 :type dma-bank-spr) + (status :reg a0) + (stall-count :reg gp)) + (asm-block wait-for-input-block + (label tie-generic-wait-for-input-block) + (l.w status (-> to-spr chcr)) + (nop! :count 3) + (and.i status status DMA-CHCR-STR) + (nop!) + (b.z status tie-generic-input-block-ready :delay (nop!)) + (nop! :count 3) + (add.i stall-count stall-count 1) + (nop! :count 6) + (b tie-generic-wait-for-input-block :delay (nop!)))) + ;; A block that brought its own palette starts with it, already interpolated for the current + ;; time of day by the prototype pass. Copy it into the persistent palette buffer, because the + ;; input buffer it arrived in will be overwritten by the next transfer while its own vertices + ;; are still being expanded. + ;; + ;; The environment map's adgif shader comes along the same way, except that it is not in the + ;; block at all: the first DMA tag's unused VIF word holds a main-memory pointer to it, and + ;; five quadword copies bring it into the shared constant block. + (rlet ((entry-kind :reg v1) + (instance-block :reg a2) + (palette-dest :reg a2) + (kind-test :reg a0) + (control :reg a0 :type generic-tie-control) + (input-block :reg a1) + (model-tag :reg a1) + (palette-src :reg t0) + (palette-qwc :reg a3) + (shader-src :reg t1) + (palette-cursor :reg t1)) + ;; t2 through t5 shuttle quadwords for the two copies. + (asm-block adopt-new-palette + (label tie-generic-input-block-ready) + (nop!) + (l.w entry-kind at (generic-work-offset in-buf tie shadow tie-type)) + (nop!) + (l.w instance-block at (generic-work-offset in-buf tie shadow ptr-inst)) + (sub kind-test entry-kind r0) + (l.w input-block at (generic-work-offset in-buf tie shadow ptr-buf)) + (b.nz kind-test tie-generic-chain-entry-kind :delay (add.i control at (generic-work-offset in-buf tie control))) + (add.i palette-src input-block 16) + (l.hu palette-qwc input-block) + (sll t2 palette-qwc 4) + (l.w shader-src input-block 8) ;; the tag's unused VIF word: where the shader lives + (add.i palette-dest at (generic-work-offset in-buf tie palette-buf)) + (add model-tag palette-src t2) + (nop!) + (l.q t2 shader-src) + (nop!) + (l.q t3 shader-src 16) + (nop!) + (l.q t4 shader-src 32) + (nop!) + (l.q t5 shader-src 48) + (nop!) + (l.q shader-src shader-src 64) + (nop!) + (s.q t2 at (generic-work-offset fx-buf work consts envmap shader)) + (nop!) + (s.q t3 at (+ (generic-work-offset fx-buf work consts envmap shader) 16)) + (nop!) + (s.q t4 at (+ (generic-work-offset fx-buf work consts envmap shader) 32)) + (nop!) + (s.q t5 at (+ (generic-work-offset fx-buf work consts envmap shader) 48)) + (nop!) + (s.q palette-cursor at (+ (generic-work-offset fx-buf work consts envmap shader) 64)) + (m palette-cursor palette-dest) + (nop!) + (add.i t2 palette-qwc -4) + (m palette-cursor palette-cursor) + (b.lt t2 r0 tie-generic-copy-palette-tail :delay (m palette-src palette-src)))) + ;; Four quadwords a pass while there are four left. + (rlet ((palette-src :reg t0) + (palette-cursor :reg t1) + (palette-qwc :reg a3)) + (asm-block copy-palette-quads + (label tie-generic-copy-palette-quads) + (nop!) + (l.q t5 palette-src) + (nop!) + (l.q t2 palette-src 16) + (add.i palette-qwc palette-qwc -4) + (l.q t3 palette-src 32) + (add.i palette-cursor palette-cursor 64) + (l.q t4 palette-src 48) + (add.i palette-src palette-src 64) + (s.q t5 palette-cursor -64) + (add.i t5 palette-qwc -4) + (s.q t2 palette-cursor -48) + (nop!) + (s.q t3 palette-cursor -32) + (b.ge t5 r0 tie-generic-copy-palette-quads :delay (s.q t4 palette-cursor -16)))) + ;; The last one to three, written out four times rather than looped: at this size the branch + ;; would cost as much as the copy. + (rlet ((palette-src :reg t0) + (palette-cursor :reg t1) + (palette-qwc :reg a3)) + (asm-block copy-palette-tail + (label tie-generic-copy-palette-tail) + (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) + (add.i palette-src palette-src 16) + (add.i palette-cursor palette-cursor 16) + (add.i palette-qwc palette-qwc -1) + (s.q t2 palette-cursor -16) + (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) + (add.i palette-src palette-src 16) + (add.i palette-cursor palette-cursor 16) + (add.i palette-qwc palette-qwc -1) + (s.q t2 palette-cursor -16) + (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) + (add.i palette-src palette-src 16) + (add.i palette-cursor palette-cursor 16) + (add.i palette-qwc palette-qwc -1) + (s.q t2 palette-cursor -16) + (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) + (add.i palette-src palette-src 16) + (add.i palette-src palette-cursor 16) + (add.i palette-qwc palette-qwc -1) + (s.q t2 palette-src -16))) + ;; Point the control block at the persistent copy and go parse the model. + (rlet ((palette-dest :reg a2) + (palette-qwc :reg a3)) + (asm-block palette-copied + (label tie-generic-palette-copied) + (m palette-qwc r0) + (b tie-generic-parse-model-block :delay (s.w palette-dest at (generic-work-offset in-buf tie control))))) + ;; A block with no palette of its own still has a model to parse; an instance-only entry has + ;; neither, and only its transform and palette indices need looking at. + (rlet ((entry-kind :reg v1) + (kind-test :reg a3) + (model-tag :reg a1) ;; the input buffer's base, stepped past the palette area + (instance-block :reg a2) + (instance-cursor :reg t2)) + (asm-block chain-entry-kind + (label tie-generic-chain-entry-kind) + (add.i kind-test entry-kind -1) + (nop!) + (b.nz kind-test tie-generic-parse-instance-block :delay (m instance-cursor instance-block)) + (nop!) + (add.i model-tag model-tag (offset-of generic-tie-input model-tag)) + (nop!))) + ;; The block is a DMA chain that was transferred with its tags, so it describes itself: each + ;; tag's quadword count says how far the next one is. Walk it and fill in the cursors - shaders, + ;; run control, packed points, the fragment header - and resolve the kick, normal and + ;; interpolation tables from the offsets in the header. The draw-point indices need no offset + ;; because they start where gsf-header ends. The shader count is not in a header field at all; + ;; it sits in an unused register slot of the first shader, and five quadwords apiece turns it + ;; into the address the run control starts at. + ;; + ;; Two more values come out of the unused VIF words of the tag heading the model: what the next + ;; chain entry will be, and whether it brings a model of its own. The prototype pass put them + ;; there so this parser can start the next transfer before it expands anything. + (rlet ((control :reg a0 :type generic-tie-control)) + (asm-block parse-model-block + (label tie-generic-parse-model-block) + (add.i a2 a1 16) + (l.hu a3 a1) + (sll t0 a3 4) + (l.wu t1 a2 28) + (sll t2 t1 2) + (l.wu a3 a1 12) + (add t1 t2 t1) + (l.wu a1 a1 8) + (add t0 a2 t0) + (s.w a3 (-> control next-model)) + (sll a3 t1 4) + (s.w a1 (-> control next-is-model)) + (add.i a1 t0 16) + (l.hu t0 t0) + (add a3 a2 a3) + (s.w a2 (-> control ptr-shaders)) + (sll a2 t0 4) + (s.w a3 (-> control ptr-runctrl)) + (add a2 a1 a2) + (s.w a1 (-> control ptr-verts)) + (add.i a1 a2 16) + (l.hu a2 a2) + (sll t2 a2 4) + (l.hu a2 a1 14) + (add.i a3 a1 32) + (l.hu t0 a1 10) + (add t0 t0 a1) + (l.hu t1 a1 12) + (add t1 t1 a1) + (l.hu t3 a1) + (add t2 a1 t2) + (s.w a1 (-> control ptr-generic)) + (add.i t2 t2 16) + (s.w a3 (-> control ptr-dps)) + (add a1 a2 a1) + (s.w t1 (-> control ptr-normals)) + (nop!) + (s.w a1 (-> control ptr-interp)) + (b tie-generic-parse-instance-block :delay (s.w t0 (-> control ptr-kicks))))) + ;; The instance part is three fields: the transform and morph state, the palette index per + ;; vertex, and - again out of a tag's unused VIF word - the address of the next instance of + ;; this same fragment, or a small marker when there is not one. + (rlet ((control :reg a0 :type generic-tie-control) + (instance-cursor :reg t2)) + (asm-block parse-instance-block + (label tie-generic-parse-instance-block) + (add.i a1 instance-cursor 16) + (l.wu a3 instance-cursor 12) + (add.i a2 a1 112) + (s.w a3 (-> control next-instance)) + (nop!) + (l.hu a3 a2 -16) + (nop!) + (s.w a1 (-> control ptr-mtxs)) + (nop!) + (s.w a2 (-> control ptr-cinds)) + (nop!) + (s.w v1 (-> control tie-type)) + (nop!))) + ;; Stop while there is still room. The limit is 64 KiB short of the end of the frame's DMA + ;; buffer, which is more than any one fragment can need, so the check only has to happen once + ;; per chain entry rather than per packet. + (asm-block check-output-room + (l.w v1 at (generic-work-offset in-buf tie shadow write-limit)) + (nop!) + (l.w a0 at (generic-work-offset saves basep)) + (sub v1 a0 v1) + (nop!) + (b.ge v1 r0 tie-generic-convert-done :delay (nop!))) + ;; generic-tie-upload-next, inlined. Work out what comes after this entry, flip the buffer it + ;; will land in, and start its transfer now so it overlaps the expansion below. + (asm-block queue-model-with-palette + (lui at #x7000) + (nop!) + (nop!) + (l.w v1 at (generic-work-offset in-buf tie control next-instance)) + (lui a1 1) + (l.w a0 at (generic-work-offset in-buf tie control next-model)) + (sub a1 v1 a1) + (l.w a2 at (generic-work-offset in-buf tie control next-is-model)) + (b.gt a1 r0 tie-generic-queue-next-instance :delay (l.w a1 at (generic-work-offset in-buf tie shadow ptr-inst))) + (b.nz a2 tie-generic-queue-model-without-palette :delay (l.w v1 at (generic-work-offset in-buf tie shadow ptr-buf))) + (xor.i a1 v1 GENERIC-TIE-INPUT-FLIP) + (m v1 a0) + (add.i a3 a1 (offset-of generic-tie-input matrix-tag)) + (s.w a1 at (generic-work-offset in-buf tie shadow ptr-buf)) + (xor.i a2 a3 (generic-work-offset in-buf tie inst-buf)) + (s.w a3 at (generic-work-offset in-buf tie shadow ptr-inst)) + (add.i a3 r0 0) + (add a1 a1 r0) + (and.i a2 a2 #xffff) + (s.w a3 at (generic-work-offset in-buf tie shadow tie-type)) + (b.nz a0 tie-generic-start-input-transfer :delay (s.w a2 at (generic-work-offset in-buf tie shadow inst-xor))) + (add.i v1 r0 1) + (nop!) + (b tie-generic-expand-fragment :delay (s.w v1 at (generic-work-offset in-buf tie shadow end-of-chain)))) + ;; Same, for an entry that reuses the palette already copied. + (asm-block queue-model-without-palette + (label tie-generic-queue-model-without-palette) + (xor.i a1 v1 GENERIC-TIE-INPUT-FLIP) + (m v1 a0) + (add.i a3 a1 (offset-of generic-tie-input matrix-tag)) + (s.w a1 at (generic-work-offset in-buf tie shadow ptr-buf)) + (xor.i a2 a3 (generic-work-offset in-buf tie inst-buf)) + (s.w a3 at (generic-work-offset in-buf tie shadow ptr-inst)) + (add.i a3 r0 1) + (add.i a1 a1 (offset-of generic-tie-input model-tag)) + (and.i a2 a2 #xffff) + (s.w a3 at (generic-work-offset in-buf tie shadow tie-type)) + (b.nz a0 tie-generic-start-input-transfer :delay (s.w a2 at (generic-work-offset in-buf tie shadow inst-xor))) + (add.i v1 r0 1) + (nop!) + (b tie-generic-expand-fragment :delay (s.w v1 at (generic-work-offset in-buf tie shadow end-of-chain)))) + ;; Same again for another instance of the fragment in hand, which alternates the instance + ;; buffer rather than the whole input buffer. + (asm-block queue-next-instance + (label tie-generic-queue-next-instance) + (m v1 v1) + (l.w a2 at (generic-work-offset in-buf tie shadow inst-xor)) + (add.i a0 r0 2) + (nop!) + (xor a1 a1 a2) + (s.w a0 at (generic-work-offset in-buf tie shadow tie-type)) + (b tie-generic-start-input-transfer :delay (s.w a1 at (generic-work-offset in-buf tie shadow ptr-inst)))) + ;; Hand it to the channel and get on with the current fragment. + (rlet ((to-spr :reg a0 :type dma-bank-spr)) + (asm-block start-input-transfer + (label tie-generic-start-input-transfer) + (lui at #x7000) + (ori a2 r0 #xffff) + (lui to-spr #x1000) + (nop!) + (ori to-spr to-spr #xd400) + (and a1 a1 a2) + (add.i a2 r0 DMA-CHCR-START-CHAIN-TTE) + (s.w a1 (-> to-spr sadr)) + (s.w v1 (-> to-spr tadr)) + (s.w r0 (-> to-spr qwc)) + (s.w a2 (-> to-spr chcr)))) + ;; The next transfer is running; expand the fragment that is already here. Everything from here + ;; to the effect processors is generic-tie-convert-proc, inlined. + (asm-block expand-fragment + (label tie-generic-expand-fragment) + (nop!) + (m v1 r0)) + ;; Everything about the fragment now in scratchpad is described by generic-tie-control, + ;; which the chain parser filled in. Read all of it at once - the loads are two cycles + ;; apart and there is nothing else to do with the gaps - and pick up the counts out of the + ;; fragment header. An instance-only chain entry needs none of the expansion that follows, + ;; because the model is still sitting in the work buffer from last time, so it jumps + ;; straight to the transform. + (rlet ((control :reg t2 :type generic-tie-control) + (out-buf :reg a0 :type gsf-buffer) + (header :reg t1 :type generic-tie-header) + (instance-mtx :reg a3 :type generic-tie-matrix) + (point-src :reg t3) + (index-src :reg t5) + (kick-src :reg t6) + (normal-src :reg t4) + (color-index-src :reg v1) + (spare :reg v1) ;; the control cursors this pass does not need land here + (palette :reg t0) + (entry-kind :reg a1) + (num-bps :reg a1) + (num-vtxs :reg a2) + (instance-only :reg a2) + (num-ips :reg a2) + (num-dps :reg t8)) + ;; Three of the fifteen control words are read into the same register and thrown away: + ;; the shader, run-control and interpolation cursors belong to the packet builder and to + ;; generic-interp-dproc, not to this pass. + (asm-block load-fragment-tables + (lui at #x7000) + (add.i control at (generic-work-offset in-buf tie control)) + (l.w out-buf at (generic-work-offset in-buf tie shadow out-buf)) + (nop!) + (l.w entry-kind (-> control tie-type)) + (nop!) + (l.w spare (-> control ptr-shaders)) + (nop!) + (l.w spare (-> control ptr-runctrl)) + (nop!) + (l.w point-src (-> control ptr-verts)) + (nop!) + (l.w header (-> control ptr-generic)) + (nop!) + (l.w spare (-> control ptr-interp)) + (nop!) + (l.w index-src (-> control ptr-dps)) + (nop!) + (l.w kick-src (-> control ptr-kicks)) + (nop!) + (l.w normal-src (-> control ptr-normals)) + (nop!) + (l.w instance-mtx (-> control ptr-mtxs)) + (nop!) + (l.w color-index-src (-> control ptr-cinds)) + (nop!) + (l.w palette (-> control ptr-palette)) + (nop!) + (l.hu num-vtxs (-> header gsf-header num-vtxs)) + (nop!) + (l.hu num-dps (-> header gsf-header num-dps)) + (add.i instance-only entry-kind -2) + (l.bu num-bps (-> header num-bps)) + (b.z instance-only tie-generic-transform-fragment :delay (l.bu num-ips (-> header num-ips))))) + ;; Divide the GSF buffer up for this fragment and publish the divisions in its info block, + ;; so that neither the effect processors nor the packet builder has to work them out again: + ;; the index/kick list first, the expanded vertices after it rounded up to a quadword, and + ;; the environment-map attributes after those. gsf-header is copied across whole. + ;; + ;; The tail of the block builds the two lane masks the kick expansion needs. 513 grown into + ;; 0x8040201008040201 puts one distinct bit in each byte lane; 257 grown the same way puts + ;; 01 in all sixteen. See generic-tie-decompress for what they are for - the loop that + ;; follows is that function inlined. + ;; + ;; ra, gp, t7 through t9 and s2 through s5 are address arithmetic and mask temporaries here + ;; and change meaning every few instructions, so they keep their physical names. + (rlet ((out-buf :reg a0 :type gsf-buffer) + (header :reg t1 :type generic-tie-header) + (num-dps :reg t8) + (ik-bytes :reg t7) + (ik-out :reg t7) + (verts-out :reg t9) + (header-quad :reg ra :class i128) + (fx-out :reg ra)) + (asm-block publish-draw-point-arrays + (add ik-bytes num-dps num-dps) + (add.i t9 r0 -16) + (add.i ra ik-bytes 15) + (add.i ik-out out-buf 32) ;; the work area starts after info and header + (and t9 ra t9) + (l.q header-quad (-> header gsf-header)) + (add t9 t7 t9) + (s.q header-quad (-> out-buf header)) + (sll ra t8 5) + (s.w ik-out (-> out-buf info ptr-iks)) + (add fx-out verts-out ra) + (s.w verts-out (-> out-buf info ptr-verts)) + (add.i t8 t8 7) + (s.w fx-out (-> out-buf info ptr-fx)) + (sra t9 t8 3) + (nop!) + (add.i t8 r0 2) + (nop!) + (mult3 s3 t8 t9) + (m ra t6) + (m t6 t8) + (m t7 t7) + (add.i t8 r0 513) + (add.i t9 r0 257) + (sll s1 t8 18) + (sll s2 t9 16) + (or t8 t8 s1) + (or t9 t9 s2) + (dsll32 s1 t8 4) + (dsll32 s2 t9 0) + (or t8 t8 s1) + (or t9 t9 s2) + (pcpyld t8 t8 t8) + (mmi-nop!) + (pcpyld t9 t9 t9) + (mmi-nop!) + (add s3 s3 ra) + (mmi-nop!) + (l.hu s2 ra) + (add ra ra t6) + (nop!) + (m t5 t5) + (nop!) + (pextlb s2 s2 s2) + (b tie-generic-expand-kick-group :delay (pextlb s1 s2 s2)))) + ;; generic-tie-decompress, inlined: sixteen draw points per pass, each one an index byte + ;; married to a kick bit that has been spread out to a whole byte. The loop is rotated, so + ;; the second output quadword of a pass is stored at the top of the next one. + ;; + ;; The pass count is generous, and why it is written this way is not established. Sixteen draw + ;; points are finished per pass, but the bound steps one halfword of kick bits per pass across + ;; ceil(num-dps / 8) halfwords, so it runs about twice as many passes as there are draw points and + ;; writes off the end of the list. It cannot get far: + ;; the overshoot is at most another 2 * num-dps bytes, the vertex array immediately behind the + ;; list is 32 bytes per vertex against the list's 2 per draw point, and the vertex array is + ;; filled a few hundred instructions later regardless. So the spill stays inside the buffer and + ;; inside data that is about to be overwritten. + (asm-block interleave-indices-and-kicks + (label tie-generic-next-kick-group) + (add.i t5 t5 16) + (nop!) + (add ra ra t6) + (add.i t7 t7 32) + (pextlb s1 s1 s1) + (s.q s2 t7 -16) + (label tie-generic-expand-kick-group) + (pextlb s2 s1 s1) + (mmi-nop!) + (and.q s2 s2 t8) + (mmi-nop!) + (pceqb s1 s2 t8) + (l.q s2 t5) + (and.q v0 s1 t9) + (mmi-nop!) + (pextlb s1 v0 s2) + (l.hu s0 ra) + (pextub s2 v0 s2) + (s.q s1 t7) + (b.ne ra s3 tie-generic-next-kick-group :delay (pextlb s1 s0 s0)) + (nop!) + (s.q s2 t7 16)) + ;; Leave a job behind for generic-interp-dproc if this fragment has any subdivision + ;; vertices: they occupy num-ips slots starting right after the base points, and the blend + ;; data they need is the interpolation table in the fragment. A fragment with none clears + ;; the pointer instead, which is how the processor knows to do nothing. + (rlet ((interp-job :reg t5 :type generic-interp-job) + (control :reg t2 :type generic-tie-control) + (interp-data :reg t2) + (num-bps :reg a1) + (num-ips :reg a2)) + (asm-block publish-interp-job + (m interp-job r0) + (b.z num-ips tie-generic-no-interp-job :delay (nop!)) + (add.i t6 num-ips 7) + (add.i interp-job at (generic-work-offset in-buf tie interp-job)) + (nop!) + (l.w interp-data (-> control ptr-interp)) + (sra t6 t6 3) + (s.h r0 (-> interp-job job-type)) + (nop!) + (s.h num-bps (-> interp-job first)) + (nop!) + (s.h num-ips (-> interp-job num)) + (nop!) + (s.w t2 (-> interp-job ptr-data)) + (nop!) + (s.w interp-job at (generic-work-offset saves ptr-interp-job)) + (b tie-generic-interp-job-ready :delay (nop!)) + (label tie-generic-no-interp-job) + (nop!) + (s.w r0 at (generic-work-offset saves ptr-interp-job)) + (nop!))) + ;; Hand the fragment's normals to VU0 four at a time. Each one is three signed bytes, so a + ;; quadword of them is four normals; pextub/pextlb move each byte into the top of a + ;; halfword and pextuh/pextlh into the top of a word, and then one arithmetic shift by 19 + ;; leaves the byte scaled by 32 with its sign intact - which the VU reads as 1.12 fixed + ;; point, giving a normal in [-1, 1). Two shifts and two interleaves per four normals, and + ;; no multiplies. + ;; + ;; The count is base points plus interpolated points rounded up to four, so the last pass + ;; can read a little past the table. vi08 and vi09 carry the two counts into VU0, which + ;; sizes its staging arrays from them; vi09 is written twice because the first write is too + ;; close to the call for the transfer to have landed. + (rlet ((normal-src :reg t4) + (normal-cursor :reg t2) + (normal-end :reg t4) + (num-bps :reg a1) + (num-ips :reg a2)) + (asm-block stage-normals + (label tie-generic-interp-job-ready) + (add t5 num-bps num-ips) + (m normal-cursor normal-src) + (add.i t4 t5 3) + (ctc2.i vi9 num-ips) + (sra t5 t4 2) + (l.q t4 normal-cursor) + (sll t5 t5 4) + (add.i normal-cursor normal-cursor 16) + (pextub t7 t4 r0) + (ctc2.i vi8 num-bps) + (pextlb t6 t4 r0) + (ctc2.i vi9 num-ips) + (add normal-end normal-cursor t5) + (callms GENERIC-VU0-TIE-SETUP) + (label tie-generic-stage-normal-group) + (pextuh t5 t7 r0) + (mmi-nop!) + (pextlh t7 t7 r0) + (mmi-nop!) + (pextuh t8 t6 r0) + (mmi-nop!) + (pextlh t6 t6 r0) + (mmi-nop!) + (sra.w t9 t6 19) + (l.q t6 normal-cursor) + (sra.w t8 t8 19) + (m.ni vf5 t9) + (sra.w t7 t7 19) + (m.ni vf6 t8) + (sra.w t5 t5 19) + (m.ni vf7 t7) + (pextub t7 t6 r0) + (m.ni vf8 t5) + (pextlb t6 t6 r0) + (mmi-nop!) + (nop!) + (callms GENERIC-VU0-TIE-NORMALS) + (b.ne normal-cursor normal-end tie-generic-stage-normal-group :delay (add.i normal-cursor normal-cursor 16)) + (nop!))) + ;; Now the positions, again four at a time. A base point is sixteen bytes: three signed + ;; 16-bit coordinates, an output offset, the texture coordinate pair, and a second output + ;; offset. The doubleword load takes the position and the word load at +8 takes the + ;; coordinates; the shift by 10 leaves each coordinate multiplied by 64, which is the scale + ;; the instance matrix is built to undo. + ;; + ;; The loop is software pipelined against VU0: the call at the top of the body works on the + ;; group staged by the previous iteration while this one crosses the next group over. That + ;; is why the last group is staged outside the loop and why there is a call after it. + (rlet ((point-src :reg t3) + (point-cursor :reg t2) + (num-bps :reg a1) + (remaining :reg t4)) + (asm-block stage-base-points + (m point-cursor point-src) + (b.z num-bps tie-generic-stage-interp-pair :delay (add.i remaining num-bps -4)) + (b.le remaining r0 tie-generic-base-point-tail :delay (nop!)) + (m point-cursor point-src) + (l.d t5 t3) + (add.i remaining num-bps -8) + (l.d t6 t3 16) + (pextlh t7 t5 r0) + (l.d t5 t3 32) + (pextlh ra t6 r0) + (l.d t6 t3 48) + (pextlh t8 t5 r0) + (l.wu t5 t3 8) + (pextlh t9 t6 r0) + (l.wu t6 t3 24) + (sra.w s3 t7 10) + (l.wu t7 t3 40) + (sra.w ra ra 10) + (l.wu t3 t3 56) + (sra.w t8 t8 10) + (m.ni vf1 s3) + (sra.w t9 t9 10) + (m.ni vf2 ra) + (add.i point-cursor point-cursor 64) + (m.ni vf3 t8) + (nop!) + (m.ni vf4 t9) + (nop!) + (m.ni vf13 t5) + (nop!) + (m.ni vf14 t6) + (nop!) + (m.ni vf15 t7) + (b.le t4 r0 tie-generic-stage-last-base-points :delay (m.ni vf16 t3)) + (label tie-generic-stage-base-point-group) + (callms GENERIC-VU0-TIE-BASE-POINTS) + (l.d t3 point-cursor) + (add.i t4 t4 -4) + (l.d t5 point-cursor 16) + (pextlh t8 t3 r0) + (l.d t3 point-cursor 32) + (pextlh t9 t5 r0) + (l.d t5 point-cursor 48) + (pextlh t6 t3 r0) + (l.wu t3 point-cursor 8) + (pextlh t7 t5 r0) + (l.wu t5 point-cursor 24) + (sra.w ra t8 10) + (l.wu t8 point-cursor 40) + (sra.w t9 t9 10) + (l.wu s3 point-cursor 56) + (sra.w t6 t6 10) + (m.ni vf1 ra) + (sra.w t7 t7 10) + (m.ni vf2 t9) + (add.i point-cursor point-cursor 64) + (m.ni vf3 t6) + (nop!) + (m.ni vf4 t7) + (nop!) + (m.ni vf13 t3) + (nop!) + (m.ni vf14 t5) + (nop!) + (m.ni vf15 t8) + (b.gt t4 r0 tie-generic-stage-base-point-group :delay (m.ni vf16 s3)) + (label tie-generic-stage-last-base-points) + (callms GENERIC-VU0-TIE-BASE-POINTS) + (nop!))) + ;; The four-wide pipeline does not care that base points and interpolated points are + ;; different sizes, so the group that straddles the boundary between them is staged by a + ;; VU0 entry written for that exact shape. num-bps modulo four picks it: one base point + ;; plus one interpolated point, two plus one, three on their own, or - when the remainder + ;; is zero, or when the fragment has no interpolated points at all - one more plain group + ;; of four. + ;; + ;; The last case reads up to three points past the end of the array when the count is not a + ;; multiple of four. VU0 transforms them and the write loop, which counts in fours as well, + ;; stores them; both overruns land inside the GSF buffer's slack. + (rlet ((point-cursor :reg t2) + (num-bps :reg a1) + (num-ips :reg a2)) + (asm-block stage-boundary-group + (label tie-generic-base-point-tail) + (b.z num-ips tie-generic-stage-final-base-group :delay (and.i t3 num-bps 3)) + (b.z t3 tie-generic-stage-final-base-group :delay (add.i t3 t3 -1)) + (b.z t3 tie-generic-stage-tail-1bp-1ip :delay (add.i t3 t3 -1)) + (b.z t3 tie-generic-stage-tail-2bp-1ip :delay (add.i t3 t3 -1)) + (b tie-generic-stage-tail-3bp :delay (nop!)) + (label tie-generic-stage-tail-1bp-1ip) + (nop!) + (l.d t3 point-cursor) + (nop!) + (l.d t4 point-cursor 16) + (pextlh t3 t3 r0) + (l.d t6 point-cursor 24) + (pextlh t4 t4 r0) + (l.wu t5 point-cursor 8) + (pextlh t6 t6 r0) + (l.wu t7 point-cursor 32) + (nop! :count 4) + (sra.w t3 t3 10) + (m.ni vf13 t5) + (sra.w t4 t4 10) + (m.ni vf14 t7) + (sra.w t5 t6 10) + (m.ni vf1 t3) + (nop!) + (m.ni vf2 t4) + (add.i point-cursor point-cursor 40) + (m.ni vf3 t5) + (b tie-generic-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-1BP-1IP)) + (label tie-generic-stage-tail-2bp-1ip) + (nop!) + (l.d t4 point-cursor) + (nop!) + (l.d t3 point-cursor 16) + (pextlh t8 t4 r0) + (l.d t5 point-cursor 32) + (pextlh t4 t3 r0) + (l.d t3 point-cursor 40) + (pextlh t5 t5 r0) + (l.wu t6 point-cursor 8) + (pextlh t7 t3 r0) + (l.wu t3 point-cursor 24) + (sra.w t9 t8 10) + (l.wu t8 point-cursor 48) + (sra.w t4 t4 10) + (m.ni vf13 t6) + (sra.w t5 t5 10) + (m.ni vf1 t9) + (sra.w t6 t7 10) + (m.ni vf2 t4) + (add.i point-cursor point-cursor 56) + (m.ni vf3 t5) + (nop!) + (m.ni vf4 t6) + (nop!) + (m.ni vf14 t3) + (nop!) + (m.ni vf15 t8) + (b tie-generic-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-2BP-1IP)) + (label tie-generic-stage-tail-3bp) + (nop!) + (l.d t4 point-cursor) + (nop!) + (l.d t3 point-cursor 16) + (pextlh t7 t4 r0) + (l.d t4 point-cursor 32) + (pextlh t5 t3 r0) + (l.wu t3 point-cursor 8) + (pextlh t6 t4 r0) + (l.wu t4 point-cursor 24) + (sra.w t7 t7 10) + (l.wu t8 point-cursor 40) + (sra.w t5 t5 10) + (m.ni vf13 t3) + (sra.w t3 t6 10) + (m.ni vf1 t7) + (nop!) + (m.ni vf2 t5) + (add.i point-cursor point-cursor 48) + (m.ni vf3 t3) + (nop!) + (m.ni vf14 t4) + (nop!) + (m.ni vf15 t8) + (b tie-generic-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-3BP)) + (label tie-generic-stage-final-base-group) + (nop!) + (l.d t3 point-cursor) + (nop!) + (l.d t4 point-cursor 16) + (pextlh t7 t3 r0) + (l.d t3 point-cursor 32) + (pextlh t8 t4 r0) + (l.d t4 point-cursor 48) + (pextlh t5 t3 r0) + (l.wu t3 point-cursor 8) + (pextlh t6 t4 r0) + (l.wu t4 point-cursor 24) + (sra.w t9 t7 10) + (l.wu t7 point-cursor 40) + (sra.w t8 t8 10) + (l.wu ra point-cursor 56) + (sra.w t5 t5 10) + (m.ni vf1 t9) + (sra.w t6 t6 10) + (m.ni vf2 t8) + (add.i point-cursor point-cursor 64) + (m.ni vf3 t5) + (nop!) + (m.ni vf4 t6) + (nop!) + (m.ni vf13 t3) + (nop!) + (m.ni vf14 t4) + (nop!) + (m.ni vf15 t7) + (nop!) + (m.ni vf16 ra) + (b.z num-ips tie-generic-staging-done :delay (callms GENERIC-VU0-TIE-BASE-POINTS)))) + ;; Whatever interpolated points are left go two at a time, because each one is twenty-four + ;; bytes: the vector the morph weight scales, the vector it is added to, and the coordinate + ;; pair. Three of them fill two quadwords, which is why the stride is 48 and not 32. + (rlet ((point-cursor :reg t2) + (num-ips :reg a2) + (remaining :reg t3)) + (asm-block stage-interp-points + (label tie-generic-stage-interp-pair) + (nop!) + (l.d t4 point-cursor) + (add.i remaining num-ips -2) + (l.d t5 point-cursor 8) + (pextlh t4 t4 r0) + (l.d t6 point-cursor 24) + (pextlh t5 t5 r0) + (l.d t7 point-cursor 32) + (pextlh t6 t6 r0) + (l.wu t8 point-cursor 16) + (pextlh t7 t7 r0) + (l.wu t9 point-cursor 40) + (sra.w t4 t4 10) + (m.ni vf13 t8) + (sra.w t5 t5 10) + (m.ni vf14 t9) + (sra.w t6 t6 10) + (m.ni vf1 t4) + (sra.w t4 t7 10) + (m.ni vf2 t5) + (add.i point-cursor point-cursor 48) + (m.ni vf3 t6) + (b.le remaining r0 tie-generic-stage-last-interp-pair :delay (m.ni vf4 t4)) + (label tie-generic-stage-interp-pair-loop) + (callms GENERIC-VU0-TIE-INTERP-POINTS) + (l.d t4 point-cursor) + (add.i remaining remaining -2) + (l.d t5 point-cursor 8) + (pextlh t4 t4 r0) + (l.d t6 point-cursor 24) + (pextlh t5 t5 r0) + (l.d t7 point-cursor 32) + (pextlh t6 t6 r0) + (l.wu t8 point-cursor 16) + (pextlh t7 t7 r0) + (l.wu t9 point-cursor 40) + (sra.w t4 t4 10) + (m.ni vf13 t8) + (sra.w t5 t5 10) + (m.ni vf14 t9) + (sra.w t6 t6 10) + (m.ni vf1 t4) + (sra.w t4 t7 10) + (m.ni vf2 t5) + (add.i point-cursor point-cursor 48) + (m.ni vf3 t6) + (b.gt remaining r0 tie-generic-stage-interp-pair-loop :delay (m.ni vf4 t4)) + (label tie-generic-stage-last-interp-pair) + (callms GENERIC-VU0-TIE-INTERP-POINTS) + (nop!) + (label tie-generic-staging-done) + (nop!) + (nop!))) + ;; The transform starts here, and so does the one piece of per-fragment color work the EE + ;; still has to do itself: the environment map's tint. It is the fragment's authored tint, + ;; scaled by this instance's fade value and then by the time-of-day environment color. + ;; Both scalings are packed halfword multiplies whose products come back out of the + ;; accumulator halved into HI and LO, so pmfhl.lh gathers them and a shift by seven both + ;; discards the 0..255 range and keeps the rounding cheap. The result goes into all four + ;; lanes of the shared envmap color constant, where prepare-dma-double will find it. + ;; + ;; The other thing decided here is which VU1 entry the packet will call. The instance pass left + ;; its near-classification reject flags in the second word of the instance record's fifth + ;; quadword; an instance carrying any of them would have gone to the near renderer, which clips, + ;; so it takes the clipping entry here and everything else takes the direct one. Patching the + ;; immediate of the MSCAL template is the whole of it. + (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) + (out-buf :reg a0 :type gsf-buffer) + (header :reg t1 :type generic-tie-header) + (tint :reg t1) + (vtx-out :reg t1) + (vtx-count :reg t3) + (cind-cursor :reg t4) + (color-index-src :reg v1) + (num-bps :reg a1) + (vu1-entry :reg t6)) + (asm-block build-envmap-tint + (label tie-generic-transform-fragment) + (m vtx-count num-bps) + (l.w t6 (-> instance-mtx fog data 0)) + (m cind-cursor color-index-src) + (l.w tint (-> header tint-color)) + (add.i t5 at (generic-work-offset fx-buf work consts envmap colors)) + (s.w t6 at (generic-work-offset saves fade-val)) + (pextlb t2 r0 t1) + (l.q t1 (-> instance-mtx matrix vector 0)) + (pextlh t7 t6 t6) + (l.q t6 (-> instance-mtx matrix vector 1)) + (pextlh t8 t7 t7) + (l.q t7 (-> instance-mtx matrix vector 2)) + (pmulth r0 t2 t8) + (l.q t2 (-> instance-mtx matrix vector 3)) + (nop!) + (m.ni vf1 t1) + (nop!) + (m.ni vf2 t6) + (nop!) + (m.ni vf3 t7) + (nop!) + (m.ni vf4 t2) + (callms GENERIC-VU0-TIE-BASE-START) + (m t1 t0) + (pextlw t2 t1 t1) + (l.w vtx-out (-> out-buf info ptr-verts)) + (pcpyld t2 t2 t2) + (l.w t8 at (generic-work-offset saves time-of-day-color)) + (pmfhl.lh t7) + (l.w vu1-entry (-> instance-mtx fog data 1)) + (pextlb t8 r0 t8) + (mmi-nop!) + (psrlh t7 t7 7) + (mmi-nop!) + (pmulth r0 t7 t8) + (mmi-nop!) + (b.nz vu1-entry tie-generic-use-clipping-entry :delay (mmi-nop!)) + (nop!) + (add.i vu1-entry r0 GENERIC-VU1-DIRECT) + (b tie-generic-entry-selected :delay (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag))) + (label tie-generic-use-clipping-entry) + (nop!) + (add.i vu1-entry r0 GENERIC-VU1-CLIPPED) + (nop!) + (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag)))) + ;; Collect four transformed base points from VU0 and write them out as four gsf-vertex + ;; records, 32 bytes each: position and coordinate pair in the first quadword, normal in + ;; the second, and then the palette color overwriting the normal's unused w. + ;; + ;; The color is a plain lookup. Each vertex carries a one-byte palette index; shifting it + ;; left by two and adding the broadcast palette base gives four addresses in one packed + ;; add, and the four loads are spread through the dependent VU0 crossings so the load + ;; delays cost nothing. Because the whole loop is one iteration behind VU0, the stores at + ;; the top of the body belong to the previous group. + ;; + ;; t5 through t9, gp, ra and s1 through s5 are the shuttles carrying quadwords between VU0 + ;; and memory. Their meaning changes every few instructions and they keep physical names. + (rlet ((vtx-out :reg t1) + (palette-lanes :reg t2 :class i128) + (vtx-count :reg t3) + (cind-cursor :reg t4)) + (asm-block write-base-vertices + (label tie-generic-entry-selected) + (pmfhl.lh t6) + (mmi-nop!) + (nop!) + (mmi-nop!) + (psrlh t6 t6 7) + (mmi-nop!) + (ppacb t6 r0 t6) + (mmi-nop!) + (nop!) + (l.w t7 cind-cursor) + (add.i cind-cursor cind-cursor 4) + (s.w t6 t5) + (pextlb t7 r0 t7) + (s.w t6 t5 4) + (pextlh t7 r0 t7) + (s.w t6 t5 8) + (sll.w t7 t7 2) + (s.w t6 t5 12) + (add.w t9 t7 palette-lanes) + (mmi-nop!) + (srl32 s3 t9 0) + (m.ni t7 vf17) + (pcpyud t5 t9 r0) + (mmi-nop!) + (srl32 t6 t5 0) + (m.ni ra vf13) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (m.ni t8 vf14) + (nop!) + (l.wu t9 t9) + (nop!) + (l.wu s3 s3) + (nop!) + (l.wu t5 t5) + (nop!) + (m.ni s0 vf18) + (nop!) + (l.wu t6 t6) + (nop!) + (s.q ra vtx-out) + (add.i vtx-count vtx-count -4) + (nop! :count 3) + (b.le vtx-count r0 tie-generic-drain-base-vertices :delay (nop!)) + (label tie-generic-write-base-vertex-group) + (callms GENERIC-VU0-TIE-BASE-XFORM) + (s.q t7 vtx-out 16) + (nop!) + (s.w t9 vtx-out 28) + (nop!) + (s.q t8 vtx-out 32) + (nop!) + (s.q s0 vtx-out 48) + (nop!) + (s.w s3 vtx-out 60) + (nop! :count 7) + (mmi-nop!) + (nop!) + (l.w t8 cind-cursor) + (add.i cind-cursor cind-cursor 4) + (m.ni t7 vf19) + (pextlb t9 r0 t8) + (m.ni t8 vf20) + (pextlh t9 r0 t9) + (m.ni s3 vf15) + (sll.w ra t9 2) + (m.ni t9 vf16) + (add.w s0 ra palette-lanes) + (s.q t7 vtx-out 80) + (srl32 v0 s0 0) + (m.ni t7 vf17) + (pcpyud ra s0 r0) + (s.q s3 vtx-out 64) + (srl32 s2 ra 0) + (m.ni s1 vf13) + (nop!) + (s.q t8 vtx-out 112) + (nop!) + (s.w t5 vtx-out 92) + (nop!) + (s.w t6 vtx-out 124) + (nop!) + (s.q t9 vtx-out 96) + (nop!) + (l.wu t9 s0) + (nop!) + (l.wu s3 v0) + (nop!) + (m.ni t8 vf14) + (add.i vtx-out vtx-out 128) + (m.ni s0 vf18) + (add.i vtx-count vtx-count -4) + (l.wu t5 ra) + (nop!) + (s.q s1 vtx-out) + (b.gt vtx-count r0 tie-generic-write-base-vertex-group :delay (l.wu t6 s2)))) + ;; The drain entry publishes the group still inside the pipeline. Four of its results have + ;; to be stored straight out of the VU registers rather than through the usual crossing, + ;; because there is no following group whose crossings could carry them. + (rlet ((vtx-out :reg t1) + (num-ips :reg a2)) + (asm-block drain-base-vertices + (label tie-generic-drain-base-vertices) + (callms GENERIC-VU0-TIE-BASE-DRAIN) + (s.q t7 vtx-out 16) + (nop!) + (s.w t9 vtx-out 28) + (nop!) + (s.q t8 vtx-out 32) + (nop!) + (s.q s0 vtx-out 48) + (nop!) + (s.w s3 vtx-out 60) + (nop!) + (vnop) + (nop!) + (s.vf vf19 vtx-out 80) + (nop!) + (s.vf vf15 vtx-out 64) + (nop!) + (s.vf vf16 vtx-out 96) + (nop!) + (s.vf vf20 vtx-out 112) + (nop!) + (s.w t5 vtx-out 92) + (nop!) + (s.w t6 vtx-out 124) + (b.z num-ips tie-generic-run-effect-processors :delay (nop!)))) + ;; Interpolated points next, and they take one of two paths. The instance's morph quadword + ;; holds the pair of color weights that trade off between a vertex's own palette entry and + ;; the average of the two entries it blends toward; the second weight arrives doubled, + ;; because summing two neighbours instead of averaging them is one instruction cheaper, so + ;; it is halved here. If it comes out zero nothing is blending - the instance is at the near + ;; edge of its LOD band - and the cheaper path, which reads only the second of each point's + ;; two vectors and only the vertex's own color, is taken instead. + ;; + ;; The blended path is the interesting one. Both weights are broadcast into every halfword + ;; lane, and the blend is pmulth followed by pmaddh - four channels of two colors weighted + ;; and summed in two instructions - with the sum coming back out of HI and LO through + ;; pmfhl.lh. The weights add up to 256 rather than to one, so the shift by eight that + ;; follows is the normalization. + ;; + ;; Each interpolated point's colors are four bytes: its own index and the two it blends + ;; toward. They follow the base points' one-byte indices, rounded up to four. + (asm-block start-interp-vertices + ;; Everything the rest of the block needs is set up here, out of the instance record + ;; and the GSF info block. After this the same registers carry broadcast weights and + ;; packed palette addresses, so they go back to physical names. + (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) + (cind-cursor :reg a3) + (out-buf :reg a0 :type gsf-buffer) + (palette :reg t0) + (palette-base :reg t1) + (color-index-src :reg v1) + (num-bps :reg a1) + (num-ips :reg a2) + (vtx-base :reg t2)) + (m palette-base palette) + (l.vf vf29 (-> instance-mtx morph)) + (m palette color-index-src) + (l.s f0 (-> instance-mtx morph z)) + (add.i palette num-bps 3) + (l.s f2 (-> instance-mtx morph w)) + (add.i cind-cursor r0 -4) + (nop!) + (cvt.w.s f0 f0) + (and cind-cursor palette cind-cursor) + (cvt.w.s f2 f2) + (l.w vtx-base (-> out-buf info ptr-verts)) + (m a0 f0) + (m palette num-ips) + (m t3 f2) + (sll a2 num-bps 5) + (sra a1 t3 1) + (add cind-cursor color-index-src cind-cursor)) + (b.z a1 tie-generic-interp-without-blend :delay (add a2 t2 a2)) + (pcpyh v1 a0) + (mmi-nop!) + (pcpyld v1 v1 v1) + (s.h v1 at (generic-work-offset in-buf tie interp-job morph-z)) + (pcpyh a0 a1) + (s.h a0 at (generic-work-offset in-buf tie interp-job morph-w)) + (pcpyld a0 a0 a0) + (l.dr s5 a3) + (pextlw a1 t1 t1) + (l.dl s5 a3 7) + (pcpyld a1 a1 a1) + (l.dr s4 a3 8) + (pextlb t2 r0 s5) + (l.dl s4 a3 15) + (pextuh t1 r0 t2) + (mmi-nop!) + (pextlh t3 r0 t2) + (mmi-nop!) + (pextlb t4 r0 s4) + (mmi-nop!) + (pextuh t2 r0 t4) + (mmi-nop!) + (pextlh t5 r0 t4) + (mmi-nop!) + (sll.w t3 t3 2) + (mmi-nop!) + (sll.w t4 t1 2) + (callms GENERIC-VU0-TIE-MORPH-START) + (sll.w t6 t5 2) + (mmi-nop!) + (sll.w t5 t2 2) + (mmi-nop!) + (add.w t1 t3 a1) + (mmi-nop!) + (add.w t4 t4 a1) + (l.wu t3 t1) + (add.w t2 t6 a1) + (l.wu t9 t4) + (add.w t5 t5 a1) + (mmi-nop!) + (srl32 s4 t1 0) + (mmi-nop!) + (srl32 s3 t4 0) + (mmi-nop!) + (srl32 ra t2 0) + (add.i a2 a2 -128) + (srl32 t8 t5 0) + (add.i a3 a3 16) + (pcpyud t7 t1 r0) + (l.wu s5 t2) + (pcpyud t6 t4 r0) + (l.wu s2 t5) + (pcpyud t4 t2 r0) + (l.wu s4 s4) + (pcpyud t5 t5 r0) + (l.wu s3 s3) + (pextlw t9 t9 t3) + (l.wu ra ra) + (pextlw t3 s2 s5) + (l.wu s2 t8) + (pextlw s5 s3 s4) + (l.wu t8 t7) + (pextlw t7 s2 ra) + (l.wu ra t6) + (pextlb t6 r0 t9) + (l.wu t9 t4) + (pextlb t4 r0 s5) + (l.wu s5 t5) + (pextlw ra ra t8) + (m.ni t5 vf17) + (pextlw t8 s5 t9) + (mmi-nop!) + (pextlb t9 r0 ra) + (mmi-nop!) + (add.h t9 t4 t9) + (m.ni t4 vf13) + (pmulth r0 t9 a0) + (mmi-nop!) + (pextlb t3 r0 t3) + (s.q t5 a2 144) + (pmaddh r0 t6 v1) + (s.q t4 a2 128) + (pextlb t5 r0 t7) + (l.dr t1 a3) + (pextlb t7 r0 t8) + (l.dl t1 a3 7) + (pmfhl.lh t6) + (m.ni t4 vf18) + (add.h t5 t5 t7) + (l.dr t2 a3 8) + (psrlh t6 t6 8) + (l.dl t2 a3 15) + (ppacb t6 r0 t6) + (s.q t4 a2 176) + (srl32 t4 t6 0) + (add.i t0 t0 -4) + (pmulth r0 t5 a0) + (m.ni t5 vf14) + (pextlb t1 r0 t1) + (s.w t6 a2 156) + (pmaddh r0 t3 v1) + (s.w t4 a2 188) + (pextlb t3 r0 t2) + (s.q t5 a2 160) + (pextuh t2 r0 t1) + (mmi-nop!) + (pmfhl.lh t6) + (m.ni t5 vf19) + (pextlh t4 r0 t1) + (mmi-nop!) + (psrlh t1 t6 8) + (mmi-nop!) + (ppacb t6 r0 t1) + (s.q t5 a2 208) + (srl32 t1 t6 0) + (s.w t6 a2 220) + (pextuh t5 r0 t3) + (m.ni t6 vf15) + (pextlh t7 r0 t3) + (mmi-nop!) + (sll.w t3 t4 2) + (s.q t6 a2 192) + (sll.w t4 t2 2) + (mmi-nop!) + (sll.w t6 t7 2) + (mmi-nop!) + (sll.w t5 t5 2) + (mmi-nop!) + (add.w s5 t3 a1) + (m.ni t2 vf20) + (add.w t4 t4 a1) + (l.wu t3 s5) + (add.w s4 t6 a1) + (l.wu t9 t4) + (b.le t0 r0 tie-generic-drain-morphed-group :delay (add.w t5 t5 a1))) + ;; Four blending vertices per pass. Same shape as the base-point loop - four records out, the + ;; palette read as four packed addresses, everything one group behind VU0 - with the two-entry + ;; color blend folded into the same instruction stream. + ;; + ;; Unnamed on purpose, as above: this is the densest interleaving in the file and every general + ;; register is reused two or three times per pass. The only registers with a lifetime worth a + ;; name are a3, the color-index cursor, and a2, the vertex output cursor; both step by a fixed + ;; amount at the bottom of the loop. + (asm-block write-morphed-interp-vertices + (label tie-generic-write-morphed-group) + (srl32 s3 s5 0) + (callms GENERIC-VU0-TIE-MORPH-XFORM) + (srl32 s0 t4 0) + (srl32 s2 s4 0) + (srl32 t8 t5 0) + (add.i a2 a2 128) + (pcpyud t7 s5 r0) + (l.wu ra s4) + (pcpyud t6 t4 r0) + (l.wu s1 t5) + (pcpyud t4 s4 r0) + (l.wu s3 s3) + (pcpyud t5 t5 r0) + (l.wu s0 s0) + (pextlw t9 t9 t3) + (l.wu s2 s2) + (pextlw t3 s1 ra) + (l.wu s1 t8) + (pextlw ra s0 s3) + (l.wu t8 t7) + (pextlw t7 s1 s2) + (l.wu s3 t6) + (pextlb t6 r0 t9) + (l.wu t9 t4) + (pextlb t4 r0 ra) + (l.wu t5 t5) + (pextlw t8 s3 t8) + (s.q t2 a2 112) + (pextlw t2 t5 t9) + (m.ni t5 vf17) + (pextlb t8 r0 t8) + (s.w t1 a2 124) + (add.h t1 t4 t8) + (mmi-nop!) + (pmulth r0 t1 a0) + (m.ni t4 vf13) + (pextlb t1 r0 t3) + (s.q t5 a2 144) + (pmaddh r0 t6 v1) + (l.dr s5 a3 16) + (pextlb t3 r0 t7) + (s.q t4 a2 128) + (pextlb t2 r0 t2) + (l.dl s5 a3 23) + (pmfhl.lh t4) + (l.dr s4 a3 24) + (add.h t2 t3 t2) + (m.ni t5 vf18) + (psrlh t3 t4 8) + (l.dl s4 a3 31) + (ppacb t3 r0 t3) + (s.q t5 a2 176) + (srl32 t5 t3 0) + (add.i t0 t0 -4) + (pmulth r0 t2 a0) + (mmi-nop!) + (pextlb t4 r0 s5) + (m.ni t6 vf14) + (pmaddh r0 t1 v1) + (s.w t3 a2 156) + (pextlb t3 r0 s4) + (s.w t5 a2 188) + (pextuh t2 r0 t4) + (s.q t6 a2 160) + (pmfhl.lh t6) + (m.ni t1 vf16) + (pextlh t4 r0 t4) + (m.ni t5 vf19) + (psrlh t6 t6 8) + (s.q t1 a2 96) + (ppacb t6 r0 t6) + (s.q t5 a2 208) + (srl32 t1 t6 0) + (add.i a3 a3 16) + (pextuh t5 r0 t3) + (mmi-nop!) + (pextlh t7 r0 t3) + (m.ni t8 vf15) + (sll.w t3 t4 2) + (s.w t6 a2 220) + (sll.w t4 t2 2) + (s.q t8 a2 192) + (sll.w t6 t7 2) + (mmi-nop!) + (sll.w t5 t5 2) + (mmi-nop!) + (add.w s5 t3 a1) + (m.ni t2 vf20) + (add.w t4 t4 a1) + (l.wu t3 s5) + (add.w s4 t6 a1) + (l.wu t9 t4) + (b.gt t0 r0 tie-generic-write-morphed-group :delay (add.w t5 t5 a1))) + ;; Publish the last collapsing group. + (asm-block drain-morphed-interp-vertices + (label tie-generic-drain-morphed-group) + (nop!) + (callms GENERIC-VU0-TIE-MORPH-DRAIN) + (nop!) + (s.q t2 a2 240) + (nop!) + (m v1 vf16) + (nop!) + (s.w t1 a2 252) + (b tie-generic-run-effect-processors :delay (s.q v1 a2 224))) + ;; The unblended path. Only the second of each point's two vectors is transformed and only the + ;; vertex's own palette entry is read, so the four indices are four plain byte loads four bytes + ;; apart instead of a packed gather. + ;; + ;; No aliases below, and deliberately so: every register in this loop is a shuttle. a0 through + ;; a3, t0 through t7 and s4/s5 each carry a palette address, then a loaded color, then a + ;; quadword on its way out of VU0, within a dozen instructions, and there is no name that would + ;; be true for more than one of those. The cursors are v1 for the color indices and a2 for the + ;; output. + (asm-block write-authored-interp-vertices + (label tie-generic-interp-without-blend) + (callms GENERIC-VU0-TIE-INTERP-START) + (nop!) + (nop!) + (s.h a0 at (generic-work-offset in-buf tie interp-job morph-z)) + (nop!) + (s.h a1 at (generic-work-offset in-buf tie interp-job morph-w)) + (nop! :count 17) + (l.bu v1 a3) + (nop!) + (l.bu a0 a3 4) + (nop!) + (l.bu a1 a3 8) + (sll v1 v1 2) + (l.bu t2 a3 12) + (sll a0 a0 2) + (nop!) + (sll t3 a1 2) + (nop!) + (sll t4 t2 2) + (m t2 vf17) + (add s5 v1 t1) + (add.i v1 a3 16) + (add a1 a0 t1) + (nop!) + (add s4 t3 t1) + (nop!) + (add a0 t4 t1) + (nop!) + (nop!) + (m.ni t7 vf13) + (nop! :count 5) + (m.ni a3 vf18) + (nop!) + (l.wu t6 s5) + (nop!) + (l.wu t3 a1) + (nop!) + (l.wu t4 s4) + (nop!) + (m.ni t5 vf14) + (nop!) + (l.wu a0 a0) + (add.i a1 t0 -4) + (s.q t7 a2) + (nop!) + (m.ni t0 vf19) + (nop!) + (s.q t2 a2 16) + (nop!) + (s.w t6 a2 28) + (b.le a1 r0 tie-generic-drain-interp-without-blend :delay (s.q a3 a2 48))) + ;; Four more, until the interpolated points run out. Same allocation as the block above. + (asm-block write-authored-interp-vertices-loop + (label tie-generic-interp-without-blend-loop) + (callms GENERIC-VU0-TIE-INTERP-XFORM) + (s.w t3 a2 60) + (nop!) + (s.q t5 a2 32) + (nop!) + (s.q t0 a2 80) + (nop!) + (s.w t4 a2 92) + (nop! :count 5) + (l.bu a3 v1) + (nop!) + (l.bu t0 v1 4) + (nop!) + (l.bu t2 v1 8) + (sll t3 a3 2) + (l.bu a3 v1 12) + (sll t6 t0 2) + (m t0 vf15) + (sll t4 t2 2) + (m.ni t2 vf20) + (sll t5 a3 2) + (m.ni a3 vf17) + (add s5 t3 t1) + (add.i v1 v1 16) + (add t3 t6 t1) + (m.ni t6 vf16) + (add s4 t4 t1) + (s.q t0 a2 64) + (add t0 t5 t1) + (s.q t2 a2 112) + (nop!) + (m.ni t2 vf13) + (nop!) + (s.w a0 a2 124) + (nop!) + (s.q t6 a2 96) + (nop!) + (m.ni t6 vf18) + (nop!) + (l.wu t7 s5) + (nop!) + (l.wu t3 t3) + (nop!) + (l.wu t4 s4) + (nop!) + (m.ni t5 vf14) + (add.i a2 a2 128) + (l.wu a0 t0) + (add.i a1 a1 -4) + (s.q t2 a2) + (nop!) + (m.ni t0 vf19) + (nop!) + (s.q a3 a2 16) + (nop!) + (s.w t7 a2 28) + (b.gt a1 r0 tie-generic-interp-without-blend-loop :delay (s.q t6 a2 48))) + ;; Publish the last unblended group and the fragment is expanded. + (asm-block drain-authored-interp-vertices + (label tie-generic-drain-interp-without-blend) + (nop!) + (callms GENERIC-VU0-TIE-INTERP-DRAIN) + (nop!) + (s.w t3 a2 60) + (nop!) + (s.q t5 a2 32) + (nop!) + (s.q t0 a2 80) + (nop!) + (s.w t4 a2 92) + (nop!) + (m v1 vf15) + (nop!) + (m.ni a1 vf20) + (nop!) + (m.ni a3 vf16) + (nop!) + (s.q v1 a2 64) + (nop!) + (s.q a1 a2 112) + (nop!) + (s.w a0 a2 124) + (nop!) + (s.q a3 a2 96)) + ;; The expanded fragment is in main memory now; these four build the packet from it. The + ;; addresses are read out of the shadow block rather than resolved through the symbol table, + ;; because this function runs with the GOAL symbol register holding something else. + ;; + ;; prepare-dma-double lays down two packet headers and reserves the six stream cursors, + ;; envmap-dproc fills the reflected coordinates, interp-dproc blends the subdivision vertices' + ;; attributes, and no-light-dproc copies positions and normals and combines the colors. TIE + ;; always wants exactly this set, so unlike mercneric there is nothing to choose between. + ;; + ;; is-envmap is cleared even though this is the environment-map path: it selects which GIF tag + ;; template the base pass takes, and the base pass here is ordinary geometry. + (rlet ((from-spr :reg a2 :type dma-bank-spr)) + (asm-block run-effect-processors + (label tie-generic-run-effect-processors) + (m v1 r0) + (l.w v1 at (generic-work-offset in-buf tie control ptr-shaders)) + (l.w a0 at (generic-work-offset in-buf tie shadow out-buf)) + (add.i a1 at (generic-work-offset fx-buf work consts envmap shader)) + (s.w v1 at (generic-work-offset saves ptr-shaders)) + (s.w a0 at (generic-work-offset saves gsf-buf)) + (s.w a1 at (generic-work-offset saves ptr-env-shader)) + (s.w r0 at (generic-work-offset saves is-envmap)) + (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-prepare-dma-double)) + (jalr ra v1 :delay (sllv v0 ra r0)) + (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-envmap-dproc)) + (jalr ra v1 :delay (sllv v0 ra r0)) + (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-interp-dproc)) + (jalr ra v1 :delay (sllv v0 ra r0)) + (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-no-light-dproc)) + (jalr ra v1 :delay (sllv v0 ra r0)) + (l.w v1 at (generic-work-offset saves cur-outbuf)) + (l.w a0 at (generic-work-offset saves qwc)) + (m a3 v1) + (nop!) + (lui at #x7000) + (lui from-spr #x1000) + (l.wu a1 at (generic-work-offset saves basep)) + (ori from-spr from-spr #xd000) + (l.w t1 (-> from-spr chcr)) + (nop!) + (add.i t0 at (generic-work-offset saves from-spr-waits)) + (and.i a3 a3 #x3fff) + (and.i t1 t1 DMA-CHCR-STR) + (nop!) + (b.z t1 tie-generic-return-output-buffer :delay (nop!)) + (m t1 from-spr) + (nop!))) + ;; The finished packet is in one of the two scratchpad output buffers and the other one is + ;; still being read out by the fromSPR channel. Wait for that, and charge the stalls to the + ;; work area so a frame spent waiting on DMA says so. + (rlet ((from-spr :reg t1 :type dma-bank-spr) + (stall-count-ptr :reg t0) + (stall-count :reg t2) + (status :reg t3)) + (asm-block wait-for-output-drain + (label tie-generic-wait-for-output-drain) + (l.w stall-count stall-count-ptr) + (nop!) + (l.w status (-> from-spr chcr)) + (nop!) + (and.i status status DMA-CHCR-STR) + (add.i stall-count stall-count 1) + (b.nz status tie-generic-wait-for-output-drain + :delay (s.w stall-count stall-count-ptr)) + (m stall-count-ptr r0))) + ;; Hand the buffer to the channel, advance the write cursor in the frame's DMA buffer by what + ;; it will deliver, and switch to the other buffer. Nothing waits for this transfer here; the + ;; wait above is what makes that safe. + (rlet ((from-spr :reg a2 :type dma-bank-spr) + (out-bank :reg a3) ;; the scratchpad buffer being handed over + (chcr-start :reg a3) ;; then the value that starts the channel + (packet-qwc :reg a0) + (next-basep :reg a0) ;; then the advanced main-memory cursor + (dma-cursor :reg a1) + (cur-outbuf :reg v1) + (end-of-chain :reg v1)) ;; then the flag that ends the pass + (asm-block return-output-buffer + (label tie-generic-return-output-buffer) + (sll t0 packet-qwc 4) + (s.w out-bank (-> from-spr sadr)) + (nop!) + (s.w dma-cursor (-> from-spr madr)) + (add.i chcr-start r0 DMA-CHCR-STR) + (s.w packet-qwc (-> from-spr qwc)) + (add next-basep dma-cursor t0) + (s.w chcr-start (-> from-spr chcr)) + (nop!) + (s.w next-basep at (generic-work-offset saves basep)) + (m next-basep r0) + (xor.i cur-outbuf cur-outbuf GENERIC-OUTBUF-FLIP) + (s.w cur-outbuf at (generic-work-offset saves cur-outbuf)) + (nop!) + (l.w end-of-chain at (generic-work-offset in-buf tie shadow end-of-chain)) + (nop!) + (nop!) + (b.z end-of-chain tie-generic-next-chain-entry :delay (nop!)))) + ;; End of chain, or out of DMA buffer. + (asm-block convert-done + (label tie-generic-convert-done) + (m v0 r0) + (l.d ra at (generic-work-offset storage data 0)) + (l.q gp at (generic-work-offset storage data 7)) + (l.q s5 at (generic-work-offset storage data 6)) + (l.q s4 at (generic-work-offset storage data 5)) + (l.q s3 at (generic-work-offset storage data 4)) + (l.q s2 at (generic-work-offset storage data 3)) + (l.q s1 at (generic-work-offset storage data 2)) + (l.q s0 at (generic-work-offset storage data 1)) + (jr ra :delay (add.i sp sp 128)) + (nop!) + (nop!) + (nop!)) + + ) + + (defun generic-tie-convert-proc () + "Expand the fragment described by generic-tie-control into the GSF buffer: the index and kick + list, the interpolation job, and one 32-byte vertex per point with its position, normal, + coordinate pair and palette color. Leaves the environment tint and the VU1 entry selection in + the shared constant block." + (declare (asm-func none) (allow-saved-regs)) + ;; One fragment, one instance, and the only expensive thing the Generic TIE path does. Nothing + ;; here is on the fast path of the game; the point of the code is to be a faithful expansion, and + ;; the interesting part is the division of labour with VU0. + ;; + ;; TIE stores a fragment's vertices in two classes. Base points exist at every level of detail; + ;; interpolated points exist only because an edge was subdivided, and each one carries the + ;; displacement that puts it back on that edge as the instance recedes. Both are int16 positions + ;; and packed coordinate pairs, and both have to become float quadwords before GENERIC can use + ;; them, so the EE widens them with packed interleaves and shifts and crosses them into VU0 four + ;; at a time. VU0 stages them into three arrays sized from the two counts, transforms them by the + ;; instance matrix, and hands them back; the EE writes the results out and fetches each vertex's + ;; color from the palette while it waits. + ;; + ;; Everything is pipelined one group of four behind VU0, which is why almost every loop body + ;; starts with a call and ends by staging the group the *next* call will work on, and why each + ;; class of point ends with a drain entry that has nothing to stage. + ;; + ;; This entry saves on the stack. The fused loop in generic-tie-convert saves the same + ;; registers into the scratchpad work area instead, which is the only real difference + ;; between the two. + (asm-block save-registers + (add.i sp sp -112) + (s.d ra sp) + (s.q s1 sp 16) + (s.q s2 sp 32) + (s.q s3 sp 48) + (s.q s4 sp 64) + (s.q s5 sp 80) + (s.q gp sp 96)) + ;; Everything about the fragment now in scratchpad is described by generic-tie-control, + ;; which the chain parser filled in. Read all of it at once - the loads are two cycles + ;; apart and there is nothing else to do with the gaps - and pick up the counts out of the + ;; fragment header. An instance-only chain entry needs none of the expansion that follows, + ;; because the model is still sitting in the work buffer from last time, so it jumps + ;; straight to the transform. + (rlet ((control :reg t2 :type generic-tie-control) + (out-buf :reg a0 :type gsf-buffer) + (header :reg t1 :type generic-tie-header) + (instance-mtx :reg a3 :type generic-tie-matrix) + (point-src :reg t3) + (index-src :reg t5) + (kick-src :reg t6) + (normal-src :reg t4) + (color-index-src :reg v1) + (spare :reg v1) ;; the control cursors this pass does not need land here + (palette :reg t0) + (entry-kind :reg a1) + (num-bps :reg a1) + (num-vtxs :reg a2) + (instance-only :reg a2) + (num-ips :reg a2) + (num-dps :reg t8)) + ;; Three of the fifteen control words are read into the same register and thrown away: + ;; the shader, run-control and interpolation cursors belong to the packet builder and to + ;; generic-interp-dproc, not to this pass. + (asm-block load-fragment-tables + (lui at #x7000) + (add.i control at (generic-work-offset in-buf tie control)) + (l.w out-buf at (generic-work-offset in-buf tie shadow out-buf)) + (nop!) + (l.w entry-kind (-> control tie-type)) + (nop!) + (l.w spare (-> control ptr-shaders)) + (nop!) + (l.w spare (-> control ptr-runctrl)) + (nop!) + (l.w point-src (-> control ptr-verts)) + (nop!) + (l.w header (-> control ptr-generic)) + (nop!) + (l.w spare (-> control ptr-interp)) + (nop!) + (l.w index-src (-> control ptr-dps)) + (nop!) + (l.w kick-src (-> control ptr-kicks)) + (nop!) + (l.w normal-src (-> control ptr-normals)) + (nop!) + (l.w instance-mtx (-> control ptr-mtxs)) + (nop!) + (l.w color-index-src (-> control ptr-cinds)) + (nop!) + (l.w palette (-> control ptr-palette)) + (nop!) + (l.hu num-vtxs (-> header gsf-header num-vtxs)) + (nop!) + (l.hu num-dps (-> header gsf-header num-dps)) + (add.i instance-only entry-kind -2) + (l.bu num-bps (-> header num-bps)) + (b.z instance-only tie-generic-proc-transform-fragment :delay (l.bu num-ips (-> header num-ips))))) + ;; Divide the GSF buffer up for this fragment and publish the divisions in its info block, + ;; so that neither the effect processors nor the packet builder has to work them out again: + ;; the index/kick list first, the expanded vertices after it rounded up to a quadword, and + ;; the environment-map attributes after those. gsf-header is copied across whole. + ;; + ;; The tail of the block builds the two lane masks the kick expansion needs. 513 grown into + ;; 0x8040201008040201 puts one distinct bit in each byte lane; 257 grown the same way puts + ;; 01 in all sixteen. See generic-tie-decompress for what they are for - the loop that + ;; follows is that function inlined. + ;; + ;; ra, gp, t7 through t9 and s2 through s5 are address arithmetic and mask temporaries here + ;; and change meaning every few instructions, so they keep their physical names. + (rlet ((out-buf :reg a0 :type gsf-buffer) + (header :reg t1 :type generic-tie-header) + (num-dps :reg t8) + (ik-bytes :reg t7) + (ik-out :reg t7) + (verts-out :reg t9) + (header-quad :reg ra :class i128) + (fx-out :reg ra)) + (asm-block publish-draw-point-arrays + (add ik-bytes num-dps num-dps) + (add.i t9 r0 -16) + (add.i ra ik-bytes 15) + (add.i ik-out out-buf 32) ;; the work area starts after info and header + (and t9 ra t9) + (l.q header-quad (-> header gsf-header)) + (add t9 t7 t9) + (s.q header-quad (-> out-buf header)) + (sll ra t8 5) + (s.w ik-out (-> out-buf info ptr-iks)) + (add fx-out verts-out ra) + (s.w verts-out (-> out-buf info ptr-verts)) + (add.i t8 t8 7) + (s.w fx-out (-> out-buf info ptr-fx)) + (sra t9 t8 3) + (nop!) + (add.i t8 r0 2) + (nop!) + (mult3 gp t8 t9) + (m ra t6) + (m t6 t8) + (m t7 t7) + (add.i t8 r0 513) + (add.i t9 r0 257) + (sll s4 t8 18) + (sll s5 t9 16) + (or t8 t8 s4) + (or t9 t9 s5) + (dsll32 s4 t8 4) + (dsll32 s5 t9 0) + (or t8 t8 s4) + (or t9 t9 s5) + (pcpyld t8 t8 t8) + (mmi-nop!) + (pcpyld t9 t9 t9) + (mmi-nop!) + (add gp gp ra) + (mmi-nop!) + (l.hu s5 ra) + (add ra ra t6) + (nop!) + (m t5 t5) + (nop!) + (pextlb s5 s5 s5) + (b tie-generic-proc-expand-kick-group :delay (pextlb s4 s5 s5)))) + ;; generic-tie-decompress, inlined: sixteen draw points per pass, each one an index byte + ;; married to a kick bit that has been spread out to a whole byte. The loop is rotated, so + ;; the second output quadword of a pass is stored at the top of the next one. + ;; + ;; The pass count is generous, and why it is written this way is not established. Sixteen draw + ;; points are finished per pass, but the bound steps one halfword of kick bits per pass across + ;; ceil(num-dps / 8) halfwords, so it runs about twice as many passes as there are draw points and + ;; writes off the end of the list. It cannot get far: + ;; the overshoot is at most another 2 * num-dps bytes, the vertex array immediately behind the + ;; list is 32 bytes per vertex against the list's 2 per draw point, and the vertex array is + ;; filled a few hundred instructions later regardless. So the spill stays inside the buffer and + ;; inside data that is about to be overwritten. + (asm-block interleave-indices-and-kicks + (label tie-generic-proc-next-kick-group) + (add.i t5 t5 16) + (nop!) + (add ra ra t6) + (add.i t7 t7 32) + (pextlb s4 s4 s4) + (s.q s5 t7 -16) + (label tie-generic-proc-expand-kick-group) + (pextlb s5 s4 s4) + (mmi-nop!) + (and.q s5 s5 t8) + (mmi-nop!) + (pceqb s4 s5 t8) + (l.q s5 t5) + (and.q s2 s4 t9) + (mmi-nop!) + (pextlb s4 s2 s5) + (l.hu s3 ra) + (pextub s5 s2 s5) + (s.q s4 t7) + (b.ne ra gp tie-generic-proc-next-kick-group :delay (pextlb s4 s3 s3)) + (nop!) + (s.q s5 t7 16)) + ;; Leave a job behind for generic-interp-dproc if this fragment has any subdivision + ;; vertices: they occupy num-ips slots starting right after the base points, and the blend + ;; data they need is the interpolation table in the fragment. A fragment with none clears + ;; the pointer instead, which is how the processor knows to do nothing. + (rlet ((interp-job :reg t5 :type generic-interp-job) + (control :reg t2 :type generic-tie-control) + (interp-data :reg t2) + (num-bps :reg a1) + (num-ips :reg a2)) + (asm-block publish-interp-job + (m interp-job r0) + (b.z num-ips tie-generic-proc-no-interp-job :delay (nop!)) + (add.i t6 num-ips 7) + (add.i interp-job at (generic-work-offset in-buf tie interp-job)) + (nop!) + (l.w interp-data (-> control ptr-interp)) + (sra t6 t6 3) + (s.h r0 (-> interp-job job-type)) + (nop!) + (s.h num-bps (-> interp-job first)) + (nop!) + (s.h num-ips (-> interp-job num)) + (nop!) + (s.w t2 (-> interp-job ptr-data)) + (nop!) + (s.w interp-job at (generic-work-offset saves ptr-interp-job)) + (b tie-generic-proc-interp-job-ready :delay (nop!)) + (label tie-generic-proc-no-interp-job) + (nop!) + (s.w r0 at (generic-work-offset saves ptr-interp-job)) + (nop!))) + ;; Hand the fragment's normals to VU0 four at a time. Each one is three signed bytes, so a + ;; quadword of them is four normals; pextub/pextlb move each byte into the top of a + ;; halfword and pextuh/pextlh into the top of a word, and then one arithmetic shift by 19 + ;; leaves the byte scaled by 32 with its sign intact - which the VU reads as 1.12 fixed + ;; point, giving a normal in [-1, 1). Two shifts and two interleaves per four normals, and + ;; no multiplies. + ;; + ;; The count is base points plus interpolated points rounded up to four, so the last pass + ;; can read a little past the table. vi08 and vi09 carry the two counts into VU0, which + ;; sizes its staging arrays from them; vi09 is written twice because the first write is too + ;; close to the call for the transfer to have landed. + (rlet ((normal-src :reg t4) + (normal-cursor :reg t2) + (normal-end :reg t4) + (num-bps :reg a1) + (num-ips :reg a2)) + (asm-block stage-normals + (label tie-generic-proc-interp-job-ready) + (add t5 num-bps num-ips) + (m normal-cursor normal-src) + (add.i t4 t5 3) + (ctc2.i vi9 num-ips) + (sra t5 t4 2) + (l.q t4 normal-cursor) + (sll t5 t5 4) + (add.i normal-cursor normal-cursor 16) + (pextub t7 t4 r0) + (ctc2.i vi8 num-bps) + (pextlb t6 t4 r0) + (ctc2.i vi9 num-ips) + (add normal-end normal-cursor t5) + (callms GENERIC-VU0-TIE-SETUP) + (label tie-generic-proc-stage-normal-group) + (pextuh t5 t7 r0) + (mmi-nop!) + (pextlh t7 t7 r0) + (mmi-nop!) + (pextuh t8 t6 r0) + (mmi-nop!) + (pextlh t6 t6 r0) + (mmi-nop!) + (sra.w t9 t6 19) + (l.q t6 normal-cursor) + (sra.w t8 t8 19) + (m.ni vf5 t9) + (sra.w t7 t7 19) + (m.ni vf6 t8) + (sra.w t5 t5 19) + (m.ni vf7 t7) + (pextub t7 t6 r0) + (m.ni vf8 t5) + (pextlb t6 t6 r0) + (mmi-nop!) + (nop!) + (callms GENERIC-VU0-TIE-NORMALS) + (b.ne normal-cursor normal-end tie-generic-proc-stage-normal-group :delay (add.i normal-cursor normal-cursor 16)) + (nop!))) + ;; Now the positions, again four at a time. A base point is sixteen bytes: three signed + ;; 16-bit coordinates, an output offset, the texture coordinate pair, and a second output + ;; offset. The doubleword load takes the position and the word load at +8 takes the + ;; coordinates; the shift by 10 leaves each coordinate multiplied by 64, which is the scale + ;; the instance matrix is built to undo. + ;; + ;; The loop is software pipelined against VU0: the call at the top of the body works on the + ;; group staged by the previous iteration while this one crosses the next group over. That + ;; is why the last group is staged outside the loop and why there is a call after it. + (rlet ((point-src :reg t3) + (point-cursor :reg t2) + (num-bps :reg a1) + (remaining :reg t4)) + (asm-block stage-base-points + (m point-cursor point-src) + (b.z num-bps tie-generic-proc-stage-interp-pair :delay (add.i remaining num-bps -4)) + (b.le remaining r0 tie-generic-proc-base-point-tail :delay (nop!)) + (m point-cursor point-src) + (l.d t5 t3) + (add.i remaining num-bps -8) + (l.d t6 t3 16) + (pextlh t7 t5 r0) + (l.d t5 t3 32) + (pextlh ra t6 r0) + (l.d t6 t3 48) + (pextlh t8 t5 r0) + (l.wu t5 t3 8) + (pextlh t9 t6 r0) + (l.wu t6 t3 24) + (sra.w gp t7 10) + (l.wu t7 t3 40) + (sra.w ra ra 10) + (l.wu t3 t3 56) + (sra.w t8 t8 10) + (m.ni vf1 gp) + (sra.w t9 t9 10) + (m.ni vf2 ra) + (add.i point-cursor point-cursor 64) + (m.ni vf3 t8) + (nop!) + (m.ni vf4 t9) + (nop!) + (m.ni vf13 t5) + (nop!) + (m.ni vf14 t6) + (nop!) + (m.ni vf15 t7) + (b.le t4 r0 tie-generic-proc-stage-last-base-points :delay (m.ni vf16 t3)) + (label tie-generic-proc-stage-base-point-group) + (callms GENERIC-VU0-TIE-BASE-POINTS) + (l.d t3 point-cursor) + (add.i t4 t4 -4) + (l.d t5 point-cursor 16) + (pextlh t8 t3 r0) + (l.d t3 point-cursor 32) + (pextlh t9 t5 r0) + (l.d t5 point-cursor 48) + (pextlh t6 t3 r0) + (l.wu t3 point-cursor 8) + (pextlh t7 t5 r0) + (l.wu t5 point-cursor 24) + (sra.w ra t8 10) + (l.wu t8 point-cursor 40) + (sra.w t9 t9 10) + (l.wu gp point-cursor 56) + (sra.w t6 t6 10) + (m.ni vf1 ra) + (sra.w t7 t7 10) + (m.ni vf2 t9) + (add.i point-cursor point-cursor 64) + (m.ni vf3 t6) + (nop!) + (m.ni vf4 t7) + (nop!) + (m.ni vf13 t3) + (nop!) + (m.ni vf14 t5) + (nop!) + (m.ni vf15 t8) + (b.gt t4 r0 tie-generic-proc-stage-base-point-group :delay (m.ni vf16 gp)) + (label tie-generic-proc-stage-last-base-points) + (callms GENERIC-VU0-TIE-BASE-POINTS) + (nop!))) + ;; The four-wide pipeline does not care that base points and interpolated points are + ;; different sizes, so the group that straddles the boundary between them is staged by a + ;; VU0 entry written for that exact shape. num-bps modulo four picks it: one base point + ;; plus one interpolated point, two plus one, three on their own, or - when the remainder + ;; is zero, or when the fragment has no interpolated points at all - one more plain group + ;; of four. + ;; + ;; The last case reads up to three points past the end of the array when the count is not a + ;; multiple of four. VU0 transforms them and the write loop, which counts in fours as well, + ;; stores them; both overruns land inside the GSF buffer's slack. + (rlet ((point-cursor :reg t2) + (num-bps :reg a1) + (num-ips :reg a2)) + (asm-block stage-boundary-group + (label tie-generic-proc-base-point-tail) + (b.z num-ips tie-generic-proc-stage-final-base-group :delay (and.i t3 num-bps 3)) + (b.z t3 tie-generic-proc-stage-final-base-group :delay (add.i t3 t3 -1)) + (b.z t3 tie-generic-proc-stage-tail-1bp-1ip :delay (add.i t3 t3 -1)) + (b.z t3 tie-generic-proc-stage-tail-2bp-1ip :delay (add.i t3 t3 -1)) + (b tie-generic-proc-stage-tail-3bp :delay (nop!)) + (label tie-generic-proc-stage-tail-1bp-1ip) + (nop!) + (l.d t3 point-cursor) + (nop!) + (l.d t4 point-cursor 16) + (pextlh t3 t3 r0) + (l.d t6 point-cursor 24) + (pextlh t4 t4 r0) + (l.wu t5 point-cursor 8) + (pextlh t6 t6 r0) + (l.wu t7 point-cursor 32) + (nop! :count 4) + (sra.w t3 t3 10) + (m.ni vf13 t5) + (sra.w t4 t4 10) + (m.ni vf14 t7) + (sra.w t5 t6 10) + (m.ni vf1 t3) + (nop!) + (m.ni vf2 t4) + (add.i point-cursor point-cursor 40) + (m.ni vf3 t5) + (b tie-generic-proc-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-1BP-1IP)) + (label tie-generic-proc-stage-tail-2bp-1ip) + (nop!) + (l.d t4 point-cursor) + (nop!) + (l.d t3 point-cursor 16) + (pextlh t8 t4 r0) + (l.d t5 point-cursor 32) + (pextlh t4 t3 r0) + (l.d t3 point-cursor 40) + (pextlh t5 t5 r0) + (l.wu t6 point-cursor 8) + (pextlh t7 t3 r0) + (l.wu t3 point-cursor 24) + (sra.w t9 t8 10) + (l.wu t8 point-cursor 48) + (sra.w t4 t4 10) + (m.ni vf13 t6) + (sra.w t5 t5 10) + (m.ni vf1 t9) + (sra.w t6 t7 10) + (m.ni vf2 t4) + (add.i point-cursor point-cursor 56) + (m.ni vf3 t5) + (nop!) + (m.ni vf4 t6) + (nop!) + (m.ni vf14 t3) + (nop!) + (m.ni vf15 t8) + (b tie-generic-proc-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-2BP-1IP)) + (label tie-generic-proc-stage-tail-3bp) + (nop!) + (l.d t4 point-cursor) + (nop!) + (l.d t3 point-cursor 16) + (pextlh t7 t4 r0) + (l.d t4 point-cursor 32) + (pextlh t5 t3 r0) + (l.wu t3 point-cursor 8) + (pextlh t6 t4 r0) + (l.wu t4 point-cursor 24) + (sra.w t7 t7 10) + (l.wu t8 point-cursor 40) + (sra.w t5 t5 10) + (m.ni vf13 t3) + (sra.w t3 t6 10) + (m.ni vf1 t7) + (nop!) + (m.ni vf2 t5) + (add.i point-cursor point-cursor 48) + (m.ni vf3 t3) + (nop!) + (m.ni vf14 t4) + (nop!) + (m.ni vf15 t8) + (b tie-generic-proc-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-3BP)) + (label tie-generic-proc-stage-final-base-group) + (nop!) + (l.d t3 point-cursor) + (nop!) + (l.d t4 point-cursor 16) + (pextlh t7 t3 r0) + (l.d t3 point-cursor 32) + (pextlh t8 t4 r0) + (l.d t4 point-cursor 48) + (pextlh t5 t3 r0) + (l.wu t3 point-cursor 8) + (pextlh t6 t4 r0) + (l.wu t4 point-cursor 24) + (sra.w t9 t7 10) + (l.wu t7 point-cursor 40) + (sra.w t8 t8 10) + (l.wu ra point-cursor 56) + (sra.w t5 t5 10) + (m.ni vf1 t9) + (sra.w t6 t6 10) + (m.ni vf2 t8) + (add.i point-cursor point-cursor 64) + (m.ni vf3 t5) + (nop!) + (m.ni vf4 t6) + (nop!) + (m.ni vf13 t3) + (nop!) + (m.ni vf14 t4) + (nop!) + (m.ni vf15 t7) + (nop!) + (m.ni vf16 ra) + (b.z num-ips tie-generic-proc-staging-done :delay (callms GENERIC-VU0-TIE-BASE-POINTS)))) + ;; Whatever interpolated points are left go two at a time, because each one is twenty-four + ;; bytes: the vector the morph weight scales, the vector it is added to, and the coordinate + ;; pair. Three of them fill two quadwords, which is why the stride is 48 and not 32. + (rlet ((point-cursor :reg t2) + (num-ips :reg a2) + (remaining :reg t3)) + (asm-block stage-interp-points + (label tie-generic-proc-stage-interp-pair) + (nop!) + (l.d t4 point-cursor) + (add.i remaining num-ips -2) + (l.d t5 point-cursor 8) + (pextlh t4 t4 r0) + (l.d t6 point-cursor 24) + (pextlh t5 t5 r0) + (l.d t7 point-cursor 32) + (pextlh t6 t6 r0) + (l.wu t8 point-cursor 16) + (pextlh t7 t7 r0) + (l.wu t9 point-cursor 40) + (sra.w t4 t4 10) + (m.ni vf13 t8) + (sra.w t5 t5 10) + (m.ni vf14 t9) + (sra.w t6 t6 10) + (m.ni vf1 t4) + (sra.w t4 t7 10) + (m.ni vf2 t5) + (add.i point-cursor point-cursor 48) + (m.ni vf3 t6) + (b.le remaining r0 tie-generic-proc-stage-last-interp-pair :delay (m.ni vf4 t4)) + (label tie-generic-proc-stage-interp-pair-loop) + (callms GENERIC-VU0-TIE-INTERP-POINTS) + (l.d t4 point-cursor) + (add.i remaining remaining -2) + (l.d t5 point-cursor 8) + (pextlh t4 t4 r0) + (l.d t6 point-cursor 24) + (pextlh t5 t5 r0) + (l.d t7 point-cursor 32) + (pextlh t6 t6 r0) + (l.wu t8 point-cursor 16) + (pextlh t7 t7 r0) + (l.wu t9 point-cursor 40) + (sra.w t4 t4 10) + (m.ni vf13 t8) + (sra.w t5 t5 10) + (m.ni vf14 t9) + (sra.w t6 t6 10) + (m.ni vf1 t4) + (sra.w t4 t7 10) + (m.ni vf2 t5) + (add.i point-cursor point-cursor 48) + (m.ni vf3 t6) + (b.gt remaining r0 tie-generic-proc-stage-interp-pair-loop :delay (m.ni vf4 t4)) + (label tie-generic-proc-stage-last-interp-pair) + (callms GENERIC-VU0-TIE-INTERP-POINTS) + (nop!) + (label tie-generic-proc-staging-done) + (nop!) + (nop!))) + ;; The transform starts here, and so does the one piece of per-fragment color work the EE + ;; still has to do itself: the environment map's tint. It is the fragment's authored tint, + ;; scaled by this instance's fade value and then by the time-of-day environment color. + ;; Both scalings are packed halfword multiplies whose products come back out of the + ;; accumulator halved into HI and LO, so pmfhl.lh gathers them and a shift by seven both + ;; discards the 0..255 range and keeps the rounding cheap. The result goes into all four + ;; lanes of the shared envmap color constant, where prepare-dma-double will find it. + ;; + ;; The other thing decided here is which VU1 entry the packet will call. The instance pass left + ;; its near-classification reject flags in the second word of the instance record's fifth + ;; quadword; an instance carrying any of them would have gone to the near renderer, which clips, + ;; so it takes the clipping entry here and everything else takes the direct one. Patching the + ;; immediate of the MSCAL template is the whole of it. + (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) + (out-buf :reg a0 :type gsf-buffer) + (header :reg t1 :type generic-tie-header) + (tint :reg t1) + (vtx-out :reg t1) + (vtx-count :reg t3) + (cind-cursor :reg t4) + (color-index-src :reg v1) + (num-bps :reg a1) + (vu1-entry :reg t6)) + (asm-block build-envmap-tint + (label tie-generic-proc-transform-fragment) + (m vtx-count num-bps) + (l.w t6 (-> instance-mtx fog data 0)) + (m cind-cursor color-index-src) + (l.w tint (-> header tint-color)) + (add.i t5 at (generic-work-offset fx-buf work consts envmap colors)) + (s.w t6 at (generic-work-offset saves fade-val)) + (pextlb t2 r0 t1) + (l.q t1 (-> instance-mtx matrix vector 0)) + (pextlh t7 t6 t6) + (l.q t6 (-> instance-mtx matrix vector 1)) + (pextlh t8 t7 t7) + (l.q t7 (-> instance-mtx matrix vector 2)) + (pmulth r0 t2 t8) + (l.q t2 (-> instance-mtx matrix vector 3)) + (nop!) + (m.ni vf1 t1) + (nop!) + (m.ni vf2 t6) + (nop!) + (m.ni vf3 t7) + (nop!) + (m.ni vf4 t2) + (callms GENERIC-VU0-TIE-BASE-START) + (m t1 t0) + (pextlw t2 t1 t1) + (l.w vtx-out (-> out-buf info ptr-verts)) + (pcpyld t2 t2 t2) + (l.w t8 at (generic-work-offset saves time-of-day-color)) + (pmfhl.lh t7) + (l.w vu1-entry (-> instance-mtx fog data 1)) + (pextlb t8 r0 t8) + (mmi-nop!) + (psrlh t7 t7 7) + (mmi-nop!) + (pmulth r0 t7 t8) + (mmi-nop!) + (b.nz vu1-entry tie-generic-proc-use-clipping-entry :delay (mmi-nop!)) + (nop!) + (add.i vu1-entry r0 GENERIC-VU1-DIRECT) + (b tie-generic-proc-entry-selected :delay (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag))) + (label tie-generic-proc-use-clipping-entry) + (nop!) + (add.i vu1-entry r0 GENERIC-VU1-CLIPPED) + (nop!) + (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag)))) + ;; Collect four transformed base points from VU0 and write them out as four gsf-vertex + ;; records, 32 bytes each: position and coordinate pair in the first quadword, normal in + ;; the second, and then the palette color overwriting the normal's unused w. + ;; + ;; The color is a plain lookup. Each vertex carries a one-byte palette index; shifting it + ;; left by two and adding the broadcast palette base gives four addresses in one packed + ;; add, and the four loads are spread through the dependent VU0 crossings so the load + ;; delays cost nothing. Because the whole loop is one iteration behind VU0, the stores at + ;; the top of the body belong to the previous group. + ;; + ;; t5 through t9, gp, ra and s1 through s5 are the shuttles carrying quadwords between VU0 + ;; and memory. Their meaning changes every few instructions and they keep physical names. + (rlet ((vtx-out :reg t1) + (palette-lanes :reg t2 :class i128) + (vtx-count :reg t3) + (cind-cursor :reg t4)) + (asm-block write-base-vertices + (label tie-generic-proc-entry-selected) + (pmfhl.lh t6) + (mmi-nop!) + (nop!) + (mmi-nop!) + (psrlh t6 t6 7) + (mmi-nop!) + (ppacb t6 r0 t6) + (mmi-nop!) + (nop!) + (l.w t7 cind-cursor) + (add.i cind-cursor cind-cursor 4) + (s.w t6 t5) + (pextlb t7 r0 t7) + (s.w t6 t5 4) + (pextlh t7 r0 t7) + (s.w t6 t5 8) + (sll.w t7 t7 2) + (s.w t6 t5 12) + (add.w t9 t7 palette-lanes) + (mmi-nop!) + (srl32 gp t9 0) + (m.ni t7 vf17) + (pcpyud t5 t9 r0) + (mmi-nop!) + (srl32 t6 t5 0) + (m.ni ra vf13) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (mmi-nop!) + (nop!) + (m.ni t8 vf14) + (nop!) + (l.wu t9 t9) + (nop!) + (l.wu gp gp) + (nop!) + (l.wu t5 t5) + (nop!) + (m.ni s3 vf18) + (nop!) + (l.wu t6 t6) + (nop!) + (s.q ra vtx-out) + (add.i vtx-count vtx-count -4) + (nop! :count 3) + (b.le vtx-count r0 tie-generic-proc-drain-base-vertices :delay (nop!)) + (label tie-generic-proc-write-base-vertex-group) + (callms GENERIC-VU0-TIE-BASE-XFORM) + (s.q t7 vtx-out 16) + (nop!) + (s.w t9 vtx-out 28) + (nop!) + (s.q t8 vtx-out 32) + (nop!) + (s.q s3 vtx-out 48) + (nop!) + (s.w gp vtx-out 60) + (nop! :count 7) + (mmi-nop!) + (nop!) + (l.w t8 cind-cursor) + (add.i cind-cursor cind-cursor 4) + (m.ni t7 vf19) + (pextlb t9 r0 t8) + (m.ni t8 vf20) + (pextlh t9 r0 t9) + (m.ni gp vf15) + (sll.w ra t9 2) + (m.ni t9 vf16) + (add.w s3 ra palette-lanes) + (s.q t7 vtx-out 80) + (srl32 s2 s3 0) + (m.ni t7 vf17) + (pcpyud ra s3 r0) + (s.q gp vtx-out 64) + (srl32 s5 ra 0) + (m.ni s4 vf13) + (nop!) + (s.q t8 vtx-out 112) + (nop!) + (s.w t5 vtx-out 92) + (nop!) + (s.w t6 vtx-out 124) + (nop!) + (s.q t9 vtx-out 96) + (nop!) + (l.wu t9 s3) + (nop!) + (l.wu gp s2) + (nop!) + (m.ni t8 vf14) + (add.i vtx-out vtx-out 128) + (m.ni s3 vf18) + (add.i vtx-count vtx-count -4) + (l.wu t5 ra) + (nop!) + (s.q s4 vtx-out) + (b.gt vtx-count r0 tie-generic-proc-write-base-vertex-group :delay (l.wu t6 s5)))) + ;; The drain entry publishes the group still inside the pipeline. Four of its results have + ;; to be stored straight out of the VU registers rather than through the usual crossing, + ;; because there is no following group whose crossings could carry them. + (rlet ((vtx-out :reg t1) + (num-ips :reg a2)) + (asm-block drain-base-vertices + (label tie-generic-proc-drain-base-vertices) + (callms GENERIC-VU0-TIE-BASE-DRAIN) + (s.q t7 vtx-out 16) + (nop!) + (s.w t9 vtx-out 28) + (nop!) + (s.q t8 vtx-out 32) + (nop!) + (s.q s3 vtx-out 48) + (nop!) + (s.w gp vtx-out 60) + (nop!) + (vnop) + (nop!) + (s.vf vf19 vtx-out 80) + (nop!) + (s.vf vf15 vtx-out 64) + (nop!) + (s.vf vf16 vtx-out 96) + (nop!) + (s.vf vf20 vtx-out 112) + (nop!) + (s.w t5 vtx-out 92) + (nop!) + (s.w t6 vtx-out 124) + (b.z num-ips tie-generic-proc-done :delay (nop!)))) + ;; Interpolated points next, and they take one of two paths. The instance's morph quadword + ;; holds the pair of color weights that trade off between a vertex's own palette entry and + ;; the average of the two entries it blends toward; the second weight arrives doubled, + ;; because summing two neighbours instead of averaging them is one instruction cheaper, so + ;; it is halved here. If it comes out zero nothing is blending - the instance is at the near + ;; edge of its LOD band - and the cheaper path, which reads only the second of each point's + ;; two vectors and only the vertex's own color, is taken instead. + ;; + ;; The blended path is the interesting one. Both weights are broadcast into every halfword + ;; lane, and the blend is pmulth followed by pmaddh - four channels of two colors weighted + ;; and summed in two instructions - with the sum coming back out of HI and LO through + ;; pmfhl.lh. The weights add up to 256 rather than to one, so the shift by eight that + ;; follows is the normalization. + ;; + ;; Each interpolated point's colors are four bytes: its own index and the two it blends + ;; toward. They follow the base points' one-byte indices, rounded up to four. + (asm-block start-interp-vertices + ;; Everything the rest of the block needs is set up here, out of the instance record + ;; and the GSF info block. After this the same registers carry broadcast weights and + ;; packed palette addresses, so they go back to physical names. + (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) + (cind-cursor :reg a3) + (out-buf :reg a0 :type gsf-buffer) + (palette :reg t0) + (palette-base :reg t1) + (color-index-src :reg v1) + (num-bps :reg a1) + (num-ips :reg a2) + (vtx-base :reg t2)) + (m palette-base palette) + (l.vf vf29 (-> instance-mtx morph)) + (m palette color-index-src) + (l.s f0 (-> instance-mtx morph z)) + (add.i palette num-bps 3) + (l.s f2 (-> instance-mtx morph w)) + (add.i cind-cursor r0 -4) + (nop!) + (cvt.w.s f0 f0) + (and cind-cursor palette cind-cursor) + (cvt.w.s f2 f2) + (l.w vtx-base (-> out-buf info ptr-verts)) + (m a0 f0) + (m palette num-ips) + (m t3 f2) + (sll a2 num-bps 5) + (sra a1 t3 1) + (add cind-cursor color-index-src cind-cursor)) + (b.z a1 tie-generic-proc-interp-without-blend :delay (add a2 t2 a2)) + (pcpyh v1 a0) + (mmi-nop!) + (pcpyld v1 v1 v1) + (s.h v1 at (generic-work-offset in-buf tie interp-job morph-z)) + (pcpyh a0 a1) + (s.h a0 at (generic-work-offset in-buf tie interp-job morph-w)) + (pcpyld a0 a0 a0) + (l.dr t3 a3) + (pextlw a1 t1 t1) + (l.dl t3 a3 7) + (pcpyld a1 a1 a1) + (l.dr t2 a3 8) + (pextlb t3 r0 t3) + (l.dl t2 a3 15) + (pextuh t1 r0 t3) + (mmi-nop!) + (pextlh t3 r0 t3) + (mmi-nop!) + (pextlb t4 r0 t2) + (mmi-nop!) + (pextuh t2 r0 t4) + (mmi-nop!) + (pextlh t5 r0 t4) + (mmi-nop!) + (sll.w t3 t3 2) + (mmi-nop!) + (sll.w t4 t1 2) + (callms GENERIC-VU0-TIE-MORPH-START) + (sll.w t6 t5 2) + (mmi-nop!) + (sll.w t5 t2 2) + (mmi-nop!) + (add.w t1 t3 a1) + (mmi-nop!) + (add.w t4 t4 a1) + (l.wu t3 t1) + (add.w t2 t6 a1) + (l.wu t9 t4) + (add.w t5 t5 a1) + (mmi-nop!) + (srl32 s5 t1 0) + (mmi-nop!) + (srl32 s4 t4 0) + (mmi-nop!) + (srl32 ra t2 0) + (add.i a2 a2 -128) + (srl32 t8 t5 0) + (add.i a3 a3 16) + (pcpyud t7 t1 r0) + (l.wu gp t2) + (pcpyud t6 t4 r0) + (l.wu s3 t5) + (pcpyud t4 t2 r0) + (l.wu s5 s5) + (pcpyud t5 t5 r0) + (l.wu s4 s4) + (pextlw t9 t9 t3) + (l.wu ra ra) + (pextlw t3 s3 gp) + (l.wu s3 t8) + (pextlw gp s4 s5) + (l.wu t8 t7) + (pextlw t7 s3 ra) + (l.wu ra t6) + (pextlb t6 r0 t9) + (l.wu t9 t4) + (pextlb t4 r0 gp) + (l.wu gp t5) + (pextlw ra ra t8) + (m.ni t5 vf17) + (pextlw t8 gp t9) + (mmi-nop!) + (pextlb t9 r0 ra) + (mmi-nop!) + (add.h t9 t4 t9) + (m.ni t4 vf13) + (pmulth r0 t9 a0) + (mmi-nop!) + (pextlb t3 r0 t3) + (s.q t5 a2 144) + (pmaddh r0 t6 v1) + (s.q t4 a2 128) + (pextlb t5 r0 t7) + (l.dr t1 a3) + (pextlb t7 r0 t8) + (l.dl t1 a3 7) + (pmfhl.lh t6) + (m.ni t4 vf18) + (add.h t5 t5 t7) + (l.dr t2 a3 8) + (psrlh t6 t6 8) + (l.dl t2 a3 15) + (ppacb t6 r0 t6) + (s.q t4 a2 176) + (srl32 t4 t6 0) + (add.i t0 t0 -4) + (pmulth r0 t5 a0) + (m.ni t5 vf14) + (pextlb t1 r0 t1) + (s.w t6 a2 156) + (pmaddh r0 t3 v1) + (s.w t4 a2 188) + (pextlb t3 r0 t2) + (s.q t5 a2 160) + (pextuh t2 r0 t1) + (mmi-nop!) + (pmfhl.lh t6) + (m.ni t5 vf19) + (pextlh t4 r0 t1) + (mmi-nop!) + (psrlh t1 t6 8) + (mmi-nop!) + (ppacb t6 r0 t1) + (s.q t5 a2 208) + (srl32 t1 t6 0) + (s.w t6 a2 220) + (pextuh t5 r0 t3) + (m.ni t6 vf15) + (pextlh t3 r0 t3) + (mmi-nop!) + (sll.w t4 t4 2) + (s.q t6 a2 192) + (sll.w t6 t2 2) + (mmi-nop!) + (sll.w t3 t3 2) + (mmi-nop!) + (sll.w t7 t5 2) + (mmi-nop!) + (add.w t2 t4 a1) + (m.ni t4 vf20) + (add.w t6 t6 a1) + (l.wu t5 t2) + (add.w t3 t3 a1) + (l.wu gp t6) + (b.le t0 r0 tie-generic-proc-drain-morphed-group :delay (add.w t7 t7 a1))) + ;; Four blending vertices per pass. Same shape as the base-point loop - four records out, the + ;; palette read as four packed addresses, everything one group behind VU0 - with the two-entry + ;; color blend folded into the same instruction stream. + ;; + ;; Unnamed on purpose, as above: this is the densest interleaving in the file and every general + ;; register is reused two or three times per pass. The only registers with a lifetime worth a + ;; name are a3, the color-index cursor, and a2, the vertex output cursor; both step by a fixed + ;; amount at the bottom of the loop. + (asm-block write-morphed-interp-vertices + (label tie-generic-proc-write-morphed-group) + (srl32 s4 t2 0) + (callms GENERIC-VU0-TIE-MORPH-XFORM) + (srl32 s1 t6 0) + (srl32 s3 t3 0) + (srl32 ra t7 0) + (add.i a2 a2 128) + (pcpyud t9 t2 r0) + (l.wu s5 t3) + (pcpyud t8 t6 r0) + (l.wu s2 t7) + (pcpyud t6 t3 r0) + (l.wu s4 s4) + (pcpyud t7 t7 r0) + (l.wu s1 s1) + (pextlw gp gp t5) + (l.wu s3 s3) + (pextlw t5 s2 s5) + (l.wu s2 ra) + (pextlw s5 s1 s4) + (l.wu ra t9) + (pextlw t9 s2 s3) + (l.wu s4 t8) + (pextlb t8 r0 gp) + (l.wu gp t6) + (pextlb t6 r0 s5) + (l.wu t7 t7) + (pextlw ra s4 ra) + (s.q t4 a2 112) + (pextlw t4 t7 gp) + (m.ni t7 vf17) + (pextlb ra r0 ra) + (s.w t1 a2 124) + (add.h t1 t6 ra) + (mmi-nop!) + (pmulth r0 t1 a0) + (m.ni t6 vf13) + (pextlb t1 r0 t5) + (s.q t7 a2 144) + (pmaddh r0 t8 v1) + (l.dr t2 a3 16) + (pextlb t5 r0 t9) + (s.q t6 a2 128) + (pextlb t4 r0 t4) + (l.dl t2 a3 23) + (pmfhl.lh t6) + (l.dr t3 a3 24) + (add.h t4 t5 t4) + (m.ni t7 vf18) + (psrlh t5 t6 8) + (l.dl t3 a3 31) + (ppacb t5 r0 t5) + (s.q t7 a2 176) + (srl32 t6 t5 0) + (add.i t0 t0 -4) + (pmulth r0 t4 a0) + (mmi-nop!) + (pextlb t4 r0 t2) + (m.ni t7 vf14) + (pmaddh r0 t1 v1) + (s.w t5 a2 156) + (pextlb t3 r0 t3) + (s.w t6 a2 188) + (pextuh t2 r0 t4) + (s.q t7 a2 160) + (pmfhl.lh t6) + (m.ni t1 vf16) + (pextlh t4 r0 t4) + (m.ni t5 vf19) + (psrlh t6 t6 8) + (s.q t1 a2 96) + (ppacb t6 r0 t6) + (s.q t5 a2 208) + (srl32 t1 t6 0) + (add.i a3 a3 16) + (pextuh t5 r0 t3) + (mmi-nop!) + (pextlh t7 r0 t3) + (m.ni t8 vf15) + (sll.w t3 t4 2) + (s.w t6 a2 220) + (sll.w t6 t2 2) + (s.q t8 a2 192) + (sll.w t8 t7 2) + (mmi-nop!) + (sll.w t7 t5 2) + (mmi-nop!) + (add.w t2 t3 a1) + (m.ni t4 vf20) + (add.w t6 t6 a1) + (l.wu t5 t2) + (add.w t3 t8 a1) + (l.wu gp t6) + (b.gt t0 r0 tie-generic-proc-write-morphed-group :delay (add.w t7 t7 a1))) + ;; Publish the last collapsing group. + (asm-block drain-morphed-interp-vertices + (label tie-generic-proc-drain-morphed-group) + (nop!) + (callms GENERIC-VU0-TIE-MORPH-DRAIN) + (nop!) + (s.q t4 a2 240) + (nop!) + (m v1 vf16) + (nop!) + (s.w t1 a2 252) + (b tie-generic-proc-done :delay (s.q v1 a2 224))) + ;; The unblended path. Only the second of each point's two vectors is transformed and only the + ;; vertex's own palette entry is read, so the four indices are four plain byte loads four bytes + ;; apart instead of a packed gather. + ;; + ;; No aliases below, and deliberately so: every register in this loop is a shuttle. a0 through + ;; a3, t0 through t7 and s4/s5 each carry a palette address, then a loaded color, then a + ;; quadword on its way out of VU0, within a dozen instructions, and there is no name that would + ;; be true for more than one of those. The cursors are v1 for the color indices and a2 for the + ;; output. + (asm-block write-authored-interp-vertices + (label tie-generic-proc-interp-without-blend) + (callms GENERIC-VU0-TIE-INTERP-START) + (nop!) + (nop!) + (s.h a0 at (generic-work-offset in-buf tie interp-job morph-z)) + (nop!) + (s.h a1 at (generic-work-offset in-buf tie interp-job morph-w)) + (nop! :count 17) + (l.bu v1 a3) + (nop!) + (l.bu a1 a3 4) + (nop!) + (l.bu a0 a3 8) + (sll v1 v1 2) + (l.bu t2 a3 12) + (sll a1 a1 2) + (nop!) + (sll t4 a0 2) + (nop!) + (sll t5 t2 2) + (m t2 vf17) + (add a0 v1 t1) + (add.i v1 a3 16) + (add t3 a1 t1) + (nop!) + (add t4 t4 t1) + (nop!) + (add a1 t5 t1) + (nop!) + (nop!) + (m.ni t6 vf13) + (nop! :count 5) + (m.ni a3 vf18) + (nop!) + (l.wu t7 a0) + (nop!) + (l.wu t3 t3) + (nop!) + (l.wu t4 t4) + (nop!) + (m.ni t5 vf14) + (nop!) + (l.wu a0 a1) + (add.i a1 t0 -4) + (s.q t6 a2) + (nop!) + (m.ni t0 vf19) + (nop!) + (s.q t2 a2 16) + (nop!) + (s.w t7 a2 28) + (b.le a1 r0 tie-generic-proc-drain-interp-without-blend :delay (s.q a3 a2 48))) + ;; Four more, until the interpolated points run out. Same allocation as the block above. + (asm-block write-authored-interp-vertices-loop + (label tie-generic-proc-interp-without-blend-loop) + (callms GENERIC-VU0-TIE-INTERP-XFORM) + (s.w t3 a2 60) + (nop!) + (s.q t5 a2 32) + (nop!) + (s.q t0 a2 80) + (nop!) + (s.w t4 a2 92) + (nop! :count 5) + (l.bu a3 v1) + (nop!) + (l.bu t0 v1 4) + (nop!) + (l.bu t2 v1 8) + (sll t3 a3 2) + (l.bu a3 v1 12) + (sll t4 t0 2) + (m t0 vf15) + (sll t5 t2 2) + (m.ni t2 vf20) + (sll t6 a3 2) + (m.ni a3 vf17) + (add t3 t3 t1) + (add.i v1 v1 16) + (add t4 t4 t1) + (m.ni t7 vf16) + (add t5 t5 t1) + (s.q t0 a2 64) + (add t0 t6 t1) + (s.q t2 a2 112) + (nop!) + (m.ni t2 vf13) + (nop!) + (s.w a0 a2 124) + (nop!) + (s.q t7 a2 96) + (nop!) + (m.ni t6 vf18) + (nop!) + (l.wu t7 t3) + (nop!) + (l.wu t3 t4) + (nop!) + (l.wu t4 t5) + (nop!) + (m.ni t5 vf14) + (add.i a2 a2 128) + (l.wu a0 t0) + (add.i a1 a1 -4) + (s.q t2 a2) + (nop!) + (m.ni t0 vf19) + (nop!) + (s.q a3 a2 16) + (nop!) + (s.w t7 a2 28) + (b.gt a1 r0 tie-generic-proc-interp-without-blend-loop :delay (s.q t6 a2 48))) + ;; Publish the last unblended group and the fragment is expanded. + (asm-block drain-authored-interp-vertices + (label tie-generic-proc-drain-interp-without-blend) + (nop!) + (callms GENERIC-VU0-TIE-INTERP-DRAIN) + (nop!) + (s.w t3 a2 60) + (nop!) + (s.q t5 a2 32) + (nop!) + (s.q t0 a2 80) + (nop!) + (s.w t4 a2 92) + (nop!) + (m v1 vf15) + (nop!) + (m.ni a1 vf20) + (nop!) + (m.ni a3 vf16) + (nop!) + (s.q v1 a2 64) + (nop!) + (s.q a1 a2 112) + (nop!) + (s.w a0 a2 124) + (nop!) + (s.q a3 a2 96)) + ;; One fragment expanded; the caller dispatches the effect processors. + (asm-block restore-and-return + (label tie-generic-proc-done) + (m v0 r0) + (l.d ra sp) + (l.q gp sp 96) + (l.q s5 sp 80) + (l.q s4 sp 64) + (l.q s3 sp 48) + (l.q s2 sp 32) + (l.q s1 sp 16) + (jr ra :delay (add.i sp sp 112)) + (nop!) + (nop!)) + + ) + + (defun generic-tie-upload-next () + "Decide what the next chain entry is, flip the scratchpad buffer it will land in, and start its + transfer without waiting. Sets shadow.tie-type to say which of the three shapes arrived, or + shadow.end-of-chain when there is nothing left." + (declare (asm-func none) (allow-saved-regs)) + ;; Three kinds of chain entry, and which one is next was decided by the prototype pass and smuggled + ;; into the unused VIF words of the DMA tags this block's transfer already delivered: + ;; + ;; another instance of the fragment we are already holding - only the transform, morph and + ;; palette indices need to arrive, and they go into the standalone instance buffer + ;; a new fragment, reusing the palette we already have - the block arrives with its palette area + ;; skipped, so the transfer starts partway into the buffer + ;; a new fragment with its own palette - the whole block arrives + ;; + ;; The first is chosen by control.next-instance holding a real address. Any main-memory address is + ;; above 64 KiB, so the exporter can leave a small marker there when a fragment's instance list is + ;; finished, and the test is a signed compare rather than a nonzero check. + ;; + ;; The flip is an xor in every case. The two input buffers are 3248 bytes apart and the first one + ;; starts at 768, whose bits do not overlap 3248, so xor and add come to the same thing; the + ;; instance buffer is reached the same way, through the xor cached in shadow.inst-xor. Doing it with + ;; xor rather than a compare and two moves is what keeps this on one path. + (rlet ((chain-addr :reg v1) ;; control.next-instance, then whichever address gets programmed + (next-model :reg a0) ;; control.next-model: zero means this was the last entry + (to-spr :reg a0 :type dma-bank-spr) + (chain-test :reg a1) ;; next-instance measured against the main-memory floor + (spad-dest :reg a1) ;; then: where in scratchpad the entry lands + (next-is-model :reg a2) + (instance-flip :reg a2) ;; then: shadow.inst-xor, then the address mask + (new-instance :reg a3) ;; the instance area inside the newly selected buffer + (entry-kind :reg a3) ;; then: the shadow.tie-type value being published + (instance-kind :reg a0)) ;; a0 again, holding that value for two instructions + ;; Both branches carry a load in their delay slot, and only one of the two loads survives on each + ;; path: the instance pointer on the instance path, the buffer pointer on the two model paths. + (asm-block choose-next-chain-entry + (lui at #x7000) + (nop!) + (l.w chain-addr at (generic-work-offset in-buf tie control next-instance)) + (lui chain-test 1) + (l.w next-model at (generic-work-offset in-buf tie control next-model)) + (sub chain-test chain-addr chain-test) + (l.w next-is-model at (generic-work-offset in-buf tie control next-is-model)) + (b.gt chain-test r0 tie-generic-upload-queue-next-instance + :delay (l.w chain-test at (generic-work-offset in-buf tie shadow ptr-inst))) + (b.nz next-is-model tie-generic-upload-queue-model-without-palette + :delay (l.w chain-addr at (generic-work-offset in-buf tie shadow ptr-buf)))) + ;; A fragment that brings its own palette: the transfer covers the whole block. + (asm-block queue-model-with-palette + (xor.i spad-dest chain-addr GENERIC-TIE-INPUT-FLIP) + (m chain-addr next-model) + (add.i new-instance spad-dest (offset-of generic-tie-input matrix-tag)) + (s.w spad-dest at (generic-work-offset in-buf tie shadow ptr-buf)) + (xor.i instance-flip new-instance (generic-work-offset in-buf tie inst-buf)) + (s.w new-instance at (generic-work-offset in-buf tie shadow ptr-inst)) + (add.i entry-kind r0 GENERIC-TIE-ENTRY-MODEL-AND-PALETTE) + ;; The matching instruction on the other path skips the palette area; here the + ;; destination is the start of the buffer, so the add is left in as a plain move. + (add spad-dest spad-dest r0) + (and.i instance-flip instance-flip #xffff) + (s.w entry-kind at (generic-work-offset in-buf tie shadow tie-type)) + (b.nz next-model tie-generic-upload-start-input-transfer + :delay (s.w instance-flip at (generic-work-offset in-buf tie shadow inst-xor))) + (add.i chain-addr r0 1) + (nop!) + (b tie-generic-upload-queued + :delay (s.w chain-addr at (generic-work-offset in-buf tie shadow end-of-chain)))) + ;; The same thing for a fragment that reuses the palette already in palette-buf. Only the + ;; published kind and the destination differ. + (asm-block queue-model-without-palette + (label tie-generic-upload-queue-model-without-palette) + (xor.i spad-dest chain-addr GENERIC-TIE-INPUT-FLIP) + (m chain-addr next-model) + (add.i new-instance spad-dest (offset-of generic-tie-input matrix-tag)) + (s.w spad-dest at (generic-work-offset in-buf tie shadow ptr-buf)) + (xor.i instance-flip new-instance (generic-work-offset in-buf tie inst-buf)) + (s.w new-instance at (generic-work-offset in-buf tie shadow ptr-inst)) + (add.i entry-kind r0 GENERIC-TIE-ENTRY-MODEL-ONLY) + (add.i spad-dest spad-dest (offset-of generic-tie-input model-tag)) + (and.i instance-flip instance-flip #xffff) + (s.w entry-kind at (generic-work-offset in-buf tie shadow tie-type)) + (b.nz next-model tie-generic-upload-start-input-transfer + :delay (s.w instance-flip at (generic-work-offset in-buf tie shadow inst-xor))) + (add.i chain-addr r0 1) + (nop!) + (b tie-generic-upload-queued + :delay (s.w chain-addr at (generic-work-offset in-buf tie shadow end-of-chain)))) + ;; Another instance of the fragment already in hand. Nothing about the model moves, so this + ;; alternates the instance area rather than the whole input buffer. + (asm-block queue-next-instance + (label tie-generic-upload-queue-next-instance) + (m chain-addr chain-addr) + (l.w instance-flip at (generic-work-offset in-buf tie shadow inst-xor)) + (add.i instance-kind r0 GENERIC-TIE-ENTRY-INSTANCE-ONLY) + (nop!) + (xor spad-dest spad-dest instance-flip) + (s.w instance-kind at (generic-work-offset in-buf tie shadow tie-type)) + (b tie-generic-upload-start-input-transfer + :delay (s.w spad-dest at (generic-work-offset in-buf tie shadow ptr-inst)))) + ;; Hand the chain to the toSPR channel and leave. Whoever wants to read the destination has to + ;; wait for the channel first. + (asm-block start-input-transfer + (label tie-generic-upload-start-input-transfer) + (lui at #x7000) + (ori instance-flip r0 #xffff) + (lui to-spr #x1000) + (nop!) + (ori to-spr to-spr #xd400) + (and spad-dest spad-dest instance-flip) + (add.i instance-flip r0 DMA-CHCR-START-CHAIN-TTE) + (s.w spad-dest (-> to-spr sadr)) + (s.w chain-addr (-> to-spr tadr)) + (s.w r0 (-> to-spr qwc)) + (s.w instance-flip (-> to-spr chcr))) + (asm-block return-nothing + (label tie-generic-upload-queued) + (nop!) + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!)))) + + (defun generic-tie-decompress () + "Interleave a fragment's draw-point indices and packed kick bits into the gsf-ik list GENERIC + draws from. a0 is the kick bitfield, a1 its stride in bytes, a2 the destination list, a3 the + number of strides to consume, and t0 the index bytes." + (declare (asm-func none) (allow-saved-regs)) + ;; The exporter ships the drawing order as two arrays, because that is how it is cheapest to + ;; store: one byte of vertex index per draw point, and one bit saying whether that point ends a + ;; strip. gsf-ik wants them side by side, a byte each. So the bits have to be spread out by a + ;; factor of eight before they can be married to the indices, and this is the trick for it. + ;; + ;; Three passes of pextlb do the spreading. Each pass interleaves a register's low eight bytes + ;; with themselves, which doubles every byte, so a halfword of sixteen flags becomes sixteen bytes + ;; each holding a whole copy of the byte its flag came from. AND against 01 02 04 08 10 20 40 80 + ;; then leaves exactly one live bit per lane, pceqb against the same mask turns "that bit was set" + ;; into a lane of all ones, and AND with all-ones-bytes turns that into a clean 0 or 1. pextlb and + ;; pextub finally interleave the sixteen flags with sixteen index bytes loaded as one quadword. + ;; Sixteen draw points per pass, two quadwords of output, no branches inside the pass. + ;; + ;; t2 and t3 each carry three unrelated things - the flags being spread, then the index quadword + ;; and the compare result, then the two halves of the finished output - so they keep their physical + ;; names. The loop is rotated: the second output quadword of a pass is stored at the top of the + ;; next one, and the flags for the next pass are already being spread in the branch's delay slot. + (rlet ((kick-bits :reg a0) ;; argument: the packed kick flags + (ik-out :reg a0) ;; then: the gsf-ik write cursor + (kick-step :reg a1) ;; argument: bytes of flags consumed per pass + (bit-select :reg a1 :class i128) ;; then: the 01 02 04 ... 80 lane mask + (ik-dest :reg a2) ;; argument: where the finished list goes + (one-per-byte :reg a2 :class i128) ;; then: 01 in every lane + (pass-count :reg a3) ;; argument: strides of flags to consume + (kick-cursor :reg a3) ;; then: the flag read cursor + (index-src :reg t0) + (kick-end :reg t1) + (stride :reg v1)) + (asm-block build-lane-masks + (nop!) + (mult3 kick-end kick-step pass-count) + (m kick-cursor kick-bits) + (m stride kick-step) + (m ik-out ik-dest) + ;; 513 and 257 seed the two masks: 0x201 grown into 0x8040201008040201 gives one + ;; distinct bit per byte lane, and 0x101 grown the same way gives 01 everywhere. + (add.i bit-select r0 513) + (add.i one-per-byte r0 257) + (sll t3 bit-select 18) + (sll t2 one-per-byte 16) + (or bit-select bit-select t3) + (or one-per-byte one-per-byte t2) + (dsll32 t3 bit-select 4) + (dsll32 t2 one-per-byte 0) + (or bit-select bit-select t3) + (or one-per-byte one-per-byte t2) + (pcpyld bit-select bit-select bit-select) + (mmi-nop!) + (pcpyld one-per-byte one-per-byte one-per-byte) + (mmi-nop!) + (add kick-end kick-end kick-cursor) + (mmi-nop!) + (l.hu t2 kick-cursor) + (add kick-cursor kick-cursor stride) + (nop!) + (m index-src index-src) + (nop!) + (pextlb t2 t2 t2) + (b tie-generic-ik-expand-kick-group :delay (pextlb t3 t2 t2))) + (asm-block interleave-indices-and-kicks + (label tie-generic-ik-next-kick-group) + (add.i index-src index-src 16) + (nop!) + (add kick-cursor kick-cursor stride) + (add.i ik-out ik-out 32) + (pextlb t3 t3 t3) + (s.q t2 ik-out -16) + (label tie-generic-ik-expand-kick-group) + (pextlb t2 t3 t3) + (mmi-nop!) + (and.q t2 t2 bit-select) + (mmi-nop!) + (pceqb t3 t2 bit-select) + (l.q t2 index-src) + (and.q t5 t3 one-per-byte) + (mmi-nop!) + (pextlb t3 t5 t2) + (l.hu t4 kick-cursor) + (pextub t2 t5 t2) + (s.q t3 ik-out) + (b.ne kick-cursor kick-end tie-generic-ik-next-kick-group :delay (pextlb t3 t4 t4)) + (nop!) + (s.q t2 ik-out 16)) + (asm-block return-nothing + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!)))) + + (defun generic-tie-dma-to-spad-sync () + "Wait out any transfer already running on the toSPR channel, start a chain transfer from the + main-memory address in a0 into the scratchpad address in a1, and wait for that one too." + (declare (asm-func none) (allow-saved-regs)) + ;; The synchronous form, used once at the start of a pass to fetch the first block of the chain + ;; before there is anything to overlap it with. Everything after that goes through + ;; generic-tie-upload-next and is left running. + ;; + ;; Both polls are padded to sixteen instructions with no-ops, which is the shape of a poll written + ;; for a channel that is expected to be busy: the register is only usable three instructions after + ;; its load, and re-reading a hardware register any faster than this buys nothing. Each poll bumps + ;; a counter, but the counter it bumps is a1, which by then is a dead copy of the destination + ;; address - the masked value in a2 is what gets programmed - so nothing records these stalls. + (rlet ((to-spr :reg v1 :type dma-bank-spr) + (chain-addr :reg a0) + (chcr :reg a0) + (spad-addr :reg a1) + (stall-count :reg a1) + (spad-addr-masked :reg a2) + (status :reg a3)) + (asm-block address-the-channel + (nop!) + (lui at #x7000) + ;; The scratchpad address register takes only the low sixteen bits of an offset into + ;; the page; the rest of a scratchpad pointer is the fixed 0x7000 selector. + (ori spad-addr-masked r0 #xffff) + (lui to-spr #x1000) + (nop!) + (ori to-spr to-spr #xd400) + (and spad-addr-masked spad-addr spad-addr-masked)) + (asm-block wait-for-channel + (label tie-generic-spad-wait-for-previous) + (l.w status (-> to-spr chcr)) + (nop! :count 3) + (and.i status status DMA-CHCR-STR) + (nop!) + (b.z status tie-generic-spad-previous-done :delay (nop!)) + (nop! :count 3) + (add.i stall-count stall-count 1) + (nop! :count 6) + (b tie-generic-spad-wait-for-previous :delay (nop!))) + (asm-block start-transfer + (label tie-generic-spad-previous-done) + (add.i status r0 DMA-CHCR-START-CHAIN-TTE) + (s.w spad-addr-masked (-> to-spr sadr)) + (s.w chain-addr (-> to-spr tadr)) + (s.w r0 (-> to-spr qwc)) + ;; The chain we are about to hand over was just written by ordinary stores, so make + ;; sure they have left the EE before the DMA controller starts reading tags. + (sync.l) + (s.w status (-> to-spr chcr))) + (asm-block wait-for-completion + (label tie-generic-spad-wait-for-transfer) + (l.w chcr (-> to-spr chcr)) + (nop! :count 3) + (and.i chcr chcr DMA-CHCR-STR) + (nop!) + (b.z chcr tie-generic-spad-transfer-done :delay (nop!)) + (nop! :count 3) + (add.i stall-count stall-count 1) + (nop! :count 6) + (b tie-generic-spad-wait-for-transfer :delay (nop!))) + (asm-block return-nothing + (label tie-generic-spad-transfer-done) + (m v0 r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!)))) + + (defun generic-tie-dma-to-spad () + "Start a chain transfer from the main-memory address in a0 into the scratchpad address in a1 and + return immediately." + (declare (asm-func none) (allow-saved-regs)) + ;; No wait at either end: the caller is expected to have made sure the channel is free and to + ;; check for completion before it touches the destination. Fire and forget is the whole point - + ;; this is what overlaps the next block's arrival with the current block's expansion. + (rlet ((to-spr :reg v1 :type dma-bank-spr) + (chain-addr :reg a0) + (spad-addr :reg a1) + (addr-mask :reg a2) + (chcr :reg v0)) + (asm-block start-transfer + (nop!) + (lui at #x7000) + (ori addr-mask r0 #xffff) + (lui to-spr #x1000) + (nop!) + (ori to-spr to-spr #xd400) + (and spad-addr spad-addr addr-mask) + (add.i chcr r0 DMA-CHCR-START-CHAIN-TTE) + (s.w spad-addr (-> to-spr sadr)) + (s.w chain-addr (-> to-spr tadr)) + (s.w r0 (-> to-spr qwc)) + (s.w chcr (-> to-spr chcr)) + (jr ra :delay (add sp sp r0))))) + + ) diff --git a/goal_src/jak1/engine/gfx/generic/generic-tie.gc b/goal_src/jak1/engine/gfx/generic/generic-tie.gc index bce7bdff9e..3275f58b07 100644 --- a/goal_src/jak1/engine/gfx/generic/generic-tie.gc +++ b/goal_src/jak1/engine/gfx/generic/generic-tie.gc @@ -7,3397 +7,89 @@ (require "engine/gfx/generic/generic-effect.gc") (require "engine/collide/collide-shape-h.gc") -;; Generic TIE -- the conversion pass that gets TIE geometry into GENERIC. -;; -;; TIE's own VU1 program is fast because it is rigid: a fixed strip template, a palette lookup, and no -;; per-vertex lighting. Anything that needs more than that -- in Jak 1 that means environment mapping -- -;; cannot use it. So a prototype can be flagged for Generic instead, and then TIE's prototype pass -;; stops building VU1 packets for it and emits a compact chain instead: which fragments were selected, -;; which instances chose each one, and the palette each needs. -;; -;; This file walks that chain. It runs entirely out of scratchpad, alternating two 3248-byte input -;; buffers so a main-memory chain transfer can be fetching the next block while the EE expands the -;; current one, and it hands finished packets to the GENERIC double-buffer builder. Expansion means -;; turning TIE's packed 16-byte base points and 24-byte interpolated points back into full quadwords, -;; gathering the normal, kick, color-index, and matrix tables that go with them, and then dispatching -;; whichever effect processor the fragment header asked for. Nineteen calls into the Generic VU0 -;; library do the vector work; see generic-tie-h.gc for the stream formats and generic-effect.gc for -;; the processors. -;; -;; Two costs are worth knowing about. Every vertex is touched by the EE here, where ordinary TIE never -;; touches one at all, and the compact stream has to be decompressed before any of it can be used -- -;; which is why Generic prototypes are rare and why the statistics block counts instances and -;; quadwords separately. +;; Generic TIE is used for environment mapped TIE geometry. In the PC Port, +;; this is handled by the normal PC TIE renderer, and this path is no longer used. -;; What one entry of the chain brings with it. The prototype pass writes this into shadow.tie-type -;; before the transfer starts, so the converter knows how to parse a block it has not looked at yet. -(defconstant GENERIC-TIE-ENTRY-MODEL-AND-PALETTE 0) -(defconstant GENERIC-TIE-ENTRY-MODEL-ONLY 1) -(defconstant GENERIC-TIE-ENTRY-INSTANCE-ONLY 2) - -;; Alternating the two input buffers is one xor because input-a starts at scratchpad 768 and the buffers -;; are one generic-tie-input apart: 768 and 3248 have no bits in common, so xor and add agree. The same -;; is true of the instance buffer, whose flip is cached in shadow.inst-xor because it depends on which -;; input buffer is current. -(defconstant GENERIC-TIE-INPUT-FLIP (type-size generic-tie-input)) +;; This file is kept around just for documentation on how it did work. +;; The EE assembly is in generic-tie-ee-asm.gc, this is just higher level GOAL functions. (define *generic-tie* #t) -(#when PC_PORT - (def-mips2c generic-tie-dma-to-spad-sync (function object object none)) - (def-mips2c generic-tie-convert (function none))) - (defun-debug generic-tie-debug () "Print the first twenty expanded Generic positions in floating-point world units." - (let ((vertices (the-as (pointer gsf-vertex) (-> *gsf-buffer* info ptr-verts)))) + (let ((vertices (-> *gsf-buffer* info ptr-verts))) (dotimes (i 20) - (let ((vertex (-> vertices i))) - (format *stdcon* - "~08x: x: ~f y: ~f z: ~f~%" - vertex - (* (-> vertex pos x) (/ 1.0 4096.0)) - (* (-> vertex pos y) (/ 1.0 4096.0)) - (* (-> vertex pos z) (/ 1.0 4096.0)))))) + (let* ((vertex (-> vertices i)) + (x (-> vertex pos x)) + (y (-> vertex pos y)) + (z (-> vertex pos z))) + (format *stdcon* "~08x: x: ~f y: ~f z: ~f~%" vertex (/ x METER_LENGTH) (/ y METER_LENGTH) (/ z METER_LENGTH))))) + 0 (none)) (defun-debug generic-tie-display-stats () "Display TIE conversion statistics when enabled." (none)) -(#unless PC_PORT - (defun generic-tie-convert () - "Walk the compact TIE chain from the start of the input buffer to the end of the chain, expanding - each fragment into the GSF buffer, building a GENERIC packet from it, and returning that packet - to the frame's DMA buffer. Stops early if the DMA buffer is nearly full." - (declare (asm-func none) (allow-saved-regs)) - ;; The whole pass, and three things overlap inside it: the toSPR channel fetches the next chain - ;; entry while the EE expands this one, the fromSPR channel returns the previous packet while the - ;; EE builds this one, and VU0 transforms the previous group of four vertices while the EE stages - ;; the next. Each of those has its own pair of buffers and the loop only ever waits at the point - ;; where it is about to reuse one. - ;; - ;; One pass looks like: wait for the input block, parse its DMA tags into generic-tie-control, - ;; check there is still room in the output, start the next block's transfer, expand this - ;; fragment, run the four effect processors over it, and hand the finished packet to fromSPR. - ;; - ;; The expansion in the middle is generic-tie-convert-proc inlined verbatim, register for - ;; register - it exists as its own function too, and the two are worth reading together. What is - ;; only here is the chain walk at either end. - ;; The saves go into the scratchpad work area rather than onto the stack, so the prologue and - ;; epilogue cost scratchpad accesses. That does mean the page register has to already hold the - ;; scratchpad base on entry, before the loop head loads it: it does, because the caller's last - ;; act is a store into this same page and the compiler leaves the page selector behind in the - ;; assembler temporary. The stack is still reserved, and still unused. - (asm-block save-registers - (add.i sp sp -128) - (s.d ra at (generic-work-offset storage data 0)) - (s.q s0 at (generic-work-offset storage data 1)) - (s.q s1 at (generic-work-offset storage data 2)) - (s.q s2 at (generic-work-offset storage data 3)) - (s.q s3 at (generic-work-offset storage data 4)) - (s.q s4 at (generic-work-offset storage data 5)) - (s.q s5 at (generic-work-offset storage data 6)) - (s.q gp at (generic-work-offset storage data 7))) - ;; One chain entry per pass around this loop, until the parser reports the end of it. - (rlet ((to-spr :reg v1 :type dma-bank-spr)) - (asm-block address-the-channel - (label tie-generic-next-chain-entry) - (lui to-spr #x1000) - (lui at #x7000) - (ori to-spr to-spr #xd400) - (nop!))) - ;; The transfer this pass is about to read was started at the end of the previous pass, which - ;; is the whole point of the two input buffers: expanding one fragment pays for fetching the - ;; next. By the time we get here it has usually already landed. - ;; - ;; The counter this poll bumps is never stored anywhere - gp is reused as a temporary a few - ;; hundred instructions later - so toSPR stalls do not reach the timing display, unlike the - ;; fromSPR stalls counted below. - (rlet ((to-spr :reg v1 :type dma-bank-spr) - (status :reg a0) - (stall-count :reg gp)) - (asm-block wait-for-input-block - (label tie-generic-wait-for-input-block) - (l.w status (-> to-spr chcr)) - (nop! :count 3) - (and.i status status DMA-CHCR-STR) - (nop!) - (b.z status tie-generic-input-block-ready :delay (nop!)) - (nop! :count 3) - (add.i stall-count stall-count 1) - (nop! :count 6) - (b tie-generic-wait-for-input-block :delay (nop!)))) - ;; A block that brought its own palette starts with it, already interpolated for the current - ;; time of day by the prototype pass. Copy it into the persistent palette buffer, because the - ;; input buffer it arrived in will be overwritten by the next transfer while its own vertices - ;; are still being expanded. - ;; - ;; The environment map's adgif shader comes along the same way, except that it is not in the - ;; block at all: the first DMA tag's unused VIF word holds a main-memory pointer to it, and - ;; five quadword copies bring it into the shared constant block. - (rlet ((entry-kind :reg v1) - (instance-block :reg a2) - (palette-dest :reg a2) - (kind-test :reg a0) - (control :reg a0 :type generic-tie-control) - (input-block :reg a1) - (model-tag :reg a1) - (palette-src :reg t0) - (palette-qwc :reg a3) - (shader-src :reg t1) - (palette-cursor :reg t1)) - ;; t2 through t5 shuttle quadwords for the two copies. - (asm-block adopt-new-palette - (label tie-generic-input-block-ready) - (nop!) - (l.w entry-kind at (generic-work-offset in-buf tie shadow tie-type)) - (nop!) - (l.w instance-block at (generic-work-offset in-buf tie shadow ptr-inst)) - (sub kind-test entry-kind r0) - (l.w input-block at (generic-work-offset in-buf tie shadow ptr-buf)) - (b.nz kind-test tie-generic-chain-entry-kind :delay (add.i control at (generic-work-offset in-buf tie control))) - (add.i palette-src input-block 16) - (l.hu palette-qwc input-block) - (sll t2 palette-qwc 4) - (l.w shader-src input-block 8) ;; the tag's unused VIF word: where the shader lives - (add.i palette-dest at (generic-work-offset in-buf tie palette-buf)) - (add model-tag palette-src t2) - (nop!) - (l.q t2 shader-src) - (nop!) - (l.q t3 shader-src 16) - (nop!) - (l.q t4 shader-src 32) - (nop!) - (l.q t5 shader-src 48) - (nop!) - (l.q shader-src shader-src 64) - (nop!) - (s.q t2 at (generic-work-offset fx-buf work consts envmap shader)) - (nop!) - (s.q t3 at (+ (generic-work-offset fx-buf work consts envmap shader) 16)) - (nop!) - (s.q t4 at (+ (generic-work-offset fx-buf work consts envmap shader) 32)) - (nop!) - (s.q t5 at (+ (generic-work-offset fx-buf work consts envmap shader) 48)) - (nop!) - (s.q palette-cursor at (+ (generic-work-offset fx-buf work consts envmap shader) 64)) - (m palette-cursor palette-dest) - (nop!) - (add.i t2 palette-qwc -4) - (m palette-cursor palette-cursor) - (b.lt t2 r0 tie-generic-copy-palette-tail :delay (m palette-src palette-src)))) - ;; Four quadwords a pass while there are four left. - (rlet ((palette-src :reg t0) - (palette-cursor :reg t1) - (palette-qwc :reg a3)) - (asm-block copy-palette-quads - (label tie-generic-copy-palette-quads) - (nop!) - (l.q t5 palette-src) - (nop!) - (l.q t2 palette-src 16) - (add.i palette-qwc palette-qwc -4) - (l.q t3 palette-src 32) - (add.i palette-cursor palette-cursor 64) - (l.q t4 palette-src 48) - (add.i palette-src palette-src 64) - (s.q t5 palette-cursor -64) - (add.i t5 palette-qwc -4) - (s.q t2 palette-cursor -48) - (nop!) - (s.q t3 palette-cursor -32) - (b.ge t5 r0 tie-generic-copy-palette-quads :delay (s.q t4 palette-cursor -16)))) - ;; The last one to three, written out four times rather than looped: at this size the branch - ;; would cost as much as the copy. - (rlet ((palette-src :reg t0) - (palette-cursor :reg t1) - (palette-qwc :reg a3)) - (asm-block copy-palette-tail - (label tie-generic-copy-palette-tail) - (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) - (add.i palette-src palette-src 16) - (add.i palette-cursor palette-cursor 16) - (add.i palette-qwc palette-qwc -1) - (s.q t2 palette-cursor -16) - (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) - (add.i palette-src palette-src 16) - (add.i palette-cursor palette-cursor 16) - (add.i palette-qwc palette-qwc -1) - (s.q t2 palette-cursor -16) - (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) - (add.i palette-src palette-src 16) - (add.i palette-cursor palette-cursor 16) - (add.i palette-qwc palette-qwc -1) - (s.q t2 palette-cursor -16) - (b.z palette-qwc tie-generic-palette-copied :delay (l.q t2 palette-src)) - (add.i palette-src palette-src 16) - (add.i palette-src palette-cursor 16) - (add.i palette-qwc palette-qwc -1) - (s.q t2 palette-src -16))) - ;; Point the control block at the persistent copy and go parse the model. - (rlet ((palette-dest :reg a2) - (palette-qwc :reg a3)) - (asm-block palette-copied - (label tie-generic-palette-copied) - (m palette-qwc r0) - (b tie-generic-parse-model-block :delay (s.w palette-dest at (generic-work-offset in-buf tie control))))) - ;; A block with no palette of its own still has a model to parse; an instance-only entry has - ;; neither, and only its transform and palette indices need looking at. - (rlet ((entry-kind :reg v1) - (kind-test :reg a3) - (model-tag :reg a1) ;; the input buffer's base, stepped past the palette area - (instance-block :reg a2) - (instance-cursor :reg t2)) - (asm-block chain-entry-kind - (label tie-generic-chain-entry-kind) - (add.i kind-test entry-kind -1) - (nop!) - (b.nz kind-test tie-generic-parse-instance-block :delay (m instance-cursor instance-block)) - (nop!) - (add.i model-tag model-tag (offset-of generic-tie-input model-tag)) - (nop!))) - ;; The block is a DMA chain that was transferred with its tags, so it describes itself: each - ;; tag's quadword count says how far the next one is. Walk it and fill in the cursors - shaders, - ;; run control, packed points, the fragment header - and resolve the kick, normal and - ;; interpolation tables from the offsets in the header. The draw-point indices need no offset - ;; because they start where gsf-header ends. The shader count is not in a header field at all; - ;; it sits in an unused register slot of the first shader, and five quadwords apiece turns it - ;; into the address the run control starts at. - ;; - ;; Two more values come out of the unused VIF words of the tag heading the model: what the next - ;; chain entry will be, and whether it brings a model of its own. The prototype pass put them - ;; there so this parser can start the next transfer before it expands anything. - (rlet ((control :reg a0 :type generic-tie-control)) - (asm-block parse-model-block - (label tie-generic-parse-model-block) - (add.i a2 a1 16) - (l.hu a3 a1) - (sll t0 a3 4) - (l.wu t1 a2 28) - (sll t2 t1 2) - (l.wu a3 a1 12) - (add t1 t2 t1) - (l.wu a1 a1 8) - (add t0 a2 t0) - (s.w a3 (-> control next-model)) - (sll a3 t1 4) - (s.w a1 (-> control next-is-model)) - (add.i a1 t0 16) - (l.hu t0 t0) - (add a3 a2 a3) - (s.w a2 (-> control ptr-shaders)) - (sll a2 t0 4) - (s.w a3 (-> control ptr-runctrl)) - (add a2 a1 a2) - (s.w a1 (-> control ptr-verts)) - (add.i a1 a2 16) - (l.hu a2 a2) - (sll t2 a2 4) - (l.hu a2 a1 14) - (add.i a3 a1 32) - (l.hu t0 a1 10) - (add t0 t0 a1) - (l.hu t1 a1 12) - (add t1 t1 a1) - (l.hu t3 a1) - (add t2 a1 t2) - (s.w a1 (-> control ptr-generic)) - (add.i t2 t2 16) - (s.w a3 (-> control ptr-dps)) - (add a1 a2 a1) - (s.w t1 (-> control ptr-normals)) - (nop!) - (s.w a1 (-> control ptr-interp)) - (b tie-generic-parse-instance-block :delay (s.w t0 (-> control ptr-kicks))))) - ;; The instance part is three fields: the transform and morph state, the palette index per - ;; vertex, and - again out of a tag's unused VIF word - the address of the next instance of - ;; this same fragment, or a small marker when there is not one. - (rlet ((control :reg a0 :type generic-tie-control) - (instance-cursor :reg t2)) - (asm-block parse-instance-block - (label tie-generic-parse-instance-block) - (add.i a1 instance-cursor 16) - (l.wu a3 instance-cursor 12) - (add.i a2 a1 112) - (s.w a3 (-> control next-instance)) - (nop!) - (l.hu a3 a2 -16) - (nop!) - (s.w a1 (-> control ptr-mtxs)) - (nop!) - (s.w a2 (-> control ptr-cinds)) - (nop!) - (s.w v1 (-> control tie-type)) - (nop!))) - ;; Stop while there is still room. The limit is 64 KiB short of the end of the frame's DMA - ;; buffer, which is more than any one fragment can need, so the check only has to happen once - ;; per chain entry rather than per packet. - (asm-block check-output-room - (l.w v1 at (generic-work-offset in-buf tie shadow write-limit)) - (nop!) - (l.w a0 at (generic-work-offset saves basep)) - (sub v1 a0 v1) - (nop!) - (b.ge v1 r0 tie-generic-convert-done :delay (nop!))) - ;; generic-tie-upload-next, inlined. Work out what comes after this entry, flip the buffer it - ;; will land in, and start its transfer now so it overlaps the expansion below. - (asm-block queue-model-with-palette - (lui at #x7000) - (nop!) - (nop!) - (l.w v1 at (generic-work-offset in-buf tie control next-instance)) - (lui a1 1) - (l.w a0 at (generic-work-offset in-buf tie control next-model)) - (sub a1 v1 a1) - (l.w a2 at (generic-work-offset in-buf tie control next-is-model)) - (b.gt a1 r0 tie-generic-queue-next-instance :delay (l.w a1 at (generic-work-offset in-buf tie shadow ptr-inst))) - (b.nz a2 tie-generic-queue-model-without-palette :delay (l.w v1 at (generic-work-offset in-buf tie shadow ptr-buf))) - (xor.i a1 v1 GENERIC-TIE-INPUT-FLIP) - (m v1 a0) - (add.i a3 a1 (offset-of generic-tie-input matrix-tag)) - (s.w a1 at (generic-work-offset in-buf tie shadow ptr-buf)) - (xor.i a2 a3 (generic-work-offset in-buf tie inst-buf)) - (s.w a3 at (generic-work-offset in-buf tie shadow ptr-inst)) - (add.i a3 r0 0) - (add a1 a1 r0) - (and.i a2 a2 #xffff) - (s.w a3 at (generic-work-offset in-buf tie shadow tie-type)) - (b.nz a0 tie-generic-start-input-transfer :delay (s.w a2 at (generic-work-offset in-buf tie shadow inst-xor))) - (add.i v1 r0 1) - (nop!) - (b tie-generic-expand-fragment :delay (s.w v1 at (generic-work-offset in-buf tie shadow end-of-chain)))) - ;; Same, for an entry that reuses the palette already copied. - (asm-block queue-model-without-palette - (label tie-generic-queue-model-without-palette) - (xor.i a1 v1 GENERIC-TIE-INPUT-FLIP) - (m v1 a0) - (add.i a3 a1 (offset-of generic-tie-input matrix-tag)) - (s.w a1 at (generic-work-offset in-buf tie shadow ptr-buf)) - (xor.i a2 a3 (generic-work-offset in-buf tie inst-buf)) - (s.w a3 at (generic-work-offset in-buf tie shadow ptr-inst)) - (add.i a3 r0 1) - (add.i a1 a1 (offset-of generic-tie-input model-tag)) - (and.i a2 a2 #xffff) - (s.w a3 at (generic-work-offset in-buf tie shadow tie-type)) - (b.nz a0 tie-generic-start-input-transfer :delay (s.w a2 at (generic-work-offset in-buf tie shadow inst-xor))) - (add.i v1 r0 1) - (nop!) - (b tie-generic-expand-fragment :delay (s.w v1 at (generic-work-offset in-buf tie shadow end-of-chain)))) - ;; Same again for another instance of the fragment in hand, which alternates the instance - ;; buffer rather than the whole input buffer. - (asm-block queue-next-instance - (label tie-generic-queue-next-instance) - (m v1 v1) - (l.w a2 at (generic-work-offset in-buf tie shadow inst-xor)) - (add.i a0 r0 2) - (nop!) - (xor a1 a1 a2) - (s.w a0 at (generic-work-offset in-buf tie shadow tie-type)) - (b tie-generic-start-input-transfer :delay (s.w a1 at (generic-work-offset in-buf tie shadow ptr-inst)))) - ;; Hand it to the channel and get on with the current fragment. - (rlet ((to-spr :reg a0 :type dma-bank-spr)) - (asm-block start-input-transfer - (label tie-generic-start-input-transfer) - (lui at #x7000) - (ori a2 r0 #xffff) - (lui to-spr #x1000) - (nop!) - (ori to-spr to-spr #xd400) - (and a1 a1 a2) - (add.i a2 r0 DMA-CHCR-START-CHAIN-TTE) - (s.w a1 (-> to-spr sadr)) - (s.w v1 (-> to-spr tadr)) - (s.w r0 (-> to-spr qwc)) - (s.w a2 (-> to-spr chcr)))) - ;; The next transfer is running; expand the fragment that is already here. Everything from here - ;; to the effect processors is generic-tie-convert-proc, inlined. - (asm-block expand-fragment - (label tie-generic-expand-fragment) - (nop!) - (m v1 r0)) - ;; Everything about the fragment now in scratchpad is described by generic-tie-control, - ;; which the chain parser filled in. Read all of it at once - the loads are two cycles - ;; apart and there is nothing else to do with the gaps - and pick up the counts out of the - ;; fragment header. An instance-only chain entry needs none of the expansion that follows, - ;; because the model is still sitting in the work buffer from last time, so it jumps - ;; straight to the transform. - (rlet ((control :reg t2 :type generic-tie-control) - (out-buf :reg a0 :type gsf-buffer) - (header :reg t1 :type generic-tie-header) - (instance-mtx :reg a3 :type generic-tie-matrix) - (point-src :reg t3) - (index-src :reg t5) - (kick-src :reg t6) - (normal-src :reg t4) - (color-index-src :reg v1) - (spare :reg v1) ;; the control cursors this pass does not need land here - (palette :reg t0) - (entry-kind :reg a1) - (num-bps :reg a1) - (num-vtxs :reg a2) - (instance-only :reg a2) - (num-ips :reg a2) - (num-dps :reg t8)) - ;; Three of the fifteen control words are read into the same register and thrown away: - ;; the shader, run-control and interpolation cursors belong to the packet builder and to - ;; generic-interp-dproc, not to this pass. - (asm-block load-fragment-tables - (lui at #x7000) - (add.i control at (generic-work-offset in-buf tie control)) - (l.w out-buf at (generic-work-offset in-buf tie shadow out-buf)) - (nop!) - (l.w entry-kind (-> control tie-type)) - (nop!) - (l.w spare (-> control ptr-shaders)) - (nop!) - (l.w spare (-> control ptr-runctrl)) - (nop!) - (l.w point-src (-> control ptr-verts)) - (nop!) - (l.w header (-> control ptr-generic)) - (nop!) - (l.w spare (-> control ptr-interp)) - (nop!) - (l.w index-src (-> control ptr-dps)) - (nop!) - (l.w kick-src (-> control ptr-kicks)) - (nop!) - (l.w normal-src (-> control ptr-normals)) - (nop!) - (l.w instance-mtx (-> control ptr-mtxs)) - (nop!) - (l.w color-index-src (-> control ptr-cinds)) - (nop!) - (l.w palette (-> control ptr-palette)) - (nop!) - (l.hu num-vtxs (-> header gsf-header num-vtxs)) - (nop!) - (l.hu num-dps (-> header gsf-header num-dps)) - (add.i instance-only entry-kind -2) - (l.bu num-bps (-> header num-bps)) - (b.z instance-only tie-generic-transform-fragment :delay (l.bu num-ips (-> header num-ips))))) - ;; Divide the GSF buffer up for this fragment and publish the divisions in its info block, - ;; so that neither the effect processors nor the packet builder has to work them out again: - ;; the index/kick list first, the expanded vertices after it rounded up to a quadword, and - ;; the environment-map attributes after those. gsf-header is copied across whole. - ;; - ;; The tail of the block builds the two lane masks the kick expansion needs. 513 grown into - ;; 0x8040201008040201 puts one distinct bit in each byte lane; 257 grown the same way puts - ;; 01 in all sixteen. See generic-tie-decompress for what they are for - the loop that - ;; follows is that function inlined. - ;; - ;; ra, gp, t7 through t9 and s2 through s5 are address arithmetic and mask temporaries here - ;; and change meaning every few instructions, so they keep their physical names. - (rlet ((out-buf :reg a0 :type gsf-buffer) - (header :reg t1 :type generic-tie-header) - (num-dps :reg t8) - (ik-bytes :reg t7) - (ik-out :reg t7) - (verts-out :reg t9) - (header-quad :reg ra :class i128) - (fx-out :reg ra)) - (asm-block publish-draw-point-arrays - (add ik-bytes num-dps num-dps) - (add.i t9 r0 -16) - (add.i ra ik-bytes 15) - (add.i ik-out out-buf 32) ;; the work area starts after info and header - (and t9 ra t9) - (l.q header-quad (-> header gsf-header)) - (add t9 t7 t9) - (s.q header-quad (-> out-buf header)) - (sll ra t8 5) - (s.w ik-out (-> out-buf info ptr-iks)) - (add fx-out verts-out ra) - (s.w verts-out (-> out-buf info ptr-verts)) - (add.i t8 t8 7) - (s.w fx-out (-> out-buf info ptr-fx)) - (sra t9 t8 3) - (nop!) - (add.i t8 r0 2) - (nop!) - (mult3 s3 t8 t9) - (m ra t6) - (m t6 t8) - (m t7 t7) - (add.i t8 r0 513) - (add.i t9 r0 257) - (sll s1 t8 18) - (sll s2 t9 16) - (or t8 t8 s1) - (or t9 t9 s2) - (dsll32 s1 t8 4) - (dsll32 s2 t9 0) - (or t8 t8 s1) - (or t9 t9 s2) - (pcpyld t8 t8 t8) - (mmi-nop!) - (pcpyld t9 t9 t9) - (mmi-nop!) - (add s3 s3 ra) - (mmi-nop!) - (l.hu s2 ra) - (add ra ra t6) - (nop!) - (m t5 t5) - (nop!) - (pextlb s2 s2 s2) - (b tie-generic-expand-kick-group :delay (pextlb s1 s2 s2)))) - ;; generic-tie-decompress, inlined: sixteen draw points per pass, each one an index byte - ;; married to a kick bit that has been spread out to a whole byte. The loop is rotated, so - ;; the second output quadword of a pass is stored at the top of the next one. - ;; - ;; The pass count is generous, and why it is written this way is not established. Sixteen draw - ;; points are finished per pass, but the bound steps one halfword of kick bits per pass across - ;; ceil(num-dps / 8) halfwords, so it runs about twice as many passes as there are draw points and - ;; writes off the end of the list. It cannot get far: - ;; the overshoot is at most another 2 * num-dps bytes, the vertex array immediately behind the - ;; list is 32 bytes per vertex against the list's 2 per draw point, and the vertex array is - ;; filled a few hundred instructions later regardless. So the spill stays inside the buffer and - ;; inside data that is about to be overwritten. - (asm-block interleave-indices-and-kicks - (label tie-generic-next-kick-group) - (add.i t5 t5 16) - (nop!) - (add ra ra t6) - (add.i t7 t7 32) - (pextlb s1 s1 s1) - (s.q s2 t7 -16) - (label tie-generic-expand-kick-group) - (pextlb s2 s1 s1) - (mmi-nop!) - (and.q s2 s2 t8) - (mmi-nop!) - (pceqb s1 s2 t8) - (l.q s2 t5) - (and.q v0 s1 t9) - (mmi-nop!) - (pextlb s1 v0 s2) - (l.hu s0 ra) - (pextub s2 v0 s2) - (s.q s1 t7) - (b.ne ra s3 tie-generic-next-kick-group :delay (pextlb s1 s0 s0)) - (nop!) - (s.q s2 t7 16)) - ;; Leave a job behind for generic-interp-dproc if this fragment has any subdivision - ;; vertices: they occupy num-ips slots starting right after the base points, and the blend - ;; data they need is the interpolation table in the fragment. A fragment with none clears - ;; the pointer instead, which is how the processor knows to do nothing. - (rlet ((interp-job :reg t5 :type generic-interp-job) - (control :reg t2 :type generic-tie-control) - (interp-data :reg t2) - (num-bps :reg a1) - (num-ips :reg a2)) - (asm-block publish-interp-job - (m interp-job r0) - (b.z num-ips tie-generic-no-interp-job :delay (nop!)) - (add.i t6 num-ips 7) - (add.i interp-job at (generic-work-offset in-buf tie interp-job)) - (nop!) - (l.w interp-data (-> control ptr-interp)) - (sra t6 t6 3) - (s.h r0 (-> interp-job job-type)) - (nop!) - (s.h num-bps (-> interp-job first)) - (nop!) - (s.h num-ips (-> interp-job num)) - (nop!) - (s.w t2 (-> interp-job ptr-data)) - (nop!) - (s.w interp-job at (generic-work-offset saves ptr-interp-job)) - (b tie-generic-interp-job-ready :delay (nop!)) - (label tie-generic-no-interp-job) - (nop!) - (s.w r0 at (generic-work-offset saves ptr-interp-job)) - (nop!))) - ;; Hand the fragment's normals to VU0 four at a time. Each one is three signed bytes, so a - ;; quadword of them is four normals; pextub/pextlb move each byte into the top of a - ;; halfword and pextuh/pextlh into the top of a word, and then one arithmetic shift by 19 - ;; leaves the byte scaled by 32 with its sign intact - which the VU reads as 1.12 fixed - ;; point, giving a normal in [-1, 1). Two shifts and two interleaves per four normals, and - ;; no multiplies. - ;; - ;; The count is base points plus interpolated points rounded up to four, so the last pass - ;; can read a little past the table. vi08 and vi09 carry the two counts into VU0, which - ;; sizes its staging arrays from them; vi09 is written twice because the first write is too - ;; close to the call for the transfer to have landed. - (rlet ((normal-src :reg t4) - (normal-cursor :reg t2) - (normal-end :reg t4) - (num-bps :reg a1) - (num-ips :reg a2)) - (asm-block stage-normals - (label tie-generic-interp-job-ready) - (add t5 num-bps num-ips) - (m normal-cursor normal-src) - (add.i t4 t5 3) - (ctc2.i vi9 num-ips) - (sra t5 t4 2) - (l.q t4 normal-cursor) - (sll t5 t5 4) - (add.i normal-cursor normal-cursor 16) - (pextub t7 t4 r0) - (ctc2.i vi8 num-bps) - (pextlb t6 t4 r0) - (ctc2.i vi9 num-ips) - (add normal-end normal-cursor t5) - (callms GENERIC-VU0-TIE-SETUP) - (label tie-generic-stage-normal-group) - (pextuh t5 t7 r0) - (mmi-nop!) - (pextlh t7 t7 r0) - (mmi-nop!) - (pextuh t8 t6 r0) - (mmi-nop!) - (pextlh t6 t6 r0) - (mmi-nop!) - (sra.w t9 t6 19) - (l.q t6 normal-cursor) - (sra.w t8 t8 19) - (m.ni vf5 t9) - (sra.w t7 t7 19) - (m.ni vf6 t8) - (sra.w t5 t5 19) - (m.ni vf7 t7) - (pextub t7 t6 r0) - (m.ni vf8 t5) - (pextlb t6 t6 r0) - (mmi-nop!) - (nop!) - (callms GENERIC-VU0-TIE-NORMALS) - (b.ne normal-cursor normal-end tie-generic-stage-normal-group :delay (add.i normal-cursor normal-cursor 16)) - (nop!))) - ;; Now the positions, again four at a time. A base point is sixteen bytes: three signed - ;; 16-bit coordinates, an output offset, the texture coordinate pair, and a second output - ;; offset. The doubleword load takes the position and the word load at +8 takes the - ;; coordinates; the shift by 10 leaves each coordinate multiplied by 64, which is the scale - ;; the instance matrix is built to undo. - ;; - ;; The loop is software pipelined against VU0: the call at the top of the body works on the - ;; group staged by the previous iteration while this one crosses the next group over. That - ;; is why the last group is staged outside the loop and why there is a call after it. - (rlet ((point-src :reg t3) - (point-cursor :reg t2) - (num-bps :reg a1) - (remaining :reg t4)) - (asm-block stage-base-points - (m point-cursor point-src) - (b.z num-bps tie-generic-stage-interp-pair :delay (add.i remaining num-bps -4)) - (b.le remaining r0 tie-generic-base-point-tail :delay (nop!)) - (m point-cursor point-src) - (l.d t5 t3) - (add.i remaining num-bps -8) - (l.d t6 t3 16) - (pextlh t7 t5 r0) - (l.d t5 t3 32) - (pextlh ra t6 r0) - (l.d t6 t3 48) - (pextlh t8 t5 r0) - (l.wu t5 t3 8) - (pextlh t9 t6 r0) - (l.wu t6 t3 24) - (sra.w s3 t7 10) - (l.wu t7 t3 40) - (sra.w ra ra 10) - (l.wu t3 t3 56) - (sra.w t8 t8 10) - (m.ni vf1 s3) - (sra.w t9 t9 10) - (m.ni vf2 ra) - (add.i point-cursor point-cursor 64) - (m.ni vf3 t8) - (nop!) - (m.ni vf4 t9) - (nop!) - (m.ni vf13 t5) - (nop!) - (m.ni vf14 t6) - (nop!) - (m.ni vf15 t7) - (b.le t4 r0 tie-generic-stage-last-base-points :delay (m.ni vf16 t3)) - (label tie-generic-stage-base-point-group) - (callms GENERIC-VU0-TIE-BASE-POINTS) - (l.d t3 point-cursor) - (add.i t4 t4 -4) - (l.d t5 point-cursor 16) - (pextlh t8 t3 r0) - (l.d t3 point-cursor 32) - (pextlh t9 t5 r0) - (l.d t5 point-cursor 48) - (pextlh t6 t3 r0) - (l.wu t3 point-cursor 8) - (pextlh t7 t5 r0) - (l.wu t5 point-cursor 24) - (sra.w ra t8 10) - (l.wu t8 point-cursor 40) - (sra.w t9 t9 10) - (l.wu s3 point-cursor 56) - (sra.w t6 t6 10) - (m.ni vf1 ra) - (sra.w t7 t7 10) - (m.ni vf2 t9) - (add.i point-cursor point-cursor 64) - (m.ni vf3 t6) - (nop!) - (m.ni vf4 t7) - (nop!) - (m.ni vf13 t3) - (nop!) - (m.ni vf14 t5) - (nop!) - (m.ni vf15 t8) - (b.gt t4 r0 tie-generic-stage-base-point-group :delay (m.ni vf16 s3)) - (label tie-generic-stage-last-base-points) - (callms GENERIC-VU0-TIE-BASE-POINTS) - (nop!))) - ;; The four-wide pipeline does not care that base points and interpolated points are - ;; different sizes, so the group that straddles the boundary between them is staged by a - ;; VU0 entry written for that exact shape. num-bps modulo four picks it: one base point - ;; plus one interpolated point, two plus one, three on their own, or - when the remainder - ;; is zero, or when the fragment has no interpolated points at all - one more plain group - ;; of four. - ;; - ;; The last case reads up to three points past the end of the array when the count is not a - ;; multiple of four. VU0 transforms them and the write loop, which counts in fours as well, - ;; stores them; both overruns land inside the GSF buffer's slack. - (rlet ((point-cursor :reg t2) - (num-bps :reg a1) - (num-ips :reg a2)) - (asm-block stage-boundary-group - (label tie-generic-base-point-tail) - (b.z num-ips tie-generic-stage-final-base-group :delay (and.i t3 num-bps 3)) - (b.z t3 tie-generic-stage-final-base-group :delay (add.i t3 t3 -1)) - (b.z t3 tie-generic-stage-tail-1bp-1ip :delay (add.i t3 t3 -1)) - (b.z t3 tie-generic-stage-tail-2bp-1ip :delay (add.i t3 t3 -1)) - (b tie-generic-stage-tail-3bp :delay (nop!)) - (label tie-generic-stage-tail-1bp-1ip) - (nop!) - (l.d t3 point-cursor) - (nop!) - (l.d t4 point-cursor 16) - (pextlh t3 t3 r0) - (l.d t6 point-cursor 24) - (pextlh t4 t4 r0) - (l.wu t5 point-cursor 8) - (pextlh t6 t6 r0) - (l.wu t7 point-cursor 32) - (nop! :count 4) - (sra.w t3 t3 10) - (m.ni vf13 t5) - (sra.w t4 t4 10) - (m.ni vf14 t7) - (sra.w t5 t6 10) - (m.ni vf1 t3) - (nop!) - (m.ni vf2 t4) - (add.i point-cursor point-cursor 40) - (m.ni vf3 t5) - (b tie-generic-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-1BP-1IP)) - (label tie-generic-stage-tail-2bp-1ip) - (nop!) - (l.d t4 point-cursor) - (nop!) - (l.d t3 point-cursor 16) - (pextlh t8 t4 r0) - (l.d t5 point-cursor 32) - (pextlh t4 t3 r0) - (l.d t3 point-cursor 40) - (pextlh t5 t5 r0) - (l.wu t6 point-cursor 8) - (pextlh t7 t3 r0) - (l.wu t3 point-cursor 24) - (sra.w t9 t8 10) - (l.wu t8 point-cursor 48) - (sra.w t4 t4 10) - (m.ni vf13 t6) - (sra.w t5 t5 10) - (m.ni vf1 t9) - (sra.w t6 t7 10) - (m.ni vf2 t4) - (add.i point-cursor point-cursor 56) - (m.ni vf3 t5) - (nop!) - (m.ni vf4 t6) - (nop!) - (m.ni vf14 t3) - (nop!) - (m.ni vf15 t8) - (b tie-generic-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-2BP-1IP)) - (label tie-generic-stage-tail-3bp) - (nop!) - (l.d t4 point-cursor) - (nop!) - (l.d t3 point-cursor 16) - (pextlh t7 t4 r0) - (l.d t4 point-cursor 32) - (pextlh t5 t3 r0) - (l.wu t3 point-cursor 8) - (pextlh t6 t4 r0) - (l.wu t4 point-cursor 24) - (sra.w t7 t7 10) - (l.wu t8 point-cursor 40) - (sra.w t5 t5 10) - (m.ni vf13 t3) - (sra.w t3 t6 10) - (m.ni vf1 t7) - (nop!) - (m.ni vf2 t5) - (add.i point-cursor point-cursor 48) - (m.ni vf3 t3) - (nop!) - (m.ni vf14 t4) - (nop!) - (m.ni vf15 t8) - (b tie-generic-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-3BP)) - (label tie-generic-stage-final-base-group) - (nop!) - (l.d t3 point-cursor) - (nop!) - (l.d t4 point-cursor 16) - (pextlh t7 t3 r0) - (l.d t3 point-cursor 32) - (pextlh t8 t4 r0) - (l.d t4 point-cursor 48) - (pextlh t5 t3 r0) - (l.wu t3 point-cursor 8) - (pextlh t6 t4 r0) - (l.wu t4 point-cursor 24) - (sra.w t9 t7 10) - (l.wu t7 point-cursor 40) - (sra.w t8 t8 10) - (l.wu ra point-cursor 56) - (sra.w t5 t5 10) - (m.ni vf1 t9) - (sra.w t6 t6 10) - (m.ni vf2 t8) - (add.i point-cursor point-cursor 64) - (m.ni vf3 t5) - (nop!) - (m.ni vf4 t6) - (nop!) - (m.ni vf13 t3) - (nop!) - (m.ni vf14 t4) - (nop!) - (m.ni vf15 t7) - (nop!) - (m.ni vf16 ra) - (b.z num-ips tie-generic-staging-done :delay (callms GENERIC-VU0-TIE-BASE-POINTS)))) - ;; Whatever interpolated points are left go two at a time, because each one is twenty-four - ;; bytes: the vector the morph weight scales, the vector it is added to, and the coordinate - ;; pair. Three of them fill two quadwords, which is why the stride is 48 and not 32. - (rlet ((point-cursor :reg t2) - (num-ips :reg a2) - (remaining :reg t3)) - (asm-block stage-interp-points - (label tie-generic-stage-interp-pair) - (nop!) - (l.d t4 point-cursor) - (add.i remaining num-ips -2) - (l.d t5 point-cursor 8) - (pextlh t4 t4 r0) - (l.d t6 point-cursor 24) - (pextlh t5 t5 r0) - (l.d t7 point-cursor 32) - (pextlh t6 t6 r0) - (l.wu t8 point-cursor 16) - (pextlh t7 t7 r0) - (l.wu t9 point-cursor 40) - (sra.w t4 t4 10) - (m.ni vf13 t8) - (sra.w t5 t5 10) - (m.ni vf14 t9) - (sra.w t6 t6 10) - (m.ni vf1 t4) - (sra.w t4 t7 10) - (m.ni vf2 t5) - (add.i point-cursor point-cursor 48) - (m.ni vf3 t6) - (b.le remaining r0 tie-generic-stage-last-interp-pair :delay (m.ni vf4 t4)) - (label tie-generic-stage-interp-pair-loop) - (callms GENERIC-VU0-TIE-INTERP-POINTS) - (l.d t4 point-cursor) - (add.i remaining remaining -2) - (l.d t5 point-cursor 8) - (pextlh t4 t4 r0) - (l.d t6 point-cursor 24) - (pextlh t5 t5 r0) - (l.d t7 point-cursor 32) - (pextlh t6 t6 r0) - (l.wu t8 point-cursor 16) - (pextlh t7 t7 r0) - (l.wu t9 point-cursor 40) - (sra.w t4 t4 10) - (m.ni vf13 t8) - (sra.w t5 t5 10) - (m.ni vf14 t9) - (sra.w t6 t6 10) - (m.ni vf1 t4) - (sra.w t4 t7 10) - (m.ni vf2 t5) - (add.i point-cursor point-cursor 48) - (m.ni vf3 t6) - (b.gt remaining r0 tie-generic-stage-interp-pair-loop :delay (m.ni vf4 t4)) - (label tie-generic-stage-last-interp-pair) - (callms GENERIC-VU0-TIE-INTERP-POINTS) - (nop!) - (label tie-generic-staging-done) - (nop!) - (nop!))) - ;; The transform starts here, and so does the one piece of per-fragment color work the EE - ;; still has to do itself: the environment map's tint. It is the fragment's authored tint, - ;; scaled by this instance's fade value and then by the time-of-day environment color. - ;; Both scalings are packed halfword multiplies whose products come back out of the - ;; accumulator halved into HI and LO, so pmfhl.lh gathers them and a shift by seven both - ;; discards the 0..255 range and keeps the rounding cheap. The result goes into all four - ;; lanes of the shared envmap color constant, where prepare-dma-double will find it. - ;; - ;; The other thing decided here is which VU1 entry the packet will call. The instance pass left - ;; its near-classification reject flags in the second word of the instance record's fifth - ;; quadword; an instance carrying any of them would have gone to the near renderer, which clips, - ;; so it takes the clipping entry here and everything else takes the direct one. Patching the - ;; immediate of the MSCAL template is the whole of it. - (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) - (out-buf :reg a0 :type gsf-buffer) - (header :reg t1 :type generic-tie-header) - (tint :reg t1) - (vtx-out :reg t1) - (vtx-count :reg t3) - (cind-cursor :reg t4) - (color-index-src :reg v1) - (num-bps :reg a1) - (vu1-entry :reg t6)) - (asm-block build-envmap-tint - (label tie-generic-transform-fragment) - (m vtx-count num-bps) - (l.w t6 (-> instance-mtx fog data 0)) - (m cind-cursor color-index-src) - (l.w tint (-> header tint-color)) - (add.i t5 at (generic-work-offset fx-buf work consts envmap colors)) - (s.w t6 at (generic-work-offset saves fade-val)) - (pextlb t2 r0 t1) - (l.q t1 (-> instance-mtx matrix vector 0)) - (pextlh t7 t6 t6) - (l.q t6 (-> instance-mtx matrix vector 1)) - (pextlh t8 t7 t7) - (l.q t7 (-> instance-mtx matrix vector 2)) - (pmulth r0 t2 t8) - (l.q t2 (-> instance-mtx matrix vector 3)) - (nop!) - (m.ni vf1 t1) - (nop!) - (m.ni vf2 t6) - (nop!) - (m.ni vf3 t7) - (nop!) - (m.ni vf4 t2) - (callms GENERIC-VU0-TIE-BASE-START) - (m t1 t0) - (pextlw t2 t1 t1) - (l.w vtx-out (-> out-buf info ptr-verts)) - (pcpyld t2 t2 t2) - (l.w t8 at (generic-work-offset saves time-of-day-color)) - (pmfhl.lh t7) - (l.w vu1-entry (-> instance-mtx fog data 1)) - (pextlb t8 r0 t8) - (mmi-nop!) - (psrlh t7 t7 7) - (mmi-nop!) - (pmulth r0 t7 t8) - (mmi-nop!) - (b.nz vu1-entry tie-generic-use-clipping-entry :delay (mmi-nop!)) - (nop!) - (add.i vu1-entry r0 GENERIC-VU1-DIRECT) - (b tie-generic-entry-selected :delay (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag))) - (label tie-generic-use-clipping-entry) - (nop!) - (add.i vu1-entry r0 GENERIC-VU1-CLIPPED) - (nop!) - (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag)))) - ;; Collect four transformed base points from VU0 and write them out as four gsf-vertex - ;; records, 32 bytes each: position and coordinate pair in the first quadword, normal in - ;; the second, and then the palette color overwriting the normal's unused w. - ;; - ;; The color is a plain lookup. Each vertex carries a one-byte palette index; shifting it - ;; left by two and adding the broadcast palette base gives four addresses in one packed - ;; add, and the four loads are spread through the dependent VU0 crossings so the load - ;; delays cost nothing. Because the whole loop is one iteration behind VU0, the stores at - ;; the top of the body belong to the previous group. - ;; - ;; t5 through t9, gp, ra and s1 through s5 are the shuttles carrying quadwords between VU0 - ;; and memory. Their meaning changes every few instructions and they keep physical names. - (rlet ((vtx-out :reg t1) - (palette-lanes :reg t2 :class i128) - (vtx-count :reg t3) - (cind-cursor :reg t4)) - (asm-block write-base-vertices - (label tie-generic-entry-selected) - (pmfhl.lh t6) - (mmi-nop!) - (nop!) - (mmi-nop!) - (psrlh t6 t6 7) - (mmi-nop!) - (ppacb t6 r0 t6) - (mmi-nop!) - (nop!) - (l.w t7 cind-cursor) - (add.i cind-cursor cind-cursor 4) - (s.w t6 t5) - (pextlb t7 r0 t7) - (s.w t6 t5 4) - (pextlh t7 r0 t7) - (s.w t6 t5 8) - (sll.w t7 t7 2) - (s.w t6 t5 12) - (add.w t9 t7 palette-lanes) - (mmi-nop!) - (srl32 s3 t9 0) - (m.ni t7 vf17) - (pcpyud t5 t9 r0) - (mmi-nop!) - (srl32 t6 t5 0) - (m.ni ra vf13) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (m.ni t8 vf14) - (nop!) - (l.wu t9 t9) - (nop!) - (l.wu s3 s3) - (nop!) - (l.wu t5 t5) - (nop!) - (m.ni s0 vf18) - (nop!) - (l.wu t6 t6) - (nop!) - (s.q ra vtx-out) - (add.i vtx-count vtx-count -4) - (nop! :count 3) - (b.le vtx-count r0 tie-generic-drain-base-vertices :delay (nop!)) - (label tie-generic-write-base-vertex-group) - (callms GENERIC-VU0-TIE-BASE-XFORM) - (s.q t7 vtx-out 16) - (nop!) - (s.w t9 vtx-out 28) - (nop!) - (s.q t8 vtx-out 32) - (nop!) - (s.q s0 vtx-out 48) - (nop!) - (s.w s3 vtx-out 60) - (nop! :count 7) - (mmi-nop!) - (nop!) - (l.w t8 cind-cursor) - (add.i cind-cursor cind-cursor 4) - (m.ni t7 vf19) - (pextlb t9 r0 t8) - (m.ni t8 vf20) - (pextlh t9 r0 t9) - (m.ni s3 vf15) - (sll.w ra t9 2) - (m.ni t9 vf16) - (add.w s0 ra palette-lanes) - (s.q t7 vtx-out 80) - (srl32 v0 s0 0) - (m.ni t7 vf17) - (pcpyud ra s0 r0) - (s.q s3 vtx-out 64) - (srl32 s2 ra 0) - (m.ni s1 vf13) - (nop!) - (s.q t8 vtx-out 112) - (nop!) - (s.w t5 vtx-out 92) - (nop!) - (s.w t6 vtx-out 124) - (nop!) - (s.q t9 vtx-out 96) - (nop!) - (l.wu t9 s0) - (nop!) - (l.wu s3 v0) - (nop!) - (m.ni t8 vf14) - (add.i vtx-out vtx-out 128) - (m.ni s0 vf18) - (add.i vtx-count vtx-count -4) - (l.wu t5 ra) - (nop!) - (s.q s1 vtx-out) - (b.gt vtx-count r0 tie-generic-write-base-vertex-group :delay (l.wu t6 s2)))) - ;; The drain entry publishes the group still inside the pipeline. Four of its results have - ;; to be stored straight out of the VU registers rather than through the usual crossing, - ;; because there is no following group whose crossings could carry them. - (rlet ((vtx-out :reg t1) - (num-ips :reg a2)) - (asm-block drain-base-vertices - (label tie-generic-drain-base-vertices) - (callms GENERIC-VU0-TIE-BASE-DRAIN) - (s.q t7 vtx-out 16) - (nop!) - (s.w t9 vtx-out 28) - (nop!) - (s.q t8 vtx-out 32) - (nop!) - (s.q s0 vtx-out 48) - (nop!) - (s.w s3 vtx-out 60) - (nop!) - (vnop) - (nop!) - (s.vf vf19 vtx-out 80) - (nop!) - (s.vf vf15 vtx-out 64) - (nop!) - (s.vf vf16 vtx-out 96) - (nop!) - (s.vf vf20 vtx-out 112) - (nop!) - (s.w t5 vtx-out 92) - (nop!) - (s.w t6 vtx-out 124) - (b.z num-ips tie-generic-run-effect-processors :delay (nop!)))) - ;; Interpolated points next, and they take one of two paths. The instance's morph quadword - ;; holds the pair of color weights that trade off between a vertex's own palette entry and - ;; the average of the two entries it blends toward; the second weight arrives doubled, - ;; because summing two neighbours instead of averaging them is one instruction cheaper, so - ;; it is halved here. If it comes out zero nothing is blending - the instance is at the near - ;; edge of its LOD band - and the cheaper path, which reads only the second of each point's - ;; two vectors and only the vertex's own color, is taken instead. - ;; - ;; The blended path is the interesting one. Both weights are broadcast into every halfword - ;; lane, and the blend is pmulth followed by pmaddh - four channels of two colors weighted - ;; and summed in two instructions - with the sum coming back out of HI and LO through - ;; pmfhl.lh. The weights add up to 256 rather than to one, so the shift by eight that - ;; follows is the normalization. - ;; - ;; Each interpolated point's colors are four bytes: its own index and the two it blends - ;; toward. They follow the base points' one-byte indices, rounded up to four. - (asm-block start-interp-vertices - ;; Everything the rest of the block needs is set up here, out of the instance record - ;; and the GSF info block. After this the same registers carry broadcast weights and - ;; packed palette addresses, so they go back to physical names. - (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) - (cind-cursor :reg a3) - (out-buf :reg a0 :type gsf-buffer) - (palette :reg t0) - (palette-base :reg t1) - (color-index-src :reg v1) - (num-bps :reg a1) - (num-ips :reg a2) - (vtx-base :reg t2)) - (m palette-base palette) - (l.vf vf29 (-> instance-mtx morph)) - (m palette color-index-src) - (l.s f0 (-> instance-mtx morph z)) - (add.i palette num-bps 3) - (l.s f2 (-> instance-mtx morph w)) - (add.i cind-cursor r0 -4) - (nop!) - (cvt.w.s f0 f0) - (and cind-cursor palette cind-cursor) - (cvt.w.s f2 f2) - (l.w vtx-base (-> out-buf info ptr-verts)) - (m a0 f0) - (m palette num-ips) - (m t3 f2) - (sll a2 num-bps 5) - (sra a1 t3 1) - (add cind-cursor color-index-src cind-cursor)) - (b.z a1 tie-generic-interp-without-blend :delay (add a2 t2 a2)) - (pcpyh v1 a0) - (mmi-nop!) - (pcpyld v1 v1 v1) - (s.h v1 at (generic-work-offset in-buf tie interp-job morph-z)) - (pcpyh a0 a1) - (s.h a0 at (generic-work-offset in-buf tie interp-job morph-w)) - (pcpyld a0 a0 a0) - (l.dr s5 a3) - (pextlw a1 t1 t1) - (l.dl s5 a3 7) - (pcpyld a1 a1 a1) - (l.dr s4 a3 8) - (pextlb t2 r0 s5) - (l.dl s4 a3 15) - (pextuh t1 r0 t2) - (mmi-nop!) - (pextlh t3 r0 t2) - (mmi-nop!) - (pextlb t4 r0 s4) - (mmi-nop!) - (pextuh t2 r0 t4) - (mmi-nop!) - (pextlh t5 r0 t4) - (mmi-nop!) - (sll.w t3 t3 2) - (mmi-nop!) - (sll.w t4 t1 2) - (callms GENERIC-VU0-TIE-MORPH-START) - (sll.w t6 t5 2) - (mmi-nop!) - (sll.w t5 t2 2) - (mmi-nop!) - (add.w t1 t3 a1) - (mmi-nop!) - (add.w t4 t4 a1) - (l.wu t3 t1) - (add.w t2 t6 a1) - (l.wu t9 t4) - (add.w t5 t5 a1) - (mmi-nop!) - (srl32 s4 t1 0) - (mmi-nop!) - (srl32 s3 t4 0) - (mmi-nop!) - (srl32 ra t2 0) - (add.i a2 a2 -128) - (srl32 t8 t5 0) - (add.i a3 a3 16) - (pcpyud t7 t1 r0) - (l.wu s5 t2) - (pcpyud t6 t4 r0) - (l.wu s2 t5) - (pcpyud t4 t2 r0) - (l.wu s4 s4) - (pcpyud t5 t5 r0) - (l.wu s3 s3) - (pextlw t9 t9 t3) - (l.wu ra ra) - (pextlw t3 s2 s5) - (l.wu s2 t8) - (pextlw s5 s3 s4) - (l.wu t8 t7) - (pextlw t7 s2 ra) - (l.wu ra t6) - (pextlb t6 r0 t9) - (l.wu t9 t4) - (pextlb t4 r0 s5) - (l.wu s5 t5) - (pextlw ra ra t8) - (m.ni t5 vf17) - (pextlw t8 s5 t9) - (mmi-nop!) - (pextlb t9 r0 ra) - (mmi-nop!) - (add.h t9 t4 t9) - (m.ni t4 vf13) - (pmulth r0 t9 a0) - (mmi-nop!) - (pextlb t3 r0 t3) - (s.q t5 a2 144) - (pmaddh r0 t6 v1) - (s.q t4 a2 128) - (pextlb t5 r0 t7) - (l.dr t1 a3) - (pextlb t7 r0 t8) - (l.dl t1 a3 7) - (pmfhl.lh t6) - (m.ni t4 vf18) - (add.h t5 t5 t7) - (l.dr t2 a3 8) - (psrlh t6 t6 8) - (l.dl t2 a3 15) - (ppacb t6 r0 t6) - (s.q t4 a2 176) - (srl32 t4 t6 0) - (add.i t0 t0 -4) - (pmulth r0 t5 a0) - (m.ni t5 vf14) - (pextlb t1 r0 t1) - (s.w t6 a2 156) - (pmaddh r0 t3 v1) - (s.w t4 a2 188) - (pextlb t3 r0 t2) - (s.q t5 a2 160) - (pextuh t2 r0 t1) - (mmi-nop!) - (pmfhl.lh t6) - (m.ni t5 vf19) - (pextlh t4 r0 t1) - (mmi-nop!) - (psrlh t1 t6 8) - (mmi-nop!) - (ppacb t6 r0 t1) - (s.q t5 a2 208) - (srl32 t1 t6 0) - (s.w t6 a2 220) - (pextuh t5 r0 t3) - (m.ni t6 vf15) - (pextlh t7 r0 t3) - (mmi-nop!) - (sll.w t3 t4 2) - (s.q t6 a2 192) - (sll.w t4 t2 2) - (mmi-nop!) - (sll.w t6 t7 2) - (mmi-nop!) - (sll.w t5 t5 2) - (mmi-nop!) - (add.w s5 t3 a1) - (m.ni t2 vf20) - (add.w t4 t4 a1) - (l.wu t3 s5) - (add.w s4 t6 a1) - (l.wu t9 t4) - (b.le t0 r0 tie-generic-drain-morphed-group :delay (add.w t5 t5 a1))) - ;; Four blending vertices per pass. Same shape as the base-point loop - four records out, the - ;; palette read as four packed addresses, everything one group behind VU0 - with the two-entry - ;; color blend folded into the same instruction stream. - ;; - ;; Unnamed on purpose, as above: this is the densest interleaving in the file and every general - ;; register is reused two or three times per pass. The only registers with a lifetime worth a - ;; name are a3, the color-index cursor, and a2, the vertex output cursor; both step by a fixed - ;; amount at the bottom of the loop. - (asm-block write-morphed-interp-vertices - (label tie-generic-write-morphed-group) - (srl32 s3 s5 0) - (callms GENERIC-VU0-TIE-MORPH-XFORM) - (srl32 s0 t4 0) - (srl32 s2 s4 0) - (srl32 t8 t5 0) - (add.i a2 a2 128) - (pcpyud t7 s5 r0) - (l.wu ra s4) - (pcpyud t6 t4 r0) - (l.wu s1 t5) - (pcpyud t4 s4 r0) - (l.wu s3 s3) - (pcpyud t5 t5 r0) - (l.wu s0 s0) - (pextlw t9 t9 t3) - (l.wu s2 s2) - (pextlw t3 s1 ra) - (l.wu s1 t8) - (pextlw ra s0 s3) - (l.wu t8 t7) - (pextlw t7 s1 s2) - (l.wu s3 t6) - (pextlb t6 r0 t9) - (l.wu t9 t4) - (pextlb t4 r0 ra) - (l.wu t5 t5) - (pextlw t8 s3 t8) - (s.q t2 a2 112) - (pextlw t2 t5 t9) - (m.ni t5 vf17) - (pextlb t8 r0 t8) - (s.w t1 a2 124) - (add.h t1 t4 t8) - (mmi-nop!) - (pmulth r0 t1 a0) - (m.ni t4 vf13) - (pextlb t1 r0 t3) - (s.q t5 a2 144) - (pmaddh r0 t6 v1) - (l.dr s5 a3 16) - (pextlb t3 r0 t7) - (s.q t4 a2 128) - (pextlb t2 r0 t2) - (l.dl s5 a3 23) - (pmfhl.lh t4) - (l.dr s4 a3 24) - (add.h t2 t3 t2) - (m.ni t5 vf18) - (psrlh t3 t4 8) - (l.dl s4 a3 31) - (ppacb t3 r0 t3) - (s.q t5 a2 176) - (srl32 t5 t3 0) - (add.i t0 t0 -4) - (pmulth r0 t2 a0) - (mmi-nop!) - (pextlb t4 r0 s5) - (m.ni t6 vf14) - (pmaddh r0 t1 v1) - (s.w t3 a2 156) - (pextlb t3 r0 s4) - (s.w t5 a2 188) - (pextuh t2 r0 t4) - (s.q t6 a2 160) - (pmfhl.lh t6) - (m.ni t1 vf16) - (pextlh t4 r0 t4) - (m.ni t5 vf19) - (psrlh t6 t6 8) - (s.q t1 a2 96) - (ppacb t6 r0 t6) - (s.q t5 a2 208) - (srl32 t1 t6 0) - (add.i a3 a3 16) - (pextuh t5 r0 t3) - (mmi-nop!) - (pextlh t7 r0 t3) - (m.ni t8 vf15) - (sll.w t3 t4 2) - (s.w t6 a2 220) - (sll.w t4 t2 2) - (s.q t8 a2 192) - (sll.w t6 t7 2) - (mmi-nop!) - (sll.w t5 t5 2) - (mmi-nop!) - (add.w s5 t3 a1) - (m.ni t2 vf20) - (add.w t4 t4 a1) - (l.wu t3 s5) - (add.w s4 t6 a1) - (l.wu t9 t4) - (b.gt t0 r0 tie-generic-write-morphed-group :delay (add.w t5 t5 a1))) - ;; Publish the last collapsing group. - (asm-block drain-morphed-interp-vertices - (label tie-generic-drain-morphed-group) - (nop!) - (callms GENERIC-VU0-TIE-MORPH-DRAIN) - (nop!) - (s.q t2 a2 240) - (nop!) - (m v1 vf16) - (nop!) - (s.w t1 a2 252) - (b tie-generic-run-effect-processors :delay (s.q v1 a2 224))) - ;; The unblended path. Only the second of each point's two vectors is transformed and only the - ;; vertex's own palette entry is read, so the four indices are four plain byte loads four bytes - ;; apart instead of a packed gather. - ;; - ;; No aliases below, and deliberately so: every register in this loop is a shuttle. a0 through - ;; a3, t0 through t7 and s4/s5 each carry a palette address, then a loaded color, then a - ;; quadword on its way out of VU0, within a dozen instructions, and there is no name that would - ;; be true for more than one of those. The cursors are v1 for the color indices and a2 for the - ;; output. - (asm-block write-authored-interp-vertices - (label tie-generic-interp-without-blend) - (callms GENERIC-VU0-TIE-INTERP-START) - (nop!) - (nop!) - (s.h a0 at (generic-work-offset in-buf tie interp-job morph-z)) - (nop!) - (s.h a1 at (generic-work-offset in-buf tie interp-job morph-w)) - (nop! :count 17) - (l.bu v1 a3) - (nop!) - (l.bu a0 a3 4) - (nop!) - (l.bu a1 a3 8) - (sll v1 v1 2) - (l.bu t2 a3 12) - (sll a0 a0 2) - (nop!) - (sll t3 a1 2) - (nop!) - (sll t4 t2 2) - (m t2 vf17) - (add s5 v1 t1) - (add.i v1 a3 16) - (add a1 a0 t1) - (nop!) - (add s4 t3 t1) - (nop!) - (add a0 t4 t1) - (nop!) - (nop!) - (m.ni t7 vf13) - (nop! :count 5) - (m.ni a3 vf18) - (nop!) - (l.wu t6 s5) - (nop!) - (l.wu t3 a1) - (nop!) - (l.wu t4 s4) - (nop!) - (m.ni t5 vf14) - (nop!) - (l.wu a0 a0) - (add.i a1 t0 -4) - (s.q t7 a2) - (nop!) - (m.ni t0 vf19) - (nop!) - (s.q t2 a2 16) - (nop!) - (s.w t6 a2 28) - (b.le a1 r0 tie-generic-drain-interp-without-blend :delay (s.q a3 a2 48))) - ;; Four more, until the interpolated points run out. Same allocation as the block above. - (asm-block write-authored-interp-vertices-loop - (label tie-generic-interp-without-blend-loop) - (callms GENERIC-VU0-TIE-INTERP-XFORM) - (s.w t3 a2 60) - (nop!) - (s.q t5 a2 32) - (nop!) - (s.q t0 a2 80) - (nop!) - (s.w t4 a2 92) - (nop! :count 5) - (l.bu a3 v1) - (nop!) - (l.bu t0 v1 4) - (nop!) - (l.bu t2 v1 8) - (sll t3 a3 2) - (l.bu a3 v1 12) - (sll t6 t0 2) - (m t0 vf15) - (sll t4 t2 2) - (m.ni t2 vf20) - (sll t5 a3 2) - (m.ni a3 vf17) - (add s5 t3 t1) - (add.i v1 v1 16) - (add t3 t6 t1) - (m.ni t6 vf16) - (add s4 t4 t1) - (s.q t0 a2 64) - (add t0 t5 t1) - (s.q t2 a2 112) - (nop!) - (m.ni t2 vf13) - (nop!) - (s.w a0 a2 124) - (nop!) - (s.q t6 a2 96) - (nop!) - (m.ni t6 vf18) - (nop!) - (l.wu t7 s5) - (nop!) - (l.wu t3 t3) - (nop!) - (l.wu t4 s4) - (nop!) - (m.ni t5 vf14) - (add.i a2 a2 128) - (l.wu a0 t0) - (add.i a1 a1 -4) - (s.q t2 a2) - (nop!) - (m.ni t0 vf19) - (nop!) - (s.q a3 a2 16) - (nop!) - (s.w t7 a2 28) - (b.gt a1 r0 tie-generic-interp-without-blend-loop :delay (s.q t6 a2 48))) - ;; Publish the last unblended group and the fragment is expanded. - (asm-block drain-authored-interp-vertices - (label tie-generic-drain-interp-without-blend) - (nop!) - (callms GENERIC-VU0-TIE-INTERP-DRAIN) - (nop!) - (s.w t3 a2 60) - (nop!) - (s.q t5 a2 32) - (nop!) - (s.q t0 a2 80) - (nop!) - (s.w t4 a2 92) - (nop!) - (m v1 vf15) - (nop!) - (m.ni a1 vf20) - (nop!) - (m.ni a3 vf16) - (nop!) - (s.q v1 a2 64) - (nop!) - (s.q a1 a2 112) - (nop!) - (s.w a0 a2 124) - (nop!) - (s.q a3 a2 96)) - ;; The expanded fragment is in main memory now; these four build the packet from it. The - ;; addresses are read out of the shadow block rather than resolved through the symbol table, - ;; because this function runs with the GOAL symbol register holding something else. - ;; - ;; prepare-dma-double lays down two packet headers and reserves the six stream cursors, - ;; envmap-dproc fills the reflected coordinates, interp-dproc blends the subdivision vertices' - ;; attributes, and no-light-dproc copies positions and normals and combines the colors. TIE - ;; always wants exactly this set, so unlike mercneric there is nothing to choose between. - ;; - ;; is-envmap is cleared even though this is the environment-map path: it selects which GIF tag - ;; template the base pass takes, and the base pass here is ordinary geometry. - (rlet ((from-spr :reg a2 :type dma-bank-spr)) - (asm-block run-effect-processors - (label tie-generic-run-effect-processors) - (m v1 r0) - (l.w v1 at (generic-work-offset in-buf tie control ptr-shaders)) - (l.w a0 at (generic-work-offset in-buf tie shadow out-buf)) - (add.i a1 at (generic-work-offset fx-buf work consts envmap shader)) - (s.w v1 at (generic-work-offset saves ptr-shaders)) - (s.w a0 at (generic-work-offset saves gsf-buf)) - (s.w a1 at (generic-work-offset saves ptr-env-shader)) - (s.w r0 at (generic-work-offset saves is-envmap)) - (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-prepare-dma-double)) - (jalr ra v1 :delay (sllv v0 ra r0)) - (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-envmap-dproc)) - (jalr ra v1 :delay (sllv v0 ra r0)) - (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-interp-dproc)) - (jalr ra v1 :delay (sllv v0 ra r0)) - (l.w v1 at (generic-work-offset in-buf tie shadow calls generic-no-light-dproc)) - (jalr ra v1 :delay (sllv v0 ra r0)) - (l.w v1 at (generic-work-offset saves cur-outbuf)) - (l.w a0 at (generic-work-offset saves qwc)) - (m a3 v1) - (nop!) - (lui at #x7000) - (lui from-spr #x1000) - (l.wu a1 at (generic-work-offset saves basep)) - (ori from-spr from-spr #xd000) - (l.w t1 (-> from-spr chcr)) - (nop!) - (add.i t0 at (generic-work-offset saves from-spr-waits)) - (and.i a3 a3 #x3fff) - (and.i t1 t1 DMA-CHCR-STR) - (nop!) - (b.z t1 tie-generic-return-output-buffer :delay (nop!)) - (m t1 from-spr) - (nop!))) - ;; The finished packet is in one of the two scratchpad output buffers and the other one is - ;; still being read out by the fromSPR channel. Wait for that, and charge the stalls to the - ;; work area so a frame spent waiting on DMA says so. - (rlet ((from-spr :reg t1 :type dma-bank-spr) - (stall-count-ptr :reg t0) - (stall-count :reg t2) - (status :reg t3)) - (asm-block wait-for-output-drain - (label tie-generic-wait-for-output-drain) - (l.w stall-count stall-count-ptr) - (nop!) - (l.w status (-> from-spr chcr)) - (nop!) - (and.i status status DMA-CHCR-STR) - (add.i stall-count stall-count 1) - (b.nz status tie-generic-wait-for-output-drain - :delay (s.w stall-count stall-count-ptr)) - (m stall-count-ptr r0))) - ;; Hand the buffer to the channel, advance the write cursor in the frame's DMA buffer by what - ;; it will deliver, and switch to the other buffer. Nothing waits for this transfer here; the - ;; wait above is what makes that safe. - (rlet ((from-spr :reg a2 :type dma-bank-spr) - (out-bank :reg a3) ;; the scratchpad buffer being handed over - (chcr-start :reg a3) ;; then the value that starts the channel - (packet-qwc :reg a0) - (next-basep :reg a0) ;; then the advanced main-memory cursor - (dma-cursor :reg a1) - (cur-outbuf :reg v1) - (end-of-chain :reg v1)) ;; then the flag that ends the pass - (asm-block return-output-buffer - (label tie-generic-return-output-buffer) - (sll t0 packet-qwc 4) - (s.w out-bank (-> from-spr sadr)) - (nop!) - (s.w dma-cursor (-> from-spr madr)) - (add.i chcr-start r0 DMA-CHCR-STR) - (s.w packet-qwc (-> from-spr qwc)) - (add next-basep dma-cursor t0) - (s.w chcr-start (-> from-spr chcr)) - (nop!) - (s.w next-basep at (generic-work-offset saves basep)) - (m next-basep r0) - (xor.i cur-outbuf cur-outbuf GENERIC-OUTBUF-FLIP) - (s.w cur-outbuf at (generic-work-offset saves cur-outbuf)) - (nop!) - (l.w end-of-chain at (generic-work-offset in-buf tie shadow end-of-chain)) - (nop!) - (nop!) - (b.z end-of-chain tie-generic-next-chain-entry :delay (nop!)))) - ;; End of chain, or out of DMA buffer. - (asm-block convert-done - (label tie-generic-convert-done) - (m v0 r0) - (l.d ra at (generic-work-offset storage data 0)) - (l.q gp at (generic-work-offset storage data 7)) - (l.q s5 at (generic-work-offset storage data 6)) - (l.q s4 at (generic-work-offset storage data 5)) - (l.q s3 at (generic-work-offset storage data 4)) - (l.q s2 at (generic-work-offset storage data 3)) - (l.q s1 at (generic-work-offset storage data 2)) - (l.q s0 at (generic-work-offset storage data 1)) - (jr ra :delay (add.i sp sp 128)) - (nop!) - (nop!) - (nop!)) +(defun generic-tie-dma-to-spad-sync (a b) + ;; not ported + (none)) - ) - - (defun generic-tie-convert-proc () - "Expand the fragment described by generic-tie-control into the GSF buffer: the index and kick - list, the interpolation job, and one 32-byte vertex per point with its position, normal, - coordinate pair and palette color. Leaves the environment tint and the VU1 entry selection in - the shared constant block." - (declare (asm-func none) (allow-saved-regs)) - ;; One fragment, one instance, and the only expensive thing the Generic TIE path does. Nothing - ;; here is on the fast path of the game; the point of the code is to be a faithful expansion, and - ;; the interesting part is the division of labour with VU0. - ;; - ;; TIE stores a fragment's vertices in two classes. Base points exist at every level of detail; - ;; interpolated points exist only because an edge was subdivided, and each one carries the - ;; displacement that puts it back on that edge as the instance recedes. Both are int16 positions - ;; and packed coordinate pairs, and both have to become float quadwords before GENERIC can use - ;; them, so the EE widens them with packed interleaves and shifts and crosses them into VU0 four - ;; at a time. VU0 stages them into three arrays sized from the two counts, transforms them by the - ;; instance matrix, and hands them back; the EE writes the results out and fetches each vertex's - ;; color from the palette while it waits. - ;; - ;; Everything is pipelined one group of four behind VU0, which is why almost every loop body - ;; starts with a call and ends by staging the group the *next* call will work on, and why each - ;; class of point ends with a drain entry that has nothing to stage. - ;; - ;; This entry saves on the stack. The fused loop in generic-tie-convert saves the same - ;; registers into the scratchpad work area instead, which is the only real difference - ;; between the two. - (asm-block save-registers - (add.i sp sp -112) - (s.d ra sp) - (s.q s1 sp 16) - (s.q s2 sp 32) - (s.q s3 sp 48) - (s.q s4 sp 64) - (s.q s5 sp 80) - (s.q gp sp 96)) - ;; Everything about the fragment now in scratchpad is described by generic-tie-control, - ;; which the chain parser filled in. Read all of it at once - the loads are two cycles - ;; apart and there is nothing else to do with the gaps - and pick up the counts out of the - ;; fragment header. An instance-only chain entry needs none of the expansion that follows, - ;; because the model is still sitting in the work buffer from last time, so it jumps - ;; straight to the transform. - (rlet ((control :reg t2 :type generic-tie-control) - (out-buf :reg a0 :type gsf-buffer) - (header :reg t1 :type generic-tie-header) - (instance-mtx :reg a3 :type generic-tie-matrix) - (point-src :reg t3) - (index-src :reg t5) - (kick-src :reg t6) - (normal-src :reg t4) - (color-index-src :reg v1) - (spare :reg v1) ;; the control cursors this pass does not need land here - (palette :reg t0) - (entry-kind :reg a1) - (num-bps :reg a1) - (num-vtxs :reg a2) - (instance-only :reg a2) - (num-ips :reg a2) - (num-dps :reg t8)) - ;; Three of the fifteen control words are read into the same register and thrown away: - ;; the shader, run-control and interpolation cursors belong to the packet builder and to - ;; generic-interp-dproc, not to this pass. - (asm-block load-fragment-tables - (lui at #x7000) - (add.i control at (generic-work-offset in-buf tie control)) - (l.w out-buf at (generic-work-offset in-buf tie shadow out-buf)) - (nop!) - (l.w entry-kind (-> control tie-type)) - (nop!) - (l.w spare (-> control ptr-shaders)) - (nop!) - (l.w spare (-> control ptr-runctrl)) - (nop!) - (l.w point-src (-> control ptr-verts)) - (nop!) - (l.w header (-> control ptr-generic)) - (nop!) - (l.w spare (-> control ptr-interp)) - (nop!) - (l.w index-src (-> control ptr-dps)) - (nop!) - (l.w kick-src (-> control ptr-kicks)) - (nop!) - (l.w normal-src (-> control ptr-normals)) - (nop!) - (l.w instance-mtx (-> control ptr-mtxs)) - (nop!) - (l.w color-index-src (-> control ptr-cinds)) - (nop!) - (l.w palette (-> control ptr-palette)) - (nop!) - (l.hu num-vtxs (-> header gsf-header num-vtxs)) - (nop!) - (l.hu num-dps (-> header gsf-header num-dps)) - (add.i instance-only entry-kind -2) - (l.bu num-bps (-> header num-bps)) - (b.z instance-only tie-generic-proc-transform-fragment :delay (l.bu num-ips (-> header num-ips))))) - ;; Divide the GSF buffer up for this fragment and publish the divisions in its info block, - ;; so that neither the effect processors nor the packet builder has to work them out again: - ;; the index/kick list first, the expanded vertices after it rounded up to a quadword, and - ;; the environment-map attributes after those. gsf-header is copied across whole. - ;; - ;; The tail of the block builds the two lane masks the kick expansion needs. 513 grown into - ;; 0x8040201008040201 puts one distinct bit in each byte lane; 257 grown the same way puts - ;; 01 in all sixteen. See generic-tie-decompress for what they are for - the loop that - ;; follows is that function inlined. - ;; - ;; ra, gp, t7 through t9 and s2 through s5 are address arithmetic and mask temporaries here - ;; and change meaning every few instructions, so they keep their physical names. - (rlet ((out-buf :reg a0 :type gsf-buffer) - (header :reg t1 :type generic-tie-header) - (num-dps :reg t8) - (ik-bytes :reg t7) - (ik-out :reg t7) - (verts-out :reg t9) - (header-quad :reg ra :class i128) - (fx-out :reg ra)) - (asm-block publish-draw-point-arrays - (add ik-bytes num-dps num-dps) - (add.i t9 r0 -16) - (add.i ra ik-bytes 15) - (add.i ik-out out-buf 32) ;; the work area starts after info and header - (and t9 ra t9) - (l.q header-quad (-> header gsf-header)) - (add t9 t7 t9) - (s.q header-quad (-> out-buf header)) - (sll ra t8 5) - (s.w ik-out (-> out-buf info ptr-iks)) - (add fx-out verts-out ra) - (s.w verts-out (-> out-buf info ptr-verts)) - (add.i t8 t8 7) - (s.w fx-out (-> out-buf info ptr-fx)) - (sra t9 t8 3) - (nop!) - (add.i t8 r0 2) - (nop!) - (mult3 gp t8 t9) - (m ra t6) - (m t6 t8) - (m t7 t7) - (add.i t8 r0 513) - (add.i t9 r0 257) - (sll s4 t8 18) - (sll s5 t9 16) - (or t8 t8 s4) - (or t9 t9 s5) - (dsll32 s4 t8 4) - (dsll32 s5 t9 0) - (or t8 t8 s4) - (or t9 t9 s5) - (pcpyld t8 t8 t8) - (mmi-nop!) - (pcpyld t9 t9 t9) - (mmi-nop!) - (add gp gp ra) - (mmi-nop!) - (l.hu s5 ra) - (add ra ra t6) - (nop!) - (m t5 t5) - (nop!) - (pextlb s5 s5 s5) - (b tie-generic-proc-expand-kick-group :delay (pextlb s4 s5 s5)))) - ;; generic-tie-decompress, inlined: sixteen draw points per pass, each one an index byte - ;; married to a kick bit that has been spread out to a whole byte. The loop is rotated, so - ;; the second output quadword of a pass is stored at the top of the next one. - ;; - ;; The pass count is generous, and why it is written this way is not established. Sixteen draw - ;; points are finished per pass, but the bound steps one halfword of kick bits per pass across - ;; ceil(num-dps / 8) halfwords, so it runs about twice as many passes as there are draw points and - ;; writes off the end of the list. It cannot get far: - ;; the overshoot is at most another 2 * num-dps bytes, the vertex array immediately behind the - ;; list is 32 bytes per vertex against the list's 2 per draw point, and the vertex array is - ;; filled a few hundred instructions later regardless. So the spill stays inside the buffer and - ;; inside data that is about to be overwritten. - (asm-block interleave-indices-and-kicks - (label tie-generic-proc-next-kick-group) - (add.i t5 t5 16) - (nop!) - (add ra ra t6) - (add.i t7 t7 32) - (pextlb s4 s4 s4) - (s.q s5 t7 -16) - (label tie-generic-proc-expand-kick-group) - (pextlb s5 s4 s4) - (mmi-nop!) - (and.q s5 s5 t8) - (mmi-nop!) - (pceqb s4 s5 t8) - (l.q s5 t5) - (and.q s2 s4 t9) - (mmi-nop!) - (pextlb s4 s2 s5) - (l.hu s3 ra) - (pextub s5 s2 s5) - (s.q s4 t7) - (b.ne ra gp tie-generic-proc-next-kick-group :delay (pextlb s4 s3 s3)) - (nop!) - (s.q s5 t7 16)) - ;; Leave a job behind for generic-interp-dproc if this fragment has any subdivision - ;; vertices: they occupy num-ips slots starting right after the base points, and the blend - ;; data they need is the interpolation table in the fragment. A fragment with none clears - ;; the pointer instead, which is how the processor knows to do nothing. - (rlet ((interp-job :reg t5 :type generic-interp-job) - (control :reg t2 :type generic-tie-control) - (interp-data :reg t2) - (num-bps :reg a1) - (num-ips :reg a2)) - (asm-block publish-interp-job - (m interp-job r0) - (b.z num-ips tie-generic-proc-no-interp-job :delay (nop!)) - (add.i t6 num-ips 7) - (add.i interp-job at (generic-work-offset in-buf tie interp-job)) - (nop!) - (l.w interp-data (-> control ptr-interp)) - (sra t6 t6 3) - (s.h r0 (-> interp-job job-type)) - (nop!) - (s.h num-bps (-> interp-job first)) - (nop!) - (s.h num-ips (-> interp-job num)) - (nop!) - (s.w t2 (-> interp-job ptr-data)) - (nop!) - (s.w interp-job at (generic-work-offset saves ptr-interp-job)) - (b tie-generic-proc-interp-job-ready :delay (nop!)) - (label tie-generic-proc-no-interp-job) - (nop!) - (s.w r0 at (generic-work-offset saves ptr-interp-job)) - (nop!))) - ;; Hand the fragment's normals to VU0 four at a time. Each one is three signed bytes, so a - ;; quadword of them is four normals; pextub/pextlb move each byte into the top of a - ;; halfword and pextuh/pextlh into the top of a word, and then one arithmetic shift by 19 - ;; leaves the byte scaled by 32 with its sign intact - which the VU reads as 1.12 fixed - ;; point, giving a normal in [-1, 1). Two shifts and two interleaves per four normals, and - ;; no multiplies. - ;; - ;; The count is base points plus interpolated points rounded up to four, so the last pass - ;; can read a little past the table. vi08 and vi09 carry the two counts into VU0, which - ;; sizes its staging arrays from them; vi09 is written twice because the first write is too - ;; close to the call for the transfer to have landed. - (rlet ((normal-src :reg t4) - (normal-cursor :reg t2) - (normal-end :reg t4) - (num-bps :reg a1) - (num-ips :reg a2)) - (asm-block stage-normals - (label tie-generic-proc-interp-job-ready) - (add t5 num-bps num-ips) - (m normal-cursor normal-src) - (add.i t4 t5 3) - (ctc2.i vi9 num-ips) - (sra t5 t4 2) - (l.q t4 normal-cursor) - (sll t5 t5 4) - (add.i normal-cursor normal-cursor 16) - (pextub t7 t4 r0) - (ctc2.i vi8 num-bps) - (pextlb t6 t4 r0) - (ctc2.i vi9 num-ips) - (add normal-end normal-cursor t5) - (callms GENERIC-VU0-TIE-SETUP) - (label tie-generic-proc-stage-normal-group) - (pextuh t5 t7 r0) - (mmi-nop!) - (pextlh t7 t7 r0) - (mmi-nop!) - (pextuh t8 t6 r0) - (mmi-nop!) - (pextlh t6 t6 r0) - (mmi-nop!) - (sra.w t9 t6 19) - (l.q t6 normal-cursor) - (sra.w t8 t8 19) - (m.ni vf5 t9) - (sra.w t7 t7 19) - (m.ni vf6 t8) - (sra.w t5 t5 19) - (m.ni vf7 t7) - (pextub t7 t6 r0) - (m.ni vf8 t5) - (pextlb t6 t6 r0) - (mmi-nop!) - (nop!) - (callms GENERIC-VU0-TIE-NORMALS) - (b.ne normal-cursor normal-end tie-generic-proc-stage-normal-group :delay (add.i normal-cursor normal-cursor 16)) - (nop!))) - ;; Now the positions, again four at a time. A base point is sixteen bytes: three signed - ;; 16-bit coordinates, an output offset, the texture coordinate pair, and a second output - ;; offset. The doubleword load takes the position and the word load at +8 takes the - ;; coordinates; the shift by 10 leaves each coordinate multiplied by 64, which is the scale - ;; the instance matrix is built to undo. - ;; - ;; The loop is software pipelined against VU0: the call at the top of the body works on the - ;; group staged by the previous iteration while this one crosses the next group over. That - ;; is why the last group is staged outside the loop and why there is a call after it. - (rlet ((point-src :reg t3) - (point-cursor :reg t2) - (num-bps :reg a1) - (remaining :reg t4)) - (asm-block stage-base-points - (m point-cursor point-src) - (b.z num-bps tie-generic-proc-stage-interp-pair :delay (add.i remaining num-bps -4)) - (b.le remaining r0 tie-generic-proc-base-point-tail :delay (nop!)) - (m point-cursor point-src) - (l.d t5 t3) - (add.i remaining num-bps -8) - (l.d t6 t3 16) - (pextlh t7 t5 r0) - (l.d t5 t3 32) - (pextlh ra t6 r0) - (l.d t6 t3 48) - (pextlh t8 t5 r0) - (l.wu t5 t3 8) - (pextlh t9 t6 r0) - (l.wu t6 t3 24) - (sra.w gp t7 10) - (l.wu t7 t3 40) - (sra.w ra ra 10) - (l.wu t3 t3 56) - (sra.w t8 t8 10) - (m.ni vf1 gp) - (sra.w t9 t9 10) - (m.ni vf2 ra) - (add.i point-cursor point-cursor 64) - (m.ni vf3 t8) - (nop!) - (m.ni vf4 t9) - (nop!) - (m.ni vf13 t5) - (nop!) - (m.ni vf14 t6) - (nop!) - (m.ni vf15 t7) - (b.le t4 r0 tie-generic-proc-stage-last-base-points :delay (m.ni vf16 t3)) - (label tie-generic-proc-stage-base-point-group) - (callms GENERIC-VU0-TIE-BASE-POINTS) - (l.d t3 point-cursor) - (add.i t4 t4 -4) - (l.d t5 point-cursor 16) - (pextlh t8 t3 r0) - (l.d t3 point-cursor 32) - (pextlh t9 t5 r0) - (l.d t5 point-cursor 48) - (pextlh t6 t3 r0) - (l.wu t3 point-cursor 8) - (pextlh t7 t5 r0) - (l.wu t5 point-cursor 24) - (sra.w ra t8 10) - (l.wu t8 point-cursor 40) - (sra.w t9 t9 10) - (l.wu gp point-cursor 56) - (sra.w t6 t6 10) - (m.ni vf1 ra) - (sra.w t7 t7 10) - (m.ni vf2 t9) - (add.i point-cursor point-cursor 64) - (m.ni vf3 t6) - (nop!) - (m.ni vf4 t7) - (nop!) - (m.ni vf13 t3) - (nop!) - (m.ni vf14 t5) - (nop!) - (m.ni vf15 t8) - (b.gt t4 r0 tie-generic-proc-stage-base-point-group :delay (m.ni vf16 gp)) - (label tie-generic-proc-stage-last-base-points) - (callms GENERIC-VU0-TIE-BASE-POINTS) - (nop!))) - ;; The four-wide pipeline does not care that base points and interpolated points are - ;; different sizes, so the group that straddles the boundary between them is staged by a - ;; VU0 entry written for that exact shape. num-bps modulo four picks it: one base point - ;; plus one interpolated point, two plus one, three on their own, or - when the remainder - ;; is zero, or when the fragment has no interpolated points at all - one more plain group - ;; of four. - ;; - ;; The last case reads up to three points past the end of the array when the count is not a - ;; multiple of four. VU0 transforms them and the write loop, which counts in fours as well, - ;; stores them; both overruns land inside the GSF buffer's slack. - (rlet ((point-cursor :reg t2) - (num-bps :reg a1) - (num-ips :reg a2)) - (asm-block stage-boundary-group - (label tie-generic-proc-base-point-tail) - (b.z num-ips tie-generic-proc-stage-final-base-group :delay (and.i t3 num-bps 3)) - (b.z t3 tie-generic-proc-stage-final-base-group :delay (add.i t3 t3 -1)) - (b.z t3 tie-generic-proc-stage-tail-1bp-1ip :delay (add.i t3 t3 -1)) - (b.z t3 tie-generic-proc-stage-tail-2bp-1ip :delay (add.i t3 t3 -1)) - (b tie-generic-proc-stage-tail-3bp :delay (nop!)) - (label tie-generic-proc-stage-tail-1bp-1ip) - (nop!) - (l.d t3 point-cursor) - (nop!) - (l.d t4 point-cursor 16) - (pextlh t3 t3 r0) - (l.d t6 point-cursor 24) - (pextlh t4 t4 r0) - (l.wu t5 point-cursor 8) - (pextlh t6 t6 r0) - (l.wu t7 point-cursor 32) - (nop! :count 4) - (sra.w t3 t3 10) - (m.ni vf13 t5) - (sra.w t4 t4 10) - (m.ni vf14 t7) - (sra.w t5 t6 10) - (m.ni vf1 t3) - (nop!) - (m.ni vf2 t4) - (add.i point-cursor point-cursor 40) - (m.ni vf3 t5) - (b tie-generic-proc-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-1BP-1IP)) - (label tie-generic-proc-stage-tail-2bp-1ip) - (nop!) - (l.d t4 point-cursor) - (nop!) - (l.d t3 point-cursor 16) - (pextlh t8 t4 r0) - (l.d t5 point-cursor 32) - (pextlh t4 t3 r0) - (l.d t3 point-cursor 40) - (pextlh t5 t5 r0) - (l.wu t6 point-cursor 8) - (pextlh t7 t3 r0) - (l.wu t3 point-cursor 24) - (sra.w t9 t8 10) - (l.wu t8 point-cursor 48) - (sra.w t4 t4 10) - (m.ni vf13 t6) - (sra.w t5 t5 10) - (m.ni vf1 t9) - (sra.w t6 t7 10) - (m.ni vf2 t4) - (add.i point-cursor point-cursor 56) - (m.ni vf3 t5) - (nop!) - (m.ni vf4 t6) - (nop!) - (m.ni vf14 t3) - (nop!) - (m.ni vf15 t8) - (b tie-generic-proc-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-2BP-1IP)) - (label tie-generic-proc-stage-tail-3bp) - (nop!) - (l.d t4 point-cursor) - (nop!) - (l.d t3 point-cursor 16) - (pextlh t7 t4 r0) - (l.d t4 point-cursor 32) - (pextlh t5 t3 r0) - (l.wu t3 point-cursor 8) - (pextlh t6 t4 r0) - (l.wu t4 point-cursor 24) - (sra.w t7 t7 10) - (l.wu t8 point-cursor 40) - (sra.w t5 t5 10) - (m.ni vf13 t3) - (sra.w t3 t6 10) - (m.ni vf1 t7) - (nop!) - (m.ni vf2 t5) - (add.i point-cursor point-cursor 48) - (m.ni vf3 t3) - (nop!) - (m.ni vf14 t4) - (nop!) - (m.ni vf15 t8) - (b tie-generic-proc-stage-interp-pair :delay (callms GENERIC-VU0-TIE-TAIL-3BP)) - (label tie-generic-proc-stage-final-base-group) - (nop!) - (l.d t3 point-cursor) - (nop!) - (l.d t4 point-cursor 16) - (pextlh t7 t3 r0) - (l.d t3 point-cursor 32) - (pextlh t8 t4 r0) - (l.d t4 point-cursor 48) - (pextlh t5 t3 r0) - (l.wu t3 point-cursor 8) - (pextlh t6 t4 r0) - (l.wu t4 point-cursor 24) - (sra.w t9 t7 10) - (l.wu t7 point-cursor 40) - (sra.w t8 t8 10) - (l.wu ra point-cursor 56) - (sra.w t5 t5 10) - (m.ni vf1 t9) - (sra.w t6 t6 10) - (m.ni vf2 t8) - (add.i point-cursor point-cursor 64) - (m.ni vf3 t5) - (nop!) - (m.ni vf4 t6) - (nop!) - (m.ni vf13 t3) - (nop!) - (m.ni vf14 t4) - (nop!) - (m.ni vf15 t7) - (nop!) - (m.ni vf16 ra) - (b.z num-ips tie-generic-proc-staging-done :delay (callms GENERIC-VU0-TIE-BASE-POINTS)))) - ;; Whatever interpolated points are left go two at a time, because each one is twenty-four - ;; bytes: the vector the morph weight scales, the vector it is added to, and the coordinate - ;; pair. Three of them fill two quadwords, which is why the stride is 48 and not 32. - (rlet ((point-cursor :reg t2) - (num-ips :reg a2) - (remaining :reg t3)) - (asm-block stage-interp-points - (label tie-generic-proc-stage-interp-pair) - (nop!) - (l.d t4 point-cursor) - (add.i remaining num-ips -2) - (l.d t5 point-cursor 8) - (pextlh t4 t4 r0) - (l.d t6 point-cursor 24) - (pextlh t5 t5 r0) - (l.d t7 point-cursor 32) - (pextlh t6 t6 r0) - (l.wu t8 point-cursor 16) - (pextlh t7 t7 r0) - (l.wu t9 point-cursor 40) - (sra.w t4 t4 10) - (m.ni vf13 t8) - (sra.w t5 t5 10) - (m.ni vf14 t9) - (sra.w t6 t6 10) - (m.ni vf1 t4) - (sra.w t4 t7 10) - (m.ni vf2 t5) - (add.i point-cursor point-cursor 48) - (m.ni vf3 t6) - (b.le remaining r0 tie-generic-proc-stage-last-interp-pair :delay (m.ni vf4 t4)) - (label tie-generic-proc-stage-interp-pair-loop) - (callms GENERIC-VU0-TIE-INTERP-POINTS) - (l.d t4 point-cursor) - (add.i remaining remaining -2) - (l.d t5 point-cursor 8) - (pextlh t4 t4 r0) - (l.d t6 point-cursor 24) - (pextlh t5 t5 r0) - (l.d t7 point-cursor 32) - (pextlh t6 t6 r0) - (l.wu t8 point-cursor 16) - (pextlh t7 t7 r0) - (l.wu t9 point-cursor 40) - (sra.w t4 t4 10) - (m.ni vf13 t8) - (sra.w t5 t5 10) - (m.ni vf14 t9) - (sra.w t6 t6 10) - (m.ni vf1 t4) - (sra.w t4 t7 10) - (m.ni vf2 t5) - (add.i point-cursor point-cursor 48) - (m.ni vf3 t6) - (b.gt remaining r0 tie-generic-proc-stage-interp-pair-loop :delay (m.ni vf4 t4)) - (label tie-generic-proc-stage-last-interp-pair) - (callms GENERIC-VU0-TIE-INTERP-POINTS) - (nop!) - (label tie-generic-proc-staging-done) - (nop!) - (nop!))) - ;; The transform starts here, and so does the one piece of per-fragment color work the EE - ;; still has to do itself: the environment map's tint. It is the fragment's authored tint, - ;; scaled by this instance's fade value and then by the time-of-day environment color. - ;; Both scalings are packed halfword multiplies whose products come back out of the - ;; accumulator halved into HI and LO, so pmfhl.lh gathers them and a shift by seven both - ;; discards the 0..255 range and keeps the rounding cheap. The result goes into all four - ;; lanes of the shared envmap color constant, where prepare-dma-double will find it. - ;; - ;; The other thing decided here is which VU1 entry the packet will call. The instance pass left - ;; its near-classification reject flags in the second word of the instance record's fifth - ;; quadword; an instance carrying any of them would have gone to the near renderer, which clips, - ;; so it takes the clipping entry here and everything else takes the direct one. Patching the - ;; immediate of the MSCAL template is the whole of it. - (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) - (out-buf :reg a0 :type gsf-buffer) - (header :reg t1 :type generic-tie-header) - (tint :reg t1) - (vtx-out :reg t1) - (vtx-count :reg t3) - (cind-cursor :reg t4) - (color-index-src :reg v1) - (num-bps :reg a1) - (vu1-entry :reg t6)) - (asm-block build-envmap-tint - (label tie-generic-proc-transform-fragment) - (m vtx-count num-bps) - (l.w t6 (-> instance-mtx fog data 0)) - (m cind-cursor color-index-src) - (l.w tint (-> header tint-color)) - (add.i t5 at (generic-work-offset fx-buf work consts envmap colors)) - (s.w t6 at (generic-work-offset saves fade-val)) - (pextlb t2 r0 t1) - (l.q t1 (-> instance-mtx matrix vector 0)) - (pextlh t7 t6 t6) - (l.q t6 (-> instance-mtx matrix vector 1)) - (pextlh t8 t7 t7) - (l.q t7 (-> instance-mtx matrix vector 2)) - (pmulth r0 t2 t8) - (l.q t2 (-> instance-mtx matrix vector 3)) - (nop!) - (m.ni vf1 t1) - (nop!) - (m.ni vf2 t6) - (nop!) - (m.ni vf3 t7) - (nop!) - (m.ni vf4 t2) - (callms GENERIC-VU0-TIE-BASE-START) - (m t1 t0) - (pextlw t2 t1 t1) - (l.w vtx-out (-> out-buf info ptr-verts)) - (pcpyld t2 t2 t2) - (l.w t8 at (generic-work-offset saves time-of-day-color)) - (pmfhl.lh t7) - (l.w vu1-entry (-> instance-mtx fog data 1)) - (pextlb t8 r0 t8) - (mmi-nop!) - (psrlh t7 t7 7) - (mmi-nop!) - (pmulth r0 t7 t8) - (mmi-nop!) - (b.nz vu1-entry tie-generic-proc-use-clipping-entry :delay (mmi-nop!)) - (nop!) - (add.i vu1-entry r0 GENERIC-VU1-DIRECT) - (b tie-generic-proc-entry-selected :delay (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag))) - (label tie-generic-proc-use-clipping-entry) - (nop!) - (add.i vu1-entry r0 GENERIC-VU1-CLIPPED) - (nop!) - (s.h vu1-entry at (generic-work-offset fx-buf work consts mscal-tag)))) - ;; Collect four transformed base points from VU0 and write them out as four gsf-vertex - ;; records, 32 bytes each: position and coordinate pair in the first quadword, normal in - ;; the second, and then the palette color overwriting the normal's unused w. - ;; - ;; The color is a plain lookup. Each vertex carries a one-byte palette index; shifting it - ;; left by two and adding the broadcast palette base gives four addresses in one packed - ;; add, and the four loads are spread through the dependent VU0 crossings so the load - ;; delays cost nothing. Because the whole loop is one iteration behind VU0, the stores at - ;; the top of the body belong to the previous group. - ;; - ;; t5 through t9, gp, ra and s1 through s5 are the shuttles carrying quadwords between VU0 - ;; and memory. Their meaning changes every few instructions and they keep physical names. - (rlet ((vtx-out :reg t1) - (palette-lanes :reg t2 :class i128) - (vtx-count :reg t3) - (cind-cursor :reg t4)) - (asm-block write-base-vertices - (label tie-generic-proc-entry-selected) - (pmfhl.lh t6) - (mmi-nop!) - (nop!) - (mmi-nop!) - (psrlh t6 t6 7) - (mmi-nop!) - (ppacb t6 r0 t6) - (mmi-nop!) - (nop!) - (l.w t7 cind-cursor) - (add.i cind-cursor cind-cursor 4) - (s.w t6 t5) - (pextlb t7 r0 t7) - (s.w t6 t5 4) - (pextlh t7 r0 t7) - (s.w t6 t5 8) - (sll.w t7 t7 2) - (s.w t6 t5 12) - (add.w t9 t7 palette-lanes) - (mmi-nop!) - (srl32 gp t9 0) - (m.ni t7 vf17) - (pcpyud t5 t9 r0) - (mmi-nop!) - (srl32 t6 t5 0) - (m.ni ra vf13) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (mmi-nop!) - (nop!) - (m.ni t8 vf14) - (nop!) - (l.wu t9 t9) - (nop!) - (l.wu gp gp) - (nop!) - (l.wu t5 t5) - (nop!) - (m.ni s3 vf18) - (nop!) - (l.wu t6 t6) - (nop!) - (s.q ra vtx-out) - (add.i vtx-count vtx-count -4) - (nop! :count 3) - (b.le vtx-count r0 tie-generic-proc-drain-base-vertices :delay (nop!)) - (label tie-generic-proc-write-base-vertex-group) - (callms GENERIC-VU0-TIE-BASE-XFORM) - (s.q t7 vtx-out 16) - (nop!) - (s.w t9 vtx-out 28) - (nop!) - (s.q t8 vtx-out 32) - (nop!) - (s.q s3 vtx-out 48) - (nop!) - (s.w gp vtx-out 60) - (nop! :count 7) - (mmi-nop!) - (nop!) - (l.w t8 cind-cursor) - (add.i cind-cursor cind-cursor 4) - (m.ni t7 vf19) - (pextlb t9 r0 t8) - (m.ni t8 vf20) - (pextlh t9 r0 t9) - (m.ni gp vf15) - (sll.w ra t9 2) - (m.ni t9 vf16) - (add.w s3 ra palette-lanes) - (s.q t7 vtx-out 80) - (srl32 s2 s3 0) - (m.ni t7 vf17) - (pcpyud ra s3 r0) - (s.q gp vtx-out 64) - (srl32 s5 ra 0) - (m.ni s4 vf13) - (nop!) - (s.q t8 vtx-out 112) - (nop!) - (s.w t5 vtx-out 92) - (nop!) - (s.w t6 vtx-out 124) - (nop!) - (s.q t9 vtx-out 96) - (nop!) - (l.wu t9 s3) - (nop!) - (l.wu gp s2) - (nop!) - (m.ni t8 vf14) - (add.i vtx-out vtx-out 128) - (m.ni s3 vf18) - (add.i vtx-count vtx-count -4) - (l.wu t5 ra) - (nop!) - (s.q s4 vtx-out) - (b.gt vtx-count r0 tie-generic-proc-write-base-vertex-group :delay (l.wu t6 s5)))) - ;; The drain entry publishes the group still inside the pipeline. Four of its results have - ;; to be stored straight out of the VU registers rather than through the usual crossing, - ;; because there is no following group whose crossings could carry them. - (rlet ((vtx-out :reg t1) - (num-ips :reg a2)) - (asm-block drain-base-vertices - (label tie-generic-proc-drain-base-vertices) - (callms GENERIC-VU0-TIE-BASE-DRAIN) - (s.q t7 vtx-out 16) - (nop!) - (s.w t9 vtx-out 28) - (nop!) - (s.q t8 vtx-out 32) - (nop!) - (s.q s3 vtx-out 48) - (nop!) - (s.w gp vtx-out 60) - (nop!) - (vnop) - (nop!) - (s.vf vf19 vtx-out 80) - (nop!) - (s.vf vf15 vtx-out 64) - (nop!) - (s.vf vf16 vtx-out 96) - (nop!) - (s.vf vf20 vtx-out 112) - (nop!) - (s.w t5 vtx-out 92) - (nop!) - (s.w t6 vtx-out 124) - (b.z num-ips tie-generic-proc-done :delay (nop!)))) - ;; Interpolated points next, and they take one of two paths. The instance's morph quadword - ;; holds the pair of color weights that trade off between a vertex's own palette entry and - ;; the average of the two entries it blends toward; the second weight arrives doubled, - ;; because summing two neighbours instead of averaging them is one instruction cheaper, so - ;; it is halved here. If it comes out zero nothing is blending - the instance is at the near - ;; edge of its LOD band - and the cheaper path, which reads only the second of each point's - ;; two vectors and only the vertex's own color, is taken instead. - ;; - ;; The blended path is the interesting one. Both weights are broadcast into every halfword - ;; lane, and the blend is pmulth followed by pmaddh - four channels of two colors weighted - ;; and summed in two instructions - with the sum coming back out of HI and LO through - ;; pmfhl.lh. The weights add up to 256 rather than to one, so the shift by eight that - ;; follows is the normalization. - ;; - ;; Each interpolated point's colors are four bytes: its own index and the two it blends - ;; toward. They follow the base points' one-byte indices, rounded up to four. - (asm-block start-interp-vertices - ;; Everything the rest of the block needs is set up here, out of the instance record - ;; and the GSF info block. After this the same registers carry broadcast weights and - ;; packed palette addresses, so they go back to physical names. - (rlet ((instance-mtx :reg a3 :type generic-tie-matrix) - (cind-cursor :reg a3) - (out-buf :reg a0 :type gsf-buffer) - (palette :reg t0) - (palette-base :reg t1) - (color-index-src :reg v1) - (num-bps :reg a1) - (num-ips :reg a2) - (vtx-base :reg t2)) - (m palette-base palette) - (l.vf vf29 (-> instance-mtx morph)) - (m palette color-index-src) - (l.s f0 (-> instance-mtx morph z)) - (add.i palette num-bps 3) - (l.s f2 (-> instance-mtx morph w)) - (add.i cind-cursor r0 -4) - (nop!) - (cvt.w.s f0 f0) - (and cind-cursor palette cind-cursor) - (cvt.w.s f2 f2) - (l.w vtx-base (-> out-buf info ptr-verts)) - (m a0 f0) - (m palette num-ips) - (m t3 f2) - (sll a2 num-bps 5) - (sra a1 t3 1) - (add cind-cursor color-index-src cind-cursor)) - (b.z a1 tie-generic-proc-interp-without-blend :delay (add a2 t2 a2)) - (pcpyh v1 a0) - (mmi-nop!) - (pcpyld v1 v1 v1) - (s.h v1 at (generic-work-offset in-buf tie interp-job morph-z)) - (pcpyh a0 a1) - (s.h a0 at (generic-work-offset in-buf tie interp-job morph-w)) - (pcpyld a0 a0 a0) - (l.dr t3 a3) - (pextlw a1 t1 t1) - (l.dl t3 a3 7) - (pcpyld a1 a1 a1) - (l.dr t2 a3 8) - (pextlb t3 r0 t3) - (l.dl t2 a3 15) - (pextuh t1 r0 t3) - (mmi-nop!) - (pextlh t3 r0 t3) - (mmi-nop!) - (pextlb t4 r0 t2) - (mmi-nop!) - (pextuh t2 r0 t4) - (mmi-nop!) - (pextlh t5 r0 t4) - (mmi-nop!) - (sll.w t3 t3 2) - (mmi-nop!) - (sll.w t4 t1 2) - (callms GENERIC-VU0-TIE-MORPH-START) - (sll.w t6 t5 2) - (mmi-nop!) - (sll.w t5 t2 2) - (mmi-nop!) - (add.w t1 t3 a1) - (mmi-nop!) - (add.w t4 t4 a1) - (l.wu t3 t1) - (add.w t2 t6 a1) - (l.wu t9 t4) - (add.w t5 t5 a1) - (mmi-nop!) - (srl32 s5 t1 0) - (mmi-nop!) - (srl32 s4 t4 0) - (mmi-nop!) - (srl32 ra t2 0) - (add.i a2 a2 -128) - (srl32 t8 t5 0) - (add.i a3 a3 16) - (pcpyud t7 t1 r0) - (l.wu gp t2) - (pcpyud t6 t4 r0) - (l.wu s3 t5) - (pcpyud t4 t2 r0) - (l.wu s5 s5) - (pcpyud t5 t5 r0) - (l.wu s4 s4) - (pextlw t9 t9 t3) - (l.wu ra ra) - (pextlw t3 s3 gp) - (l.wu s3 t8) - (pextlw gp s4 s5) - (l.wu t8 t7) - (pextlw t7 s3 ra) - (l.wu ra t6) - (pextlb t6 r0 t9) - (l.wu t9 t4) - (pextlb t4 r0 gp) - (l.wu gp t5) - (pextlw ra ra t8) - (m.ni t5 vf17) - (pextlw t8 gp t9) - (mmi-nop!) - (pextlb t9 r0 ra) - (mmi-nop!) - (add.h t9 t4 t9) - (m.ni t4 vf13) - (pmulth r0 t9 a0) - (mmi-nop!) - (pextlb t3 r0 t3) - (s.q t5 a2 144) - (pmaddh r0 t6 v1) - (s.q t4 a2 128) - (pextlb t5 r0 t7) - (l.dr t1 a3) - (pextlb t7 r0 t8) - (l.dl t1 a3 7) - (pmfhl.lh t6) - (m.ni t4 vf18) - (add.h t5 t5 t7) - (l.dr t2 a3 8) - (psrlh t6 t6 8) - (l.dl t2 a3 15) - (ppacb t6 r0 t6) - (s.q t4 a2 176) - (srl32 t4 t6 0) - (add.i t0 t0 -4) - (pmulth r0 t5 a0) - (m.ni t5 vf14) - (pextlb t1 r0 t1) - (s.w t6 a2 156) - (pmaddh r0 t3 v1) - (s.w t4 a2 188) - (pextlb t3 r0 t2) - (s.q t5 a2 160) - (pextuh t2 r0 t1) - (mmi-nop!) - (pmfhl.lh t6) - (m.ni t5 vf19) - (pextlh t4 r0 t1) - (mmi-nop!) - (psrlh t1 t6 8) - (mmi-nop!) - (ppacb t6 r0 t1) - (s.q t5 a2 208) - (srl32 t1 t6 0) - (s.w t6 a2 220) - (pextuh t5 r0 t3) - (m.ni t6 vf15) - (pextlh t3 r0 t3) - (mmi-nop!) - (sll.w t4 t4 2) - (s.q t6 a2 192) - (sll.w t6 t2 2) - (mmi-nop!) - (sll.w t3 t3 2) - (mmi-nop!) - (sll.w t7 t5 2) - (mmi-nop!) - (add.w t2 t4 a1) - (m.ni t4 vf20) - (add.w t6 t6 a1) - (l.wu t5 t2) - (add.w t3 t3 a1) - (l.wu gp t6) - (b.le t0 r0 tie-generic-proc-drain-morphed-group :delay (add.w t7 t7 a1))) - ;; Four blending vertices per pass. Same shape as the base-point loop - four records out, the - ;; palette read as four packed addresses, everything one group behind VU0 - with the two-entry - ;; color blend folded into the same instruction stream. - ;; - ;; Unnamed on purpose, as above: this is the densest interleaving in the file and every general - ;; register is reused two or three times per pass. The only registers with a lifetime worth a - ;; name are a3, the color-index cursor, and a2, the vertex output cursor; both step by a fixed - ;; amount at the bottom of the loop. - (asm-block write-morphed-interp-vertices - (label tie-generic-proc-write-morphed-group) - (srl32 s4 t2 0) - (callms GENERIC-VU0-TIE-MORPH-XFORM) - (srl32 s1 t6 0) - (srl32 s3 t3 0) - (srl32 ra t7 0) - (add.i a2 a2 128) - (pcpyud t9 t2 r0) - (l.wu s5 t3) - (pcpyud t8 t6 r0) - (l.wu s2 t7) - (pcpyud t6 t3 r0) - (l.wu s4 s4) - (pcpyud t7 t7 r0) - (l.wu s1 s1) - (pextlw gp gp t5) - (l.wu s3 s3) - (pextlw t5 s2 s5) - (l.wu s2 ra) - (pextlw s5 s1 s4) - (l.wu ra t9) - (pextlw t9 s2 s3) - (l.wu s4 t8) - (pextlb t8 r0 gp) - (l.wu gp t6) - (pextlb t6 r0 s5) - (l.wu t7 t7) - (pextlw ra s4 ra) - (s.q t4 a2 112) - (pextlw t4 t7 gp) - (m.ni t7 vf17) - (pextlb ra r0 ra) - (s.w t1 a2 124) - (add.h t1 t6 ra) - (mmi-nop!) - (pmulth r0 t1 a0) - (m.ni t6 vf13) - (pextlb t1 r0 t5) - (s.q t7 a2 144) - (pmaddh r0 t8 v1) - (l.dr t2 a3 16) - (pextlb t5 r0 t9) - (s.q t6 a2 128) - (pextlb t4 r0 t4) - (l.dl t2 a3 23) - (pmfhl.lh t6) - (l.dr t3 a3 24) - (add.h t4 t5 t4) - (m.ni t7 vf18) - (psrlh t5 t6 8) - (l.dl t3 a3 31) - (ppacb t5 r0 t5) - (s.q t7 a2 176) - (srl32 t6 t5 0) - (add.i t0 t0 -4) - (pmulth r0 t4 a0) - (mmi-nop!) - (pextlb t4 r0 t2) - (m.ni t7 vf14) - (pmaddh r0 t1 v1) - (s.w t5 a2 156) - (pextlb t3 r0 t3) - (s.w t6 a2 188) - (pextuh t2 r0 t4) - (s.q t7 a2 160) - (pmfhl.lh t6) - (m.ni t1 vf16) - (pextlh t4 r0 t4) - (m.ni t5 vf19) - (psrlh t6 t6 8) - (s.q t1 a2 96) - (ppacb t6 r0 t6) - (s.q t5 a2 208) - (srl32 t1 t6 0) - (add.i a3 a3 16) - (pextuh t5 r0 t3) - (mmi-nop!) - (pextlh t7 r0 t3) - (m.ni t8 vf15) - (sll.w t3 t4 2) - (s.w t6 a2 220) - (sll.w t6 t2 2) - (s.q t8 a2 192) - (sll.w t8 t7 2) - (mmi-nop!) - (sll.w t7 t5 2) - (mmi-nop!) - (add.w t2 t3 a1) - (m.ni t4 vf20) - (add.w t6 t6 a1) - (l.wu t5 t2) - (add.w t3 t8 a1) - (l.wu gp t6) - (b.gt t0 r0 tie-generic-proc-write-morphed-group :delay (add.w t7 t7 a1))) - ;; Publish the last collapsing group. - (asm-block drain-morphed-interp-vertices - (label tie-generic-proc-drain-morphed-group) - (nop!) - (callms GENERIC-VU0-TIE-MORPH-DRAIN) - (nop!) - (s.q t4 a2 240) - (nop!) - (m v1 vf16) - (nop!) - (s.w t1 a2 252) - (b tie-generic-proc-done :delay (s.q v1 a2 224))) - ;; The unblended path. Only the second of each point's two vectors is transformed and only the - ;; vertex's own palette entry is read, so the four indices are four plain byte loads four bytes - ;; apart instead of a packed gather. - ;; - ;; No aliases below, and deliberately so: every register in this loop is a shuttle. a0 through - ;; a3, t0 through t7 and s4/s5 each carry a palette address, then a loaded color, then a - ;; quadword on its way out of VU0, within a dozen instructions, and there is no name that would - ;; be true for more than one of those. The cursors are v1 for the color indices and a2 for the - ;; output. - (asm-block write-authored-interp-vertices - (label tie-generic-proc-interp-without-blend) - (callms GENERIC-VU0-TIE-INTERP-START) - (nop!) - (nop!) - (s.h a0 at (generic-work-offset in-buf tie interp-job morph-z)) - (nop!) - (s.h a1 at (generic-work-offset in-buf tie interp-job morph-w)) - (nop! :count 17) - (l.bu v1 a3) - (nop!) - (l.bu a1 a3 4) - (nop!) - (l.bu a0 a3 8) - (sll v1 v1 2) - (l.bu t2 a3 12) - (sll a1 a1 2) - (nop!) - (sll t4 a0 2) - (nop!) - (sll t5 t2 2) - (m t2 vf17) - (add a0 v1 t1) - (add.i v1 a3 16) - (add t3 a1 t1) - (nop!) - (add t4 t4 t1) - (nop!) - (add a1 t5 t1) - (nop!) - (nop!) - (m.ni t6 vf13) - (nop! :count 5) - (m.ni a3 vf18) - (nop!) - (l.wu t7 a0) - (nop!) - (l.wu t3 t3) - (nop!) - (l.wu t4 t4) - (nop!) - (m.ni t5 vf14) - (nop!) - (l.wu a0 a1) - (add.i a1 t0 -4) - (s.q t6 a2) - (nop!) - (m.ni t0 vf19) - (nop!) - (s.q t2 a2 16) - (nop!) - (s.w t7 a2 28) - (b.le a1 r0 tie-generic-proc-drain-interp-without-blend :delay (s.q a3 a2 48))) - ;; Four more, until the interpolated points run out. Same allocation as the block above. - (asm-block write-authored-interp-vertices-loop - (label tie-generic-proc-interp-without-blend-loop) - (callms GENERIC-VU0-TIE-INTERP-XFORM) - (s.w t3 a2 60) - (nop!) - (s.q t5 a2 32) - (nop!) - (s.q t0 a2 80) - (nop!) - (s.w t4 a2 92) - (nop! :count 5) - (l.bu a3 v1) - (nop!) - (l.bu t0 v1 4) - (nop!) - (l.bu t2 v1 8) - (sll t3 a3 2) - (l.bu a3 v1 12) - (sll t4 t0 2) - (m t0 vf15) - (sll t5 t2 2) - (m.ni t2 vf20) - (sll t6 a3 2) - (m.ni a3 vf17) - (add t3 t3 t1) - (add.i v1 v1 16) - (add t4 t4 t1) - (m.ni t7 vf16) - (add t5 t5 t1) - (s.q t0 a2 64) - (add t0 t6 t1) - (s.q t2 a2 112) - (nop!) - (m.ni t2 vf13) - (nop!) - (s.w a0 a2 124) - (nop!) - (s.q t7 a2 96) - (nop!) - (m.ni t6 vf18) - (nop!) - (l.wu t7 t3) - (nop!) - (l.wu t3 t4) - (nop!) - (l.wu t4 t5) - (nop!) - (m.ni t5 vf14) - (add.i a2 a2 128) - (l.wu a0 t0) - (add.i a1 a1 -4) - (s.q t2 a2) - (nop!) - (m.ni t0 vf19) - (nop!) - (s.q a3 a2 16) - (nop!) - (s.w t7 a2 28) - (b.gt a1 r0 tie-generic-proc-interp-without-blend-loop :delay (s.q t6 a2 48))) - ;; Publish the last unblended group and the fragment is expanded. - (asm-block drain-authored-interp-vertices - (label tie-generic-proc-drain-interp-without-blend) - (nop!) - (callms GENERIC-VU0-TIE-INTERP-DRAIN) - (nop!) - (s.w t3 a2 60) - (nop!) - (s.q t5 a2 32) - (nop!) - (s.q t0 a2 80) - (nop!) - (s.w t4 a2 92) - (nop!) - (m v1 vf15) - (nop!) - (m.ni a1 vf20) - (nop!) - (m.ni a3 vf16) - (nop!) - (s.q v1 a2 64) - (nop!) - (s.q a1 a2 112) - (nop!) - (s.w a0 a2 124) - (nop!) - (s.q a3 a2 96)) - ;; One fragment expanded; the caller dispatches the effect processors. - (asm-block restore-and-return - (label tie-generic-proc-done) - (m v0 r0) - (l.d ra sp) - (l.q gp sp 96) - (l.q s5 sp 80) - (l.q s4 sp 64) - (l.q s3 sp 48) - (l.q s2 sp 32) - (l.q s1 sp 16) - (jr ra :delay (add.i sp sp 112)) - (nop!) - (nop!)) - - ) - - (defun generic-tie-upload-next () - "Decide what the next chain entry is, flip the scratchpad buffer it will land in, and start its - transfer without waiting. Sets shadow.tie-type to say which of the three shapes arrived, or - shadow.end-of-chain when there is nothing left." - (declare (asm-func none) (allow-saved-regs)) - ;; Three kinds of chain entry, and which one is next was decided by the prototype pass and smuggled - ;; into the unused VIF words of the DMA tags this block's transfer already delivered: - ;; - ;; another instance of the fragment we are already holding - only the transform, morph and - ;; palette indices need to arrive, and they go into the standalone instance buffer - ;; a new fragment, reusing the palette we already have - the block arrives with its palette area - ;; skipped, so the transfer starts partway into the buffer - ;; a new fragment with its own palette - the whole block arrives - ;; - ;; The first is chosen by control.next-instance holding a real address. Any main-memory address is - ;; above 64 KiB, so the exporter can leave a small marker there when a fragment's instance list is - ;; finished, and the test is a signed compare rather than a nonzero check. - ;; - ;; The flip is an xor in every case. The two input buffers are 3248 bytes apart and the first one - ;; starts at 768, whose bits do not overlap 3248, so xor and add come to the same thing; the - ;; instance buffer is reached the same way, through the xor cached in shadow.inst-xor. Doing it with - ;; xor rather than a compare and two moves is what keeps this on one path. - (rlet ((chain-addr :reg v1) ;; control.next-instance, then whichever address gets programmed - (next-model :reg a0) ;; control.next-model: zero means this was the last entry - (to-spr :reg a0 :type dma-bank-spr) - (chain-test :reg a1) ;; next-instance measured against the main-memory floor - (spad-dest :reg a1) ;; then: where in scratchpad the entry lands - (next-is-model :reg a2) - (instance-flip :reg a2) ;; then: shadow.inst-xor, then the address mask - (new-instance :reg a3) ;; the instance area inside the newly selected buffer - (entry-kind :reg a3) ;; then: the shadow.tie-type value being published - (instance-kind :reg a0)) ;; a0 again, holding that value for two instructions - ;; Both branches carry a load in their delay slot, and only one of the two loads survives on each - ;; path: the instance pointer on the instance path, the buffer pointer on the two model paths. - (asm-block choose-next-chain-entry - (lui at #x7000) - (nop!) - (l.w chain-addr at (generic-work-offset in-buf tie control next-instance)) - (lui chain-test 1) - (l.w next-model at (generic-work-offset in-buf tie control next-model)) - (sub chain-test chain-addr chain-test) - (l.w next-is-model at (generic-work-offset in-buf tie control next-is-model)) - (b.gt chain-test r0 tie-generic-upload-queue-next-instance - :delay (l.w chain-test at (generic-work-offset in-buf tie shadow ptr-inst))) - (b.nz next-is-model tie-generic-upload-queue-model-without-palette - :delay (l.w chain-addr at (generic-work-offset in-buf tie shadow ptr-buf)))) - ;; A fragment that brings its own palette: the transfer covers the whole block. - (asm-block queue-model-with-palette - (xor.i spad-dest chain-addr GENERIC-TIE-INPUT-FLIP) - (m chain-addr next-model) - (add.i new-instance spad-dest (offset-of generic-tie-input matrix-tag)) - (s.w spad-dest at (generic-work-offset in-buf tie shadow ptr-buf)) - (xor.i instance-flip new-instance (generic-work-offset in-buf tie inst-buf)) - (s.w new-instance at (generic-work-offset in-buf tie shadow ptr-inst)) - (add.i entry-kind r0 GENERIC-TIE-ENTRY-MODEL-AND-PALETTE) - ;; The matching instruction on the other path skips the palette area; here the - ;; destination is the start of the buffer, so the add is left in as a plain move. - (add spad-dest spad-dest r0) - (and.i instance-flip instance-flip #xffff) - (s.w entry-kind at (generic-work-offset in-buf tie shadow tie-type)) - (b.nz next-model tie-generic-upload-start-input-transfer - :delay (s.w instance-flip at (generic-work-offset in-buf tie shadow inst-xor))) - (add.i chain-addr r0 1) - (nop!) - (b tie-generic-upload-queued - :delay (s.w chain-addr at (generic-work-offset in-buf tie shadow end-of-chain)))) - ;; The same thing for a fragment that reuses the palette already in palette-buf. Only the - ;; published kind and the destination differ. - (asm-block queue-model-without-palette - (label tie-generic-upload-queue-model-without-palette) - (xor.i spad-dest chain-addr GENERIC-TIE-INPUT-FLIP) - (m chain-addr next-model) - (add.i new-instance spad-dest (offset-of generic-tie-input matrix-tag)) - (s.w spad-dest at (generic-work-offset in-buf tie shadow ptr-buf)) - (xor.i instance-flip new-instance (generic-work-offset in-buf tie inst-buf)) - (s.w new-instance at (generic-work-offset in-buf tie shadow ptr-inst)) - (add.i entry-kind r0 GENERIC-TIE-ENTRY-MODEL-ONLY) - (add.i spad-dest spad-dest (offset-of generic-tie-input model-tag)) - (and.i instance-flip instance-flip #xffff) - (s.w entry-kind at (generic-work-offset in-buf tie shadow tie-type)) - (b.nz next-model tie-generic-upload-start-input-transfer - :delay (s.w instance-flip at (generic-work-offset in-buf tie shadow inst-xor))) - (add.i chain-addr r0 1) - (nop!) - (b tie-generic-upload-queued - :delay (s.w chain-addr at (generic-work-offset in-buf tie shadow end-of-chain)))) - ;; Another instance of the fragment already in hand. Nothing about the model moves, so this - ;; alternates the instance area rather than the whole input buffer. - (asm-block queue-next-instance - (label tie-generic-upload-queue-next-instance) - (m chain-addr chain-addr) - (l.w instance-flip at (generic-work-offset in-buf tie shadow inst-xor)) - (add.i instance-kind r0 GENERIC-TIE-ENTRY-INSTANCE-ONLY) - (nop!) - (xor spad-dest spad-dest instance-flip) - (s.w instance-kind at (generic-work-offset in-buf tie shadow tie-type)) - (b tie-generic-upload-start-input-transfer - :delay (s.w spad-dest at (generic-work-offset in-buf tie shadow ptr-inst)))) - ;; Hand the chain to the toSPR channel and leave. Whoever wants to read the destination has to - ;; wait for the channel first. - (asm-block start-input-transfer - (label tie-generic-upload-start-input-transfer) - (lui at #x7000) - (ori instance-flip r0 #xffff) - (lui to-spr #x1000) - (nop!) - (ori to-spr to-spr #xd400) - (and spad-dest spad-dest instance-flip) - (add.i instance-flip r0 DMA-CHCR-START-CHAIN-TTE) - (s.w spad-dest (-> to-spr sadr)) - (s.w chain-addr (-> to-spr tadr)) - (s.w r0 (-> to-spr qwc)) - (s.w instance-flip (-> to-spr chcr))) - (asm-block return-nothing - (label tie-generic-upload-queued) - (nop!) - (m v0 r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!)))) - - (defun generic-tie-decompress () - "Interleave a fragment's draw-point indices and packed kick bits into the gsf-ik list GENERIC - draws from. a0 is the kick bitfield, a1 its stride in bytes, a2 the destination list, a3 the - number of strides to consume, and t0 the index bytes." - (declare (asm-func none) (allow-saved-regs)) - ;; The exporter ships the drawing order as two arrays, because that is how it is cheapest to - ;; store: one byte of vertex index per draw point, and one bit saying whether that point ends a - ;; strip. gsf-ik wants them side by side, a byte each. So the bits have to be spread out by a - ;; factor of eight before they can be married to the indices, and this is the trick for it. - ;; - ;; Three passes of pextlb do the spreading. Each pass interleaves a register's low eight bytes - ;; with themselves, which doubles every byte, so a halfword of sixteen flags becomes sixteen bytes - ;; each holding a whole copy of the byte its flag came from. AND against 01 02 04 08 10 20 40 80 - ;; then leaves exactly one live bit per lane, pceqb against the same mask turns "that bit was set" - ;; into a lane of all ones, and AND with all-ones-bytes turns that into a clean 0 or 1. pextlb and - ;; pextub finally interleave the sixteen flags with sixteen index bytes loaded as one quadword. - ;; Sixteen draw points per pass, two quadwords of output, no branches inside the pass. - ;; - ;; t2 and t3 each carry three unrelated things - the flags being spread, then the index quadword - ;; and the compare result, then the two halves of the finished output - so they keep their physical - ;; names. The loop is rotated: the second output quadword of a pass is stored at the top of the - ;; next one, and the flags for the next pass are already being spread in the branch's delay slot. - (rlet ((kick-bits :reg a0) ;; argument: the packed kick flags - (ik-out :reg a0) ;; then: the gsf-ik write cursor - (kick-step :reg a1) ;; argument: bytes of flags consumed per pass - (bit-select :reg a1 :class i128) ;; then: the 01 02 04 ... 80 lane mask - (ik-dest :reg a2) ;; argument: where the finished list goes - (one-per-byte :reg a2 :class i128) ;; then: 01 in every lane - (pass-count :reg a3) ;; argument: strides of flags to consume - (kick-cursor :reg a3) ;; then: the flag read cursor - (index-src :reg t0) - (kick-end :reg t1) - (stride :reg v1)) - (asm-block build-lane-masks - (nop!) - (mult3 kick-end kick-step pass-count) - (m kick-cursor kick-bits) - (m stride kick-step) - (m ik-out ik-dest) - ;; 513 and 257 seed the two masks: 0x201 grown into 0x8040201008040201 gives one - ;; distinct bit per byte lane, and 0x101 grown the same way gives 01 everywhere. - (add.i bit-select r0 513) - (add.i one-per-byte r0 257) - (sll t3 bit-select 18) - (sll t2 one-per-byte 16) - (or bit-select bit-select t3) - (or one-per-byte one-per-byte t2) - (dsll32 t3 bit-select 4) - (dsll32 t2 one-per-byte 0) - (or bit-select bit-select t3) - (or one-per-byte one-per-byte t2) - (pcpyld bit-select bit-select bit-select) - (mmi-nop!) - (pcpyld one-per-byte one-per-byte one-per-byte) - (mmi-nop!) - (add kick-end kick-end kick-cursor) - (mmi-nop!) - (l.hu t2 kick-cursor) - (add kick-cursor kick-cursor stride) - (nop!) - (m index-src index-src) - (nop!) - (pextlb t2 t2 t2) - (b tie-generic-ik-expand-kick-group :delay (pextlb t3 t2 t2))) - (asm-block interleave-indices-and-kicks - (label tie-generic-ik-next-kick-group) - (add.i index-src index-src 16) - (nop!) - (add kick-cursor kick-cursor stride) - (add.i ik-out ik-out 32) - (pextlb t3 t3 t3) - (s.q t2 ik-out -16) - (label tie-generic-ik-expand-kick-group) - (pextlb t2 t3 t3) - (mmi-nop!) - (and.q t2 t2 bit-select) - (mmi-nop!) - (pceqb t3 t2 bit-select) - (l.q t2 index-src) - (and.q t5 t3 one-per-byte) - (mmi-nop!) - (pextlb t3 t5 t2) - (l.hu t4 kick-cursor) - (pextub t2 t5 t2) - (s.q t3 ik-out) - (b.ne kick-cursor kick-end tie-generic-ik-next-kick-group :delay (pextlb t3 t4 t4)) - (nop!) - (s.q t2 ik-out 16)) - (asm-block return-nothing - (m v0 r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!)))) - - (defun generic-tie-dma-to-spad-sync () - "Wait out any transfer already running on the toSPR channel, start a chain transfer from the - main-memory address in a0 into the scratchpad address in a1, and wait for that one too." - (declare (asm-func none) (allow-saved-regs)) - ;; The synchronous form, used once at the start of a pass to fetch the first block of the chain - ;; before there is anything to overlap it with. Everything after that goes through - ;; generic-tie-upload-next and is left running. - ;; - ;; Both polls are padded to sixteen instructions with no-ops, which is the shape of a poll written - ;; for a channel that is expected to be busy: the register is only usable three instructions after - ;; its load, and re-reading a hardware register any faster than this buys nothing. Each poll bumps - ;; a counter, but the counter it bumps is a1, which by then is a dead copy of the destination - ;; address - the masked value in a2 is what gets programmed - so nothing records these stalls. - (rlet ((to-spr :reg v1 :type dma-bank-spr) - (chain-addr :reg a0) - (chcr :reg a0) - (spad-addr :reg a1) - (stall-count :reg a1) - (spad-addr-masked :reg a2) - (status :reg a3)) - (asm-block address-the-channel - (nop!) - (lui at #x7000) - ;; The scratchpad address register takes only the low sixteen bits of an offset into - ;; the page; the rest of a scratchpad pointer is the fixed 0x7000 selector. - (ori spad-addr-masked r0 #xffff) - (lui to-spr #x1000) - (nop!) - (ori to-spr to-spr #xd400) - (and spad-addr-masked spad-addr spad-addr-masked)) - (asm-block wait-for-channel - (label tie-generic-spad-wait-for-previous) - (l.w status (-> to-spr chcr)) - (nop! :count 3) - (and.i status status DMA-CHCR-STR) - (nop!) - (b.z status tie-generic-spad-previous-done :delay (nop!)) - (nop! :count 3) - (add.i stall-count stall-count 1) - (nop! :count 6) - (b tie-generic-spad-wait-for-previous :delay (nop!))) - (asm-block start-transfer - (label tie-generic-spad-previous-done) - (add.i status r0 DMA-CHCR-START-CHAIN-TTE) - (s.w spad-addr-masked (-> to-spr sadr)) - (s.w chain-addr (-> to-spr tadr)) - (s.w r0 (-> to-spr qwc)) - ;; The chain we are about to hand over was just written by ordinary stores, so make - ;; sure they have left the EE before the DMA controller starts reading tags. - (sync.l) - (s.w status (-> to-spr chcr))) - (asm-block wait-for-completion - (label tie-generic-spad-wait-for-transfer) - (l.w chcr (-> to-spr chcr)) - (nop! :count 3) - (and.i chcr chcr DMA-CHCR-STR) - (nop!) - (b.z chcr tie-generic-spad-transfer-done :delay (nop!)) - (nop! :count 3) - (add.i stall-count stall-count 1) - (nop! :count 6) - (b tie-generic-spad-wait-for-transfer :delay (nop!))) - (asm-block return-nothing - (label tie-generic-spad-transfer-done) - (m v0 r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!)))) - - (defun generic-tie-dma-to-spad () - "Start a chain transfer from the main-memory address in a0 into the scratchpad address in a1 and - return immediately." - (declare (asm-func none) (allow-saved-regs)) - ;; No wait at either end: the caller is expected to have made sure the channel is free and to - ;; check for completion before it touches the destination. Fire and forget is the whole point - - ;; this is what overlaps the next block's arrival with the current block's expansion. - (rlet ((to-spr :reg v1 :type dma-bank-spr) - (chain-addr :reg a0) - (spad-addr :reg a1) - (addr-mask :reg a2) - (chcr :reg v0)) - (asm-block start-transfer - (nop!) - (lui at #x7000) - (ori addr-mask r0 #xffff) - (lui to-spr #x1000) - (nop!) - (ori to-spr to-spr #xd400) - (and spad-addr spad-addr addr-mask) - (add.i chcr r0 DMA-CHCR-START-CHAIN-TTE) - (s.w spad-addr (-> to-spr sadr)) - (s.w chain-addr (-> to-spr tadr)) - (s.w r0 (-> to-spr qwc)) - (s.w chcr (-> to-spr chcr)) - (jr ra :delay (add sp sp r0))))) - - ) +(define-extern generic-prepare-dma-double function) +(define-extern generic-envmap-dproc function) +(define-extern generic-interp-dproc function) +(define-extern generic-no-light-dproc function) +(define-extern generic-tie-convert (function none)) (defun generic-tie-execute ((sink generic-dma-foreground-sink) (dma-buf dma-buffer) (input-chain basic)) "Initialize Generic state for sink, transfer and convert input-chain, append the expanded packets to dma-buf, and update renderer performance and DMA-memory statistics." - ;; The frame-level wrapper: set up everything the converter reads out of scratchpad, fetch the first - ;; chain entry, convert until the chain runs out, and put back the two VU1 cursors so the next - ;; renderer to feed this bucket continues the same rotation. - ;; (local-vars (v1-28 uint128) (v1-29 uint128) (v1-30 uint128) (a0-33 int) (a0-35 int)) - ;; (rlet ((vf1 :class vf)) - (when (logtest? *vu1-enable-user* (vu1-renderer-mask generic)) - (when *generic-tie* - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) - (reset! (-> *perf-stats* data 2)) - (when (nonzero? input-chain) - (let ((output-start (-> dma-buf base))) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves basep) - (the-as uint (-> dma-buf base))) - (generic-initialize sink (-> *math-camera* perspective) *default-lights*) - (generic-tie-dma-to-spad-sync input-chain - (-> (scratchpad-object terrain-context) work foreground generic-work in-buf tie input-a)) - ; (let ((v1-24 (+ 716 (the-as int #x70000000)))) - ;; "shadow" is the converter's own control block: which input buffer is current, which - ;; instance area goes with it, where the expanded vertices go, and how far it may write. - ;; The first chain entry brings a model and a palette, hence tie-type zero. - ;; - ;; inst-xor is ptr-inst xored with the address of the standalone instance buffer, so one xor - ;; switches between an instance delivered inside a full block and one delivered on its own. - ;; The write limit is 64 KiB short of the end of the buffer, which is more than any single - ;; fragment can produce, so the converter only has to check it once per chain entry. - (let ((shadow (-> (scratchpad-object terrain-context) work foreground generic-work in-buf tie shadow))) - (set! (-> shadow end-of-chain) (the-as uint 0)) - (set! (-> shadow tie-type) GENERIC-TIE-ENTRY-MODEL-AND-PALETTE) - (set! (-> shadow out-buf) *gsf-buffer*) - (set! (-> shadow ptr-buf) (the-as uint (+ 768 (scratchpad-object int)))) - (set! (-> shadow ptr-inst) (the-as uint (+ 3648 (scratchpad-object int)))) - (set! (-> shadow inst-xor) 4640) - (set! (-> shadow write-limit) (the-as uint (&+ (-> dma-buf end) -65536)))) - ;;(let ((v1-26 (+ 748 (the-as int #x70000000)))) - ;; The converter runs without the symbol table available, so the four processors it needs are - ;; cached as addresses. TIE always wants this exact set: two packet headers, reflected - ;; coordinates, the subdivision blend, and an unlit color combine. - (let ((calls (-> (scratchpad-object terrain-context) work foreground generic-work in-buf tie shadow calls))) - (set! (-> calls generic-prepare-dma-double) generic-prepare-dma-double-new) - ;; The Generic TIE/dproc path is currently disabled and its PC implementations are absent. - ;; (set! (-> calls generic-envmap-dproc) generic-envmap-dproc) - ;; (set! (-> calls generic-interp-dproc) generic-interp-dproc) - ;; (set! (-> calls generic-no-light-dproc) generic-no-light-dproc) - ) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves time-of-day-color r) - (the int (-> *time-of-day-context* current-sun env-color x))) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves time-of-day-color g) - (the int (-> *time-of-day-context* current-sun env-color y))) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves time-of-day-color b) - (the int (-> *time-of-day-context* current-sun env-color z))) - (set! (-> (scratchpad-object terrain-context) work foreground generic-work saves time-of-day-color a) - (the int (-> *time-of-day-context* current-sun env-color w))) - ; (let ((v1-27 *time-of-day-context*)) - ; (let ((a0-26 (+ 16 (the-as int #x70000000)))) - ; (.lvf vf1 (&-> v1-27 current-sun env-color quad)) - ; (.ftoi.vf vf1 vf1) - ; (.mov v1-28 vf1) - ; (.ppach v1-29 (the-as uint128 0) v1-28) - ; (.ppacb v1-30 r0 (the-as int v1-29)) - ; (set! (-> a0-26 foreground generic-work saves time-of-day-color) (the-as uint v1-30)) - ; ) - ; ) - (generic-tie-convert) - (set! (-> dma-buf base) - (the-as pointer - (-> (scratchpad-object terrain-context) work foreground generic-work saves basep))) - (generic-wrapup sink) - ;; Put the MSCAL template back to the clipping entry. The converter patched its immediate per - ;; instance and whatever the last one wanted is not what the next renderer to use this - ;; scratchpad copy should inherit. - (set! (-> (scratchpad-object terrain-context) work foreground generic-work fx-buf work consts mscal-tag) - (logior (logand (-> (scratchpad-object terrain-context) work foreground generic-work fx-buf work consts mscal-tag) -65536) - GENERIC-VU1-CLIPPED)) - ; (dma-sync (the-as pointer #x1000d000) 0 0) - (let ((dma-usage *dma-mem-usage*)) - (when (nonzero? dma-usage) - (set! (-> dma-usage length) (max 18 (-> dma-usage length))) - (set! (-> dma-usage data 17 name) "tie-generic") - (+! (-> dma-usage data 17 count) 1) - (+! (-> dma-usage data 17 used) - (&- (-> dma-buf base) (the-as uint output-start))) - (set! (-> dma-usage data 17 total) (-> dma-usage data 17 used)))))) - (read! (-> *perf-stats* data 2)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #xd2 :g #xd2 :b #x50 :a #x80)))) - 0) - 0 - (none) - ;) - ) + (local-vars (v1-28 uint128) (v1-29 uint128) (v1-30 uint128) (a0-33 int) (a0-35 int)) + ;; og:preserve-this, no longer supported + (break!) + (slet (spad terrain-context) + (rlet ((vf1 :class vf)) + (when (logtest? *vu1-enable-user* (vu1-renderer-mask generic)) + (when *generic-tie* + (add-profile-frame! #x40 0 #x40 #x80 'draw) + (invalidate-cache-line input-chain) + (invalidate-cache-line (-> dma-buf base)) + (reset! (-> *perf-stats* data 2)) + (when (nonzero? input-chain) + (let ((output-start (-> dma-buf base))) + (set! (-> spad work foreground generic-work saves basep) (the-as uint (-> dma-buf base))) + (generic-initialize sink (-> *math-camera* perspective) *default-lights*) + (generic-tie-dma-to-spad-sync input-chain (-> spad work foreground generic-work in-buf tie input-a)) + (let ((shadow (-> spad work foreground generic-work in-buf tie shadow))) + (set! (-> shadow end-of-chain) (the-as uint 0)) + (set! (-> shadow tie-type) 0) + (set! (-> shadow out-buf) *gsf-buffer*) + (set! (-> shadow ptr-buf) (the-as uint (-> spad work foreground generic-work in-buf tie input-a))) + (set! (-> shadow ptr-inst) (the-as uint (-> spad work foreground generic-work in-buf tie input-a matrix-tag))) + (set! (-> shadow inst-xor) 4640) + (set! (-> shadow write-limit) (the-as uint (&+ (-> dma-buf end) -65536)))) + (let ((calls (-> spad work foreground generic-work in-buf tie shadow calls))) + (set! (-> calls generic-prepare-dma-double) generic-prepare-dma-double) + (set! (-> calls generic-envmap-dproc) generic-envmap-dproc) + (set! (-> calls generic-interp-dproc) generic-interp-dproc) + (set! (-> calls generic-no-light-dproc) generic-no-light-dproc)) + (let ((v1-27 *time-of-day-context*)) + (let ((a0-26 (-> spad work))) + (.lvf vf1 (&-> v1-27 current-sun env-color quad)) + (.ftoi.vf vf1 vf1) + (.mov v1-28 vf1) + (.ppach v1-29 (the-as uint128 0) v1-28) + (.ppacb v1-30 (the-as uint128 0) v1-29) + (set! (-> a0-26 foreground generic-work saves time-of-day-color) (the-as rgba v1-30)))) + (generic-tie-convert) + (set! (-> dma-buf base) (the-as pointer (-> spad work foreground generic-work saves basep))) + (generic-wrapup sink) + (set! (-> spad work foreground generic-work fx-buf work consts mscal-tag) + (logior (logand (-> spad work foreground generic-work fx-buf work consts mscal-tag) -65536) 6)) + (dma-sync (the-as pointer #x1000d000) 0 0) + (dma-mem-usage-add! tie-generic 1 (&- (-> dma-buf base) (the-as uint output-start))))) + (read! (-> *perf-stats* data 2)) + (add-profile-frame! #xd2 #xd2 #x50 #x80 'draw)) + 0) + 0 + (none)))) diff --git a/goal_src/jak1/engine/gfx/shadow/shadow-cpu-ee-asm.gc b/goal_src/jak1/engine/gfx/shadow/shadow-cpu-ee-asm.gc new file mode 100644 index 0000000000..ab9ea32fd7 --- /dev/null +++ b/goal_src/jak1/engine/gfx/shadow/shadow-cpu-ee-asm.gc @@ -0,0 +1,2303 @@ + +(#unless PC_PORT + ;; VU0 does the two pieces of per-vertex arithmetic that the EE would spend its whole frame on: + ;; deciding which way a triangle faces the light, and finding where a vertex projects onto the + ;; ground plane. Both are batched four at a time so that the four sequential divides and the four + ;; cross products fill the pipeline instead of stalling on each other. + ;; + ;; SHADOW-VU0-CLASSIFY-TRIS takes four triangles in vf02-vf04, vf07-vf09, vf12-vf14, vf17-vf19 and + ;; leaves the sign of each cross product dotted with the light direction in the y lane of vf22-vf25. + ;; The y lane is deliberate: the EE reads the result back with qmfc2 and tests bit 63 of the + ;; general-purpose register, which is the sign of lane y. + ;; + ;; SHADOW-VU0-PROJECT-VERTS takes four vertices in vf03-vf06 and returns the four projected duals in + ;; vf23-vf26 -- but one call late. Each call copies its inputs into vf07-vf10, starts the four + ;; divides for this group, and finishes the *previous* group, whose vertices are still sitting in + ;; vf07-vf10 from last time. The EE loop is written to match: it hands over a group, then reads the + ;; results of the group before it. SHADOW-VU0-PROJECT-DRAIN is the same code with the arithmetic that + ;; sets up a new group deleted, and squeezes the final group out. + (defvu0 shadow-vu0-block + ;; The four triangles, their two edge vectors, and the facing results. Each triangle's second edge + ;; register is reused for the cross product and then for the running dot product. + (rlet ((light-dir :reg vf01) + (tri-a-0 :reg vf02) (tri-a-1 :reg vf03) (tri-a-2 :reg vf04) + (edge-a-0 :reg vf05) (edge-a-1 :reg vf06) + (tri-b-0 :reg vf07) (tri-b-1 :reg vf08) (tri-b-2 :reg vf09) + (edge-b-0 :reg vf10) (edge-b-1 :reg vf11) + (tri-c-0 :reg vf12) (tri-c-1 :reg vf13) (tri-c-2 :reg vf14) + (edge-c-0 :reg vf15) (edge-c-1 :reg vf16) + (tri-d-0 :reg vf17) (tri-d-1 :reg vf18) (tri-d-2 :reg vf19) + (edge-d-0 :reg vf20) (edge-d-1 :reg vf21) + (facing-a :reg vf22) (facing-b :reg vf23) (facing-c :reg vf24) (facing-d :reg vf25)) + (label classify-tris) + (vu-pair (nop) (sub.xyzw edge-a-0 tri-a-1 tri-a-0)) ;; #x000 + (vu-pair (nop) (sub.xyzw edge-a-1 tri-a-2 tri-a-0)) ;; #x001 + (vu-pair (nop) (sub.xyzw edge-b-0 tri-b-1 tri-b-0)) ;; #x002 + (vu-pair (nop) (sub.xyzw edge-b-1 tri-b-2 tri-b-0)) ;; #x003 + (vu-pair (nop) (sub.xyzw edge-c-0 tri-c-1 tri-c-0)) ;; #x004 + (vu-pair (nop) (sub.xyzw edge-c-1 tri-c-2 tri-c-0)) ;; #x005 + (vu-pair (nop) (sub.xyzw edge-d-0 tri-d-1 tri-d-0)) ;; #x006 + (vu-pair (nop) (sub.xyzw edge-d-1 tri-d-2 tri-d-0)) ;; #x007 + ;; Four face normals, back to back, so no cross product waits on the one before it. + (vu-pair (nop) (opmula.xyz ACC edge-a-0 edge-a-1)) ;; #x008 + (vu-pair (nop) (opmsub.xyz edge-a-0 edge-a-1 edge-a-0)) ;; #x009 + (vu-pair (nop) (opmula.xyz ACC edge-b-0 edge-b-1)) ;; #x00a + (vu-pair (nop) (opmsub.xyz edge-b-0 edge-b-1 edge-b-0)) ;; #x00b + (vu-pair (nop) (opmula.xyz ACC edge-c-0 edge-c-1)) ;; #x00c + (vu-pair (nop) (opmsub.xyz edge-c-0 edge-c-1 edge-c-0)) ;; #x00d + (vu-pair (nop) (opmula.xyz ACC edge-d-0 edge-d-1)) ;; #x00e + (vu-pair (nop) (opmsub.xyz edge-d-0 edge-d-1 edge-d-0)) ;; #x00f + (vu-pair (nop) (mul.xyz edge-a-0 edge-a-0 light-dir)) ;; #x010 + (vu-pair (nop) (mul.xyz edge-b-0 edge-b-0 light-dir)) ;; #x011 + (vu-pair (nop) (mul.xyz edge-c-0 edge-c-0 light-dir)) ;; #x012 + (vu-pair (nop) (mul.xyz edge-d-0 edge-d-0 light-dir)) ;; #x013 + ;; Fold x+y and then +z into the y lane. Landing the dot product in y rather than x is what lets + ;; the EE test it with an ordinary bltz on the register it reads back. + (vu-pair (nop) (addx.y edge-a-0 edge-a-0 edge-a-0)) ;; #x014 + (vu-pair (nop) (addx.y edge-b-0 edge-b-0 edge-b-0)) ;; #x015 + (vu-pair (nop) (addx.y edge-c-0 edge-c-0 edge-c-0)) ;; #x016 + (vu-pair (nop) (addx.y edge-d-0 edge-d-0 edge-d-0)) ;; #x017 + (vu-pair (nop) (addz.y facing-a edge-a-0 edge-a-0)) ;; #x018 + (vu-pair (nop) (addz.y facing-b edge-b-0 edge-b-0)) ;; #x019 + ;; The E bit rides the second-to-last pair, so the fourth result still gets written. + (vu-pair (nop) (addz.y facing-c edge-c-0 edge-c-0 :e)) ;; #x01a + (vu-pair (nop) (addz.y facing-d edge-d-0 edge-d-0))) ;; #x01b + ;; Projection. A vertex's dual is where the ray from the locus through it crosses the ground plane: + ;; + ;; dual = v - (locus - v) * dist(v) / dot(locus - v, n) + ;; + ;; dist and the denominator are folded into lane x of their own register so that the four divides + ;; can be issued one after another; held-0..held-3 keep the previous group's vertices alive while + ;; those divides finish. + (rlet ((locus :reg vf01) + (ground-plane :reg vf02) + (vertex-0 :reg vf03) (vertex-1 :reg vf04) (vertex-2 :reg vf05) (vertex-3 :reg vf06) + (held-0 :reg vf07) (held-1 :reg vf08) (held-2 :reg vf09) (held-3 :reg vf10) + (dist-0 :reg vf11) (dist-1 :reg vf12) (dist-2 :reg vf13) (dist-3 :reg vf14) + (denom-0 :reg vf15) (denom-1 :reg vf16) (denom-2 :reg vf17) (denom-3 :reg vf18) + (ray-0 :reg vf19) (ray-1 :reg vf20) (ray-2 :reg vf21) (ray-3 :reg vf22) + (dual-0 :reg vf23) (dual-1 :reg vf24) (dual-2 :reg vf25) (dual-3 :reg vf26) + ;; ray-1..ray-3 are still needed after their scaling, so the scaled copies get their own + ;; registers; ray-0 is scaled in place because nothing reads it again. + (scaled-ray-1 :reg vf27) (scaled-ray-2 :reg vf28) (scaled-ray-3 :reg vf29)) + (label project-verts) + (vu-pair (nop) (mul.xyzw scaled-ray-1 ray-1 Q)) ;; #x01c + (vu-pair (div Q dist-2.x denom-2.x) (sub.xyzw ray-0 locus vertex-0)) ;; #x01d + (vu-pair (move.xyzw dual-0 held-0) (sub.xyzw ray-1 locus vertex-1)) ;; #x01e + (vu-pair (nop) (sub.xyzw ray-2 locus vertex-2)) ;; #x01f + (vu-pair (move.xyzw dual-2 held-2) (sub.xyzw ray-3 locus vertex-3)) ;; #x020 + (vu-pair (move.xyzw dual-3 held-3) (sub.xyzw dual-1 held-1 scaled-ray-1)) ;; #x021 + (vu-pair (nop) (mul.xyzw dist-0 vertex-0 ground-plane)) ;; #x022 + (vu-pair (nop) (mul.xyz denom-0 ray-0 ground-plane)) ;; #x023 + (vu-pair (div Q dist-3.x denom-3.x) (mul.xyzw dist-1 vertex-1 ground-plane)) ;; #x024 + (vu-pair (move.xyzw held-0 vertex-0) (mul.xyzw scaled-ray-2 scaled-ray-2 Q)) ;; #x025 + (vu-pair (move.xyzw held-1 vertex-1) (mul.xyz denom-1 ray-1 ground-plane)) ;; #x026 + (vu-pair (move.xyzw held-2 vertex-2) (addy.x dist-0 dist-0 dist-0)) ;; #x027 + (vu-pair (move.xyzw held-3 vertex-3) (addy.x denom-0 denom-0 denom-0)) ;; #x028 + (vu-pair (nop) (sub.xyzw dual-2 dual-2 scaled-ray-2)) ;; #x029 + (vu-pair (nop) (addy.x dist-1 dist-1 dist-1)) ;; #x02a + (vu-pair (nop) (mul.xyzw scaled-ray-3 scaled-ray-3 Q)) ;; #x02b + (vu-pair (nop) (addy.x denom-1 denom-1 denom-1)) ;; #x02c + (vu-pair (nop) (addz.x dist-0 dist-0 dist-0)) ;; #x02d + (vu-pair (nop) (addz.x denom-0 denom-0 denom-0)) ;; #x02e + (vu-pair (nop) (sub.xyzw dual-3 dual-3 scaled-ray-3)) ;; #x02f + (vu-pair (nop) (addz.x dist-1 dist-1 dist-1)) ;; #x030 + (vu-pair (nop) (addz.x denom-1 denom-1 denom-1)) ;; #x031 + ;; The plane's d rides in w, and a vertex's w is one, so folding w in finishes the distance. + (vu-pair (nop) (addw.x dist-0 dist-0 dist-0)) ;; #x032 + (vu-pair (nop) (mul.xyzw dist-2 held-2 ground-plane)) ;; #x033 + (vu-pair (nop) (addw.x dist-1 dist-1 dist-1)) ;; #x034 + (vu-pair (nop) (mul.xyz denom-2 ray-2 ground-plane)) ;; #x035 + (vu-pair (nop) (mul.xyzw dist-3 held-3 ground-plane)) ;; #x036 + (vu-pair (div Q dist-0.x denom-0.x) (mul.xyz denom-3 ray-3 ground-plane)) ;; #x037 + (vu-pair (nop) (addy.x dist-2 dist-2 dist-2)) ;; #x038 + (vu-pair (nop) (addy.x denom-2 denom-2 denom-2)) ;; #x039 + (vu-pair (nop) (addy.x dist-3 dist-3 dist-3)) ;; #x03a + (vu-pair (nop) (addy.x denom-3 denom-3 denom-3)) ;; #x03b + (vu-pair (nop) (addz.x dist-2 dist-2 dist-2)) ;; #x03c + (vu-pair (nop) (addz.x denom-2 denom-2 denom-2)) ;; #x03d + (vu-pair (div Q dist-1.x denom-1.x) (addz.x dist-3 dist-3 dist-3)) ;; #x03e + (vu-pair (nop) (mul.xyzw ray-0 ray-0 Q)) ;; #x03f + (vu-pair (move.xyzw scaled-ray-2 ray-2) (addz.x denom-3 denom-3 denom-3)) ;; #x040 + (vu-pair (move.xyzw scaled-ray-3 ray-3) (addw.x dist-2 dist-2 dist-2)) ;; #x041 + (vu-pair (nop) (addw.x dist-3 dist-3 dist-3 :e)) ;; #x042 + (vu-pair (nop) (sub.xyzw held-0 held-0 ray-0)) ;; #x043 + ;; The drain. Every instruction that would start a new group is gone; what is left finishes the + ;; group still in flight and writes all four duals. + (label project-verts-drain) + (vu-pair (nop) (mul.xyzw scaled-ray-1 ray-1 Q)) ;; #x044 + (vu-pair (div Q dist-2.x denom-2.x) (nop)) ;; #x045 + (vu-pair (move.xyzw dual-0 held-0) (nop)) ;; #x046 + (vu-pair (nop) (nop)) ;; #x047 + (vu-pair (move.xyzw dual-2 held-2) (nop)) ;; #x048 + (vu-pair (move.xyzw dual-3 held-3) (sub.xyzw dual-1 held-1 scaled-ray-1)) ;; #x049 + (vu-pair (nop) (nop)) ;; #x04a + (vu-pair (nop) (nop)) ;; #x04b + (vu-pair (div Q dist-3.x denom-3.x) (nop)) ;; #x04c + (vu-pair (nop) (mul.xyzw scaled-ray-2 scaled-ray-2 Q)) ;; #x04d + (vu-pair (nop) (nop)) ;; #x04e + (vu-pair (nop) (nop)) ;; #x04f + (vu-pair (nop) (nop)) ;; #x050 + (vu-pair (nop) (sub.xyzw dual-2 dual-2 scaled-ray-2)) ;; #x051 + (vu-pair (nop) (nop)) ;; #x052 + (vu-pair (nop) (mul.xyzw scaled-ray-3 scaled-ray-3 Q)) ;; #x053 + (vu-pair (nop) (nop)) ;; #x054 + (vu-pair (nop) (nop)) ;; #x055 + (vu-pair (nop) (nop :e)) ;; #x056 + (vu-pair (nop) (sub.xyzw dual-3 dual-3 scaled-ray-3)))) ;; #x057 + ) + +(#unless PC_PORT + ;; The workspace is a bump arena. dcache-top starts at data and each stage appends its own list to + ;; it: the dual vertices first, then the facing single-sided triangles, the single-sided silhouette + ;; edges, and the double-sided silhouette edges. Nothing is ever freed, and nothing is reordered, + ;; because every stage's table pointer is recorded in the header for the packet builders that follow. + ;; + ;; The mesh itself stays in the scratchpad where toSPR left it: skinning writes over the source + ;; vertices and triangle classification writes each face's answer into the spare byte of its own + ;; shadow-tri record, so the arena only holds what the geometry has no room for. + (asm-data + (label shadow-negative-two) + (word #xc0000000) + (label shadow-one) + (word #x3f800000)) + + (defun shadow-xform-verts () + "Skin the caster's shadow mesh in place in the scratchpad. A vertex names either one joint, or two + joints and a blend weight in its w lane, and never more." + (declare (asm-func none) (allow-saved-regs)) + ;; The mesh arrives packed: a header, the vertices, then a stream of shadow-matrix-ref pairs, one + ;; per vertex, with every single-joint vertex ahead of every two-joint vertex so the two cases can + ;; be separate loops. The joint transforms followed the geometry into the scratchpad, one pris-mtx + ;; each; only the t-mtx half is wanted here. + (rlet ((geo :reg a0 :type shadow-header) + ;; a0 gives up the header as soon as the two-joint count has been read out of it. + (two-count :reg a0) + (work :reg a1 :type shadow-dcache) + ;; a1 then walks the reference stream. + (joint-ref :reg a1 :type shadow-matrix-ref) + (vertex :reg a2 :type shadow-vertex) + (matrix-table :reg v1) + (single-count :reg a3) + ;; The two joint transforms. Each register briefly holds a joint index before it becomes the + ;; address of that joint's record. + (matrix-a :reg t0 :type pris-mtx) + (matrix-b :reg a3 :type pris-mtx) + (mtx-a-0 :reg vf1) + (mtx-a-1 :reg vf2) + (mtx-a-2 :reg vf3) + (mtx-a-3 :reg vf4) + (mtx-b-0 :reg vf5) + (mtx-b-1 :reg vf6) + (mtx-b-2 :reg vf7) + (mtx-b-3 :reg vf8) + (skinned :reg vf9) + (blend :reg vf10) + (result :reg v0)) + (asm-block locate-mesh + (nop!) + (l.w matrix-table (-> geo qwc-data)) + (nop!) + (l.w vertex (-> geo ofs-verts)) + (sll matrix-table matrix-table 4) + (l.w matrix-a (-> geo ofs-refs)) + (add vertex vertex geo) + (l.h single-count (-> geo num-verts)) + (add matrix-a matrix-a geo) + ;; num-joints only sized the matrix transfer; skinning follows the per-vertex references. + (l.w t1 (-> geo num-joints)) + (add matrix-table geo matrix-table) + ;; The rest of the renderer reads the vertices out of the scratchpad, not the arena. + (s.w vertex (-> work vtx-table)) + (add.i matrix-table matrix-table SHADOW-MATRIX-BIAS) + (nop!) + (m joint-ref matrix-a) + (l.h matrix-a (-> geo num-twos)) + (m vertex vertex) + (nop!) + (sub single-count single-count matrix-a) + ;; This scratchpad address is never read: both loops load t0 before using it. + (lui matrix-a #x7000) + (ori matrix-a matrix-a 2608) + (b.z single-count xform-two-joint-verts :delay (nop!))) + ;; One joint. The masked multiply-add leaves w alone, so the vertex keeps its weight -- harmless + ;; here, and the edge and triangle tables index this table by position either way. + (asm-block skin-one-joint-vertices + (label xform-one-joint-loop) + (add.i single-count single-count -1) + (l.bu matrix-a (-> joint-ref joint-0)) + (nop!) + ;; joint-1 comes along with it and is ignored. + (l.bu t1 (-> joint-ref joint-1)) + (nop!) + (add.i joint-ref joint-ref (type-size shadow-matrix-ref)) + ;; one pris-mtx per joint + (sll matrix-a matrix-a 7) + (nop!) + (add matrix-a matrix-a matrix-table) + (nop!) + (nop!) + (l.vf mtx-a-0 (-> matrix-a t-mtx vector 0)) + (nop!) + (l.vf mtx-a-1 (-> matrix-a t-mtx vector 1)) + (nop!) + (l.vf mtx-a-2 (-> matrix-a t-mtx vector 2)) + (nop!) + (l.vf mtx-a-3 (-> matrix-a t-mtx vector 3)) + (nop!) + (l.vf skinned vertex) + (vmulaw.xyzw acc mtx-a-3 vf0) + (nop!) + (vmaddax.xyzw acc mtx-a-0 skinned) + (nop!) + (vmadday.xyzw acc mtx-a-1 skinned) + (nop!) + (vmaddz.xyz skinned mtx-a-2 skinned) + (nop!) + (nop!) + (s.vf skinned vertex) + (add.i vertex vertex (type-size shadow-vertex)) + (nop!) + (b.nz single-count xform-one-joint-loop :delay (nop!))) + ;; Two joints. Both transforms are run on the same source vertex, then blended by the weight the + ;; vertex carries in w: + ;; + ;; out = a * weight + b * (1 - weight) + ;; + ;; The 1 - weight complement is taken before either transform runs, into the w lane of the + ;; register that will hold the first result, so nothing has to be materialized to hold it. The + ;; final vaddx.w rebuilds a w of one out of vf0 alone, which is the cheapest 1.0 the VU has. + (asm-block skin-two-joint-vertices + (label xform-two-joint-verts) + (nop!) + (l.h two-count (-> geo num-twos)) + (b.z two-count xform-done :delay (nop!)) + (label xform-two-joint-loop) + (add.i two-count two-count -1) + (l.bu matrix-a (-> joint-ref joint-0)) + (nop!) + (l.bu matrix-b (-> joint-ref joint-1)) + (sll matrix-a matrix-a 7) + (add.i joint-ref joint-ref (type-size shadow-matrix-ref)) + (sll matrix-b matrix-b 7) + (add matrix-a matrix-a matrix-table) + (add matrix-b matrix-b matrix-table) + (l.vf mtx-a-0 (-> matrix-a t-mtx vector 0)) + (nop!) + (l.vf mtx-a-1 (-> matrix-a t-mtx vector 1)) + (nop!) + (l.vf mtx-a-2 (-> matrix-a t-mtx vector 2)) + (nop!) + (l.vf mtx-a-3 (-> matrix-a t-mtx vector 3)) + (nop!) + (l.vf skinned vertex) + (nop!) + (l.vf mtx-b-0 (-> matrix-b t-mtx vector 0)) + (nop!) + (l.vf mtx-b-1 (-> matrix-b t-mtx vector 1)) + (nop!) + (l.vf mtx-b-2 (-> matrix-b t-mtx vector 2)) + (nop!) + (l.vf mtx-b-3 (-> matrix-b t-mtx vector 3)) + (vsubw.w blend vf0 skinned) + (nop!) + (vmulaw.xyzw acc mtx-a-3 vf0) + (nop!) + (vmaddax.xyzw acc mtx-a-0 skinned) + (nop!) + (vmadday.xyzw acc mtx-a-1 skinned) + (nop!) + (vmaddz.xyz blend mtx-a-2 skinned) + (nop!) + (vmulaw.xyzw acc mtx-b-3 vf0) + (nop!) + (vmaddax.xyzw acc mtx-b-0 skinned) + (nop!) + (vmadday.xyzw acc mtx-b-1 skinned) + (nop!) + (vmaddz.xyz skinned mtx-b-2 skinned) + (nop!) + (vmulaw.xyz acc blend skinned) + (nop!) + (vmaddw.xyz skinned skinned blend) + (nop!) + (vaddx.w skinned vf0 vf0) + (nop!) + (nop!) + (s.vf skinned vertex) + (add.i vertex vertex (type-size shadow-vertex)) + (nop!) + (b.nz two-count xform-two-joint-loop :delay (nop!)) + (nop!) + (nop!)) + (asm-block return + (label xform-done) + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-calc-dual-verts () + "Project every vertex of the caster's mesh along the ray from the light locus onto the ground + plane, and append the resulting dual vertices to the workspace arena." + (declare (asm-func none) (allow-saved-regs)) + ;; A vertex and its dual are the two ends of one extrusion, so a silhouette edge becomes the quad + ;; joining one pair to the next and a facing triangle becomes a cap on either end. VU0 takes four + ;; vertices per call and answers one call late, so this loop hands over a group and then reads back + ;; the group before it. The quadword loads run ahead of the count and can read up to three vertices + ;; past the end of the table; those extra results are computed and then not stored. + (rlet ((geo :reg a0 :type shadow-header) + (count :reg a0) + (work :reg a1 :type shadow-dcache) + (vertex :reg a2 :type shadow-vertex) + (dual :reg v1) + (dual-base :reg a3) + ;; The four vertices in flight, shuttled through EE registers because a coprocessor move is + ;; the only way into VU0's register file. + (quad-0 :reg a3) + (quad-1 :reg t0) + (quad-2 :reg t1) + (quad-3 :reg t2) + (locus :reg vf1) + (ground-plane :reg vf2) + (vertex-0 :reg vf3) + (vertex-1 :reg vf4) + (vertex-2 :reg vf5) + (vertex-3 :reg vf6) + (dual-0 :reg vf23) + (dual-1 :reg vf24) + (dual-2 :reg vf25) + (dual-3 :reg vf26) + (result :reg v0)) + (asm-block start-dual-vertex-table + (nop!) + (l.w dual (-> work dcache-top)) + (nop!) + (l.w vertex (-> work vtx-table)) + ;; Duals are written a quadword at a time, so round the arena cursor up before claiming it. + (add.i dual dual 15) + (l.vf locus (-> work center)) + (sra dual dual 4) + (l.vf ground-plane (-> work plane)) + (sll dual-base dual 4) + (l.h count (-> geo num-verts)) + (m dual dual-base) + (s.w dual-base (-> work ptr-dual-verts)) + (m vertex vertex) + (nop!) + (b.z count dual-verts-done :delay (nop!))) + ;; Two groups go in before the first comes out: one to fill the pipeline and one to run while the + ;; first is finishing. + (asm-block prime-vu0-pipeline + (nop!) + (l.q quad-0 vertex) + (nop!) + (l.q quad-1 vertex 16) + (nop!) + (l.q quad-2 vertex 32) + (nop!) + (l.q quad-3 vertex 48) + (add.i vertex vertex 64) + (m vertex-0 quad-0) + (nop!) + (m.ni vertex-1 quad-1) + (nop!) + (m.ni vertex-2 quad-2) + (nop!) + (m.ni vertex-3 quad-3) + (callms SHADOW-VU0-PROJECT-VERTS) + (nop!) + (add.i count count -4) + (l.q quad-0 vertex) + (b.le count r0 drain-vu0-pipeline :delay (l.q quad-1 vertex 16)) + (nop!) + (l.q quad-2 vertex 32) + (nop!) + (l.q quad-3 vertex 48) + (add.i vertex vertex 64) + (m vertex-0 quad-0) + (nop!) + (m.ni vertex-1 quad-1) + (nop!) + (m.ni vertex-2 quad-2) + (nop!) + (m.ni vertex-3 quad-3)) + ;; Start this group, then spend the divide latency reading out the previous group's four duals + ;; and fetching the next four vertices. The runs of no-ops are that latency: a VU0 vector + ;; register is not readable for seven instructions after the microprogram writes it, and the + ;; microprogram itself needs a head start before the first result exists at all. + (asm-block project-vertex-groups + (label project-group-loop) + (callms SHADOW-VU0-PROJECT-VERTS) + (nop! :count 10) + (m quad-0 dual-0) + (nop! :count 7) + (s.q quad-0 dual) + (nop! :count 3) + (m.ni quad-0 dual-1) + (nop! :count 7) + (s.q quad-0 dual 16) + (nop! :count 7) + (m.ni quad-0 dual-2) + (nop! :count 7) + (s.q quad-0 dual 32) + (nop! :count 7) + (m.ni quad-0 dual-3) + (nop! :count 7) + (s.q quad-0 dual 48) + (nop! :count 3) + (l.q quad-0 vertex) + (nop!) + (l.q quad-1 vertex 16) + (nop!) + (l.q quad-2 vertex 32) + (add.i count count -4) + (l.q quad-3 vertex 48) + (add.i vertex vertex 64) + (add.i dual dual 64) + (nop!) + (m.ni vertex-0 quad-0) + (nop!) + (m.ni vertex-1 quad-1) + (nop!) + (m.ni vertex-2 quad-2) + (b.gt count r0 project-group-loop :delay (m.ni vertex-3 quad-3))) + ;; Fewer than four vertices are left. The drain entry finishes the group in flight; a0 is zero or + ;; negative by exactly the number of vertices the last group overran, and a2, t0, and t2 hold it + ;; biased so that one test each picks the cut-off. + (asm-block drain-vu0-pipeline + (label drain-vu0-pipeline) + (callms SHADOW-VU0-PROJECT-DRAIN) + (nop!) + (vnop) + (nop!) + (add.i a2 count 3) + (m quad-0 dual-0) + (add.i t0 count 2) + (m t1 dual-1) + (add.i t2 count 1) + (m t3 dual-2) + (add.i count count 4) + (m t4 dual-3) + (b.z a2 dual-verts-advance :delay (s.q quad-0 dual)) + (b.z t0 dual-verts-advance :delay (s.q t1 dual 16)) + (b.z t2 dual-verts-advance :delay (s.q t3 dual 32)) + (nop!) + (s.q t4 dual 48)) + (asm-block finish-dual-vertex-table + (label dual-verts-advance) + (sll count count 4) + (nop!) + (add dual dual count) + (nop!) + (label dual-verts-done) + (s.w dual (-> work dcache-top)) + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-scissor-edges () + "Clip every extrusion against the camera's near plane so no shadow wall reaches behind the eye." + (declare (asm-func none) (allow-saved-regs)) + ;; near-plane is (0, 0, 1, -2d) in camera space, so a point's distance from it is just z - 2d: the + ;; volume is cut off at twice the near-plane distance, which keeps a wall that passes the camera + ;; from exploding across the whole screen after the perspective divide. + ;; + ;; The distance is deliberately copied into lane y before it is read back, because the EE tests the + ;; sign of the quadword's low doubleword -- lane y -- with an ordinary bltz. When exactly one end of + ;; an extrusion is outside, that end is replaced with the intersection point; the arithmetic is the + ;; same either way, only the destination changes, so the pair keeps its orientation. + (rlet ((geo :reg a0 :type shadow-header) + (work :reg a1 :type shadow-dcache) + ;; Both cursors take over argument registers once the tables have been found. + (dual :reg a0) + (vertex :reg a1) + (count :reg v1) + ;; a2 and a3 first carry the two table pointers and then the two plane distances. + (vertex-table :reg a2) + (dual-table :reg a3) + (vertex-dist :reg a2) + (dual-dist :reg a3) + (vertex-pos :reg vf1) + (dual-pos :reg vf2) + (near-plane :reg vf3) + (extrusion :reg vf4) + (numerator :reg vf5) + (denominator :reg vf6) + (vertex-test :reg vf7) + (dual-test :reg vf8) + (result :reg v0)) + (asm-block find-tables + (nop!) + (l.w dual-table (-> work ptr-dual-verts)) + (nop!) + (l.w vertex-table (-> work vtx-table)) + (nop!) + (l.vf near-plane (-> work near-plane)) + (nop!) + (l.h count (-> geo num-verts)) + (m dual dual-table) + (nop!) + (m vertex vertex-table) + (nop!) + (b.z count near-clip-done :delay (nop!))) + (asm-block near-clip-pairs + (label near-clip-loop) + (l.vf vertex-pos vertex) + (l.vf dual-pos dual) + (vaddw.z vertex-test vertex-pos near-plane) + (vaddw.z dual-test dual-pos near-plane) + (vsubz.z denominator vertex-pos dual-pos) + (vaddw.z numerator vertex-pos near-plane) + ;; Move each distance from z into y so a scalar sign test can find it. + (vaddz.y vertex-test vf0 vertex-test) + (vaddz.y dual-test vf0 dual-test) + (vsub.xyz extrusion dual-pos vertex-pos) + (m vertex-dist vertex-test) + (m dual-dist dual-test) + (b.lt vertex-dist r0 near-clip-vertex-outside :delay (nop!)) + (b.gt dual-dist r0 near-clip-next :delay (nop!)) + (b near-clip-move-dual :delay (nop!))) + ;; The original vertex is behind the plane. If the dual is too the whole extrusion is gone and + ;; nothing needs moving; otherwise the original slides forward onto the plane. + (asm-block near-clip-move-vertex + (label near-clip-vertex-outside) + (b.lt dual-dist r0 near-clip-next :delay (nop!)) + (vdiv Q numerator.z denominator.z) + (vwaitq) + (vmulq.xyzw extrusion extrusion Q) + (vnop) + (vnop) + (vnop) + (add.vf vertex-pos vertex-pos extrusion) + (b near-clip-next :delay (s.vf vertex-pos vertex))) + ;; The dual is the end that is behind the plane, so the same intersection point is written there + ;; instead. The wall keeps its winding either way. + (asm-block near-clip-move-dual + (label near-clip-move-dual) + (vdiv Q numerator.z denominator.z) + (vwaitq) + (vmulq.xyzw extrusion extrusion Q) + (vnop) + (vnop) + (vnop) + (add.vf vertex-pos vertex-pos extrusion) + (b near-clip-next :delay (s.vf vertex-pos dual))) + (asm-block next-pair + (label near-clip-next) + (add.i vertex vertex 16) + (add.i dual dual 16) + (add.i count count -1) + (b.nz count near-clip-loop :delay (nop!))) + (asm-block return + (label near-clip-done) + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!))) + ) + + (defun shadow-scissor-top () + "Pull any original vertex that stands above top-plane down onto it, shortening the volume from the + top without disturbing where it lands." + (declare (asm-func none) (allow-saved-regs)) + ;; Only the near end of each extrusion moves: the dual is already on the ground plane and must stay + ;; there. Sliding the original down its own extrusion keeps the wall planar, so the volume stays + ;; closed. This runs only when the caster asks for it with the scissor-top flag, and it is what + ;; keeps a tall character from casting shadow through a ceiling it is standing under. + (rlet ((geo :reg a0 :type shadow-header) + (work :reg a1 :type shadow-dcache) + (count :reg a0) + (dual :reg a1) + (vertex :reg v1) + (dist :reg a2) + (vertex-pos :reg vf1) + (dual-pos :reg vf2) + (top-plane :reg vf3) + (extrusion :reg vf4) + (numerator :reg vf5) + (denominator :reg vf6) + (result :reg v0)) + (asm-block find-tables + (nop!) + (l.w dist (-> work ptr-dual-verts)) + (nop!) + (l.w vertex (-> work vtx-table)) + (nop!) + (l.vf top-plane (-> work top-plane)) + (nop!) + (l.h count (-> geo num-verts)) + (m dual dist) + (nop!) + (m vertex vertex) + (nop!) + (b.z count top-clip-done :delay (nop!))) + (asm-block top-clip-pairs + (label top-clip-loop) + (l.vf vertex-pos vertex) + (l.vf dual-pos dual) + (vsub.xyzw extrusion dual-pos vertex-pos) + ;; The plane's d rides in w and a vertex's w is one, so folding all four lanes of the product + ;; gives the signed distance; the extrusion has no w, so only three lanes fold for the slope. + (mul.vf numerator vertex-pos top-plane) + (vmul.xyz denominator extrusion top-plane) + (vaddx.y numerator numerator numerator) + (vaddy.x denominator denominator denominator) + (vaddz.y numerator numerator numerator) + (vaddz.x denominator denominator denominator) + (vaddw.y numerator numerator numerator) + (m dist numerator) + (nop!) + (b.lt dist r0 top-clip-next :delay (nop!)) + (vdiv Q numerator.y denominator.x) + (vwaitq) + (vmulq.xyzw extrusion extrusion Q) + (vsub.xyzw vertex-pos vertex-pos extrusion) + (s.vf vertex-pos vertex)) + (asm-block next-pair + (label top-clip-next) + (add.i vertex vertex 16) + (add.i dual dual 16) + (add.i count count -1) + (b.nz count top-clip-loop :delay (nop!))) + (asm-block return + (label top-clip-done) + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!))) + ) + + (defun shadow-init-vars () + "Move the light locus, the light direction, and both clipping planes from world space into camera + space, and set up the arena cursor." + (declare (asm-func none) (allow-saved-regs)) + ;; Everything downstream of here -- projection, near clipping, top clipping, facing tests -- works + ;; in camera space, because that is the space VU1's perspective matrix expects and it is cheaper to + ;; move six quadwords once than every vertex twice. + ;; + ;; A direction only needs the rotation. A plane needs the rotation on its normal and then + ;; + ;; d' = d - dot(camera translation, rotated normal) + ;; + ;; which is the vsub?.w chain: the product of the translation row with the plane already has d + ;; sitting in w (the masked multiply-add never wrote it), so subtracting x, y, and z out of w in + ;; turn leaves exactly that. + (rlet ((camera :reg v1 :type math-camera) + (work :reg a1 :type shadow-dcache) + (cam-row-0 :reg vf7) + (cam-row-1 :reg vf8) + (cam-row-2 :reg vf9) + (cam-trans :reg vf10) + (light-dir :reg vf1) + (locus :reg vf2) + (ground-plane :reg vf11) + (top-plane :reg vf12) + (ground-fold :reg vf13) + (top-fold :reg vf14) + (result :reg v0)) + (asm-block camera-space-projection + (m! camera *math-camera*) + (m camera camera) + (l.vf cam-row-0 (-> camera camera-rot vector 0)) + (l.vf cam-row-1 (-> camera camera-rot vector 1)) + (l.vf cam-row-2 (-> camera camera-rot vector 2)) + (l.vf cam-trans (-> camera camera-rot vector 3)) + (l.vf light-dir (-> work light-dir)) + (l.vf ground-plane (-> work plane)) + (l.vf top-plane (-> work top-plane)) + (l.vf locus (-> work center)) + (vmulax.xyzw acc cam-row-0 light-dir) + (vmadday.xyzw acc cam-row-1 light-dir) + (vmaddz.xyzw light-dir cam-row-2 light-dir) + (vmulax.xyzw acc cam-row-0 ground-plane) + (vmadday.xyzw acc cam-row-1 ground-plane) + (vmaddz.xyz ground-plane cam-row-2 ground-plane) + (vmulax.xyzw acc cam-row-0 top-plane) + (vmadday.xyzw acc cam-row-1 top-plane) + (vmaddz.xyz top-plane cam-row-2 top-plane) + (mul.vf ground-fold cam-trans ground-plane) + (vmulaw.xyzw acc cam-trans vf0) + (vmaddax.xyzw acc cam-row-0 locus) + (mul.vf top-fold cam-trans top-plane) + (vsubx.w ground-fold ground-fold ground-fold) + (vsubx.w top-fold top-fold top-fold) + (vmadday.xyzw acc cam-row-1 locus) + (vmaddz.xyzw locus cam-row-2 locus) + (vsuby.w ground-fold ground-fold ground-fold) + (vsuby.w top-fold top-fold top-fold) + ;; Only w is written back into the plane; its rotated normal is already in xyz. + (vsubz.w ground-plane ground-fold ground-fold) + (vsubz.w top-plane top-fold top-fold) + (s.vf locus (-> work center)) + (s.vf light-dir (-> work light-dir)) + (s.vf ground-plane (-> work plane)) + (s.vf top-plane (-> work top-plane))) + (asm-block return + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-find-facing-single-tris () + "Mark every single-sided triangle that faces the light and collect the marked ones into the + workspace arena." + (declare (asm-func none) (allow-saved-regs)) + ;; A single-sided mesh casts from the faces the light can see, so each triangle's cross product is + ;; dotted with the light direction and the sign decides. The answer is written back into the spare + ;; faces byte of the triangle's own record, because the edge pass needs to ask a triangle which way + ;; it faced without keeping a parallel table. + ;; + ;; VU0 classifies four triangles at a time, so the addresses of twelve vertices have to be produced + ;; per call. That is done entirely in the multimedia unit: one quadword holds four shadow-tri + ;; records, and unpacking bytes to halfwords, shifting left by four to scale an index into a + ;; sixteen-byte vertex, unpacking halfwords to words, and adding the broadcast table base turns + ;; four records into three quadwords of four ready-made addresses. Only extracting them for the + ;; load instructions costs anything, which is what pcpyud and dsra32 are doing. + (rlet ((geo :reg a0 :type shadow-header) + ;; a0 becomes the vertex table base, broadcast into all four packed words. + (vertex-base :reg a0) + (work :reg a1 :type shadow-dcache) + (list-base :reg v1) + (list :reg a2) + (tri :reg a3 :type shadow-tri) + (count :reg t0) + ;; A triangle that faces the light gets a one in its faces byte; zero means it does not. + (facing-mark :reg t1) + (indices :reg t3) + (light-dir :reg vf1) + (tri-a-0 :reg vf2) + (tri-a-1 :reg vf3) + (tri-a-2 :reg vf4) + (edge-0 :reg vf5) + (edge-1 :reg vf6) + (tri-b-0 :reg vf7) + (tri-b-1 :reg vf8) + (tri-b-2 :reg vf9) + (tri-c-0 :reg vf12) + (tri-c-1 :reg vf13) + (tri-c-2 :reg vf14) + (tri-d-0 :reg vf17) + (tri-d-1 :reg vf18) + (tri-d-2 :reg vf19) + (facing-a :reg vf22) + (facing-b :reg vf23) + (facing-c :reg vf24) + (facing-d :reg vf25) + (result :reg v0)) + ;; t2 and t4 through t9, plus s4, s5, gp, and ra, are the address shuttle. They hold a different + ;; packed quantity every two instructions and are left with physical names for that reason; the + ;; block comment above describes what the sequence as a whole is doing. + (asm-block enter-classifier + (add.i sp sp -64) + (s.d ra sp) + (s.q s4 sp 16) + (s.q s5 sp 32) + (s.q gp sp 48) + (l.w list-base (-> work dcache-top)) + (l.h count (-> geo num-single-tris)) + (m list list-base) + (l.w tri (-> geo ofs-single-tris)) + (add vertex-base tri geo) + (m tri vertex-base) + ;; VU0 entry 0 wants only the light direction; these two are overwritten before they are read. + (l.vf tri-a-0 (-> work center)) + (l.vf light-dir (-> work light-dir)) + (l.vf vf11 (-> work plane)) + (l.w vertex-base (-> work vtx-table)) + (pextlw vertex-base vertex-base vertex-base) + (pextlw vertex-base vertex-base vertex-base) + (add.i count count -4) + (add.i facing-mark r0 1) + (b.lt count r0 classify-remainder :delay (add.i count count 4)) + (nop!) + (l.q indices tri) + (nop!) + (nop!)) + (asm-block address-four-triangles + (pextub t2 r0 indices) + (mmi-nop!) + (pextlb indices r0 indices) + (mmi-nop!) + (sll.h t2 t2 4) + (mmi-nop!) + (sll.h t4 indices 4) + (mmi-nop!) + (pextuh indices r0 t4) + (mmi-nop!) + (pextlh t4 r0 t4) + (mmi-nop!) + (pextuh t7 r0 t2) + (mmi-nop!) + (pextlh t5 r0 t2) + (mmi-nop!) + (add.w t6 t4 vertex-base) + (mmi-nop!) + (pcpyud t4 t6 r0) + (l.q t2 t6) + (add.w t8 indices vertex-base) + (l.q indices t4) + (pcpyud t9 t8 r0) + (l.q t4 t8) + (dsra32 t6 t6 0) + (dsra32 t8 t8 0) + (add.w s5 t5 vertex-base) + (l.q t5 t9) + (pcpyud t9 s5 r0) + (l.q t6 t6) + (add.w gp t7 vertex-base) + (l.q t7 t8) + (pcpyud ra gp r0) + (l.q t8 s5) + (dsra32 s5 s5 0) + (dsra32 s4 gp 0) + (nop!) + (l.q s5 s5) + (nop!) + (l.q t9 t9) + (nop!) + (l.q gp gp) + (nop!) + (l.q s4 s4) + (nop!) + (l.q ra ra) + (nop!) + (m.ni tri-a-0 t2) + (nop!) + (m.ni tri-a-1 t6) + (nop!) + (m.ni tri-a-2 indices) + (nop!) + (m.ni tri-b-0 t4) + (nop!) + (m.ni tri-b-1 t7) + (nop!) + (m.ni tri-b-2 t5) + (nop!) + (m.ni tri-c-0 t8) + (nop!) + (m.ni tri-c-1 s5) + (nop!) + (m.ni tri-c-2 t9) + (nop!) + (m.ni tri-d-0 gp) + (nop!) + (m.ni tri-d-1 s4) + (nop!) + (m.ni tri-d-2 ra)) + ;; Hand the loaded group to VU0 and immediately start addressing the next one; the previous + ;; group's four answers come back at the bottom. Reading a negative dot product means the face + ;; turns toward the light. + (asm-block classify-triangle-groups + (label classify-four-tris) + (nop!) + (l.q indices tri 16) + (add.i count count -4) + (callms SHADOW-VU0-CLASSIFY-TRIS) + (pextub t2 r0 indices) + (mmi-nop!) + (pextlb indices r0 indices) + (mmi-nop!) + (sll.h t2 t2 4) + (mmi-nop!) + (sll.h t4 indices 4) + (mmi-nop!) + (pextuh indices r0 t4) + (mmi-nop!) + (pextlh t4 r0 t4) + (mmi-nop!) + (pextuh t7 r0 t2) + (mmi-nop!) + (pextlh t5 r0 t2) + (mmi-nop!) + (add.w t6 t4 vertex-base) + (mmi-nop!) + (pcpyud t4 t6 r0) + (l.q t2 t6) + (add.w t8 indices vertex-base) + (l.q indices t4) + (pcpyud t9 t8 r0) + (l.q t4 t8) + (dsra32 t6 t6 0) + (dsra32 t8 t8 0) + (add.w s5 t5 vertex-base) + (l.q t5 t9) + (pcpyud t9 s5 r0) + (l.q t6 t6) + (add.w gp t7 vertex-base) + (l.q t7 t8) + (pcpyud ra gp r0) + (l.q t8 s5) + (dsra32 s5 s5 0) + (dsra32 s4 gp 0) + (nop!) + (l.q s5 s5) + (nop!) + (l.q t9 t9) + (nop!) + (l.q gp gp) + (nop!) + (l.q s4 s4) + (nop!) + (l.q ra ra) + (nop!) + (m.ni tri-a-0 t2) + (nop!) + (m.ni tri-a-1 t6) + (nop!) + (m.ni tri-a-2 indices) + (nop!) + (m.ni tri-b-0 t4) + (nop!) + (m.ni tri-b-1 t7) + (nop!) + (m.ni tri-b-2 t5) + (nop!) + (m.ni tri-c-0 t8) + (nop!) + (m.ni tri-c-1 s5) + (nop!) + (m.ni tri-c-2 t9) + (nop!) + (m.ni tri-d-0 gp) + (nop!) + (m.ni tri-d-1 s4) + (nop!) + (m.ni tri-d-2 ra) + (nop!) + (m.ni indices facing-a) + (nop!) + (m.ni t4 facing-b) + (nop!) + (m.ni t2 facing-c) + (b.ge indices r0 checked-tri-0 :delay (m.ni indices facing-d)) + (s.b facing-mark (-> tri faces)) + (nop!) + (s.w tri list) + (add.i list list 4) + (label checked-tri-0) + (b.ge t4 r0 checked-tri-1 :delay (add.i tri tri (type-size shadow-tri))) + (s.b facing-mark (-> tri faces)) + (nop!) + (s.w tri list) + (add.i list list 4) + (label checked-tri-1) + (b.ge t2 r0 checked-tri-2 :delay (add.i tri tri (type-size shadow-tri))) + (s.b facing-mark (-> tri faces)) + (nop!) + (s.w tri list) + (add.i list list 4) + (label checked-tri-2) + (b.ge indices r0 checked-tri-3 :delay (add.i tri tri (type-size shadow-tri))) + (s.b facing-mark (-> tri faces)) + (nop!) + (s.w tri list) + (add.i list list 4) + (label checked-tri-3) + (b.gt count r0 classify-four-tris :delay (add.i tri tri (type-size shadow-tri)))) + ;; The last one to three triangles, one at a time and without VU0: the cross product and dot fit + ;; in the inline VU0 macro instructions and the batch would cost more than it saves. + (asm-block classify-remaining-triangles + (label classify-remainder) + (b.le count r0 single-tris-done :delay (nop!)) + (label classify-one-tri) + (l.bu t2 (-> tri ind-0)) + (l.bu indices (-> tri ind-1)) + (l.bu facing-mark (-> tri ind-2)) + (sll t2 t2 4) + (sll indices indices 4) + (sll facing-mark facing-mark 4) + (add t2 t2 vertex-base) + (add indices indices vertex-base) + (add facing-mark facing-mark vertex-base) + (l.vf tri-a-0 t2) + (l.vf tri-a-1 indices) + (l.vf tri-a-2 facing-mark) + (vsub.xyzw edge-0 tri-a-1 tri-a-0) + (vsub.xyzw edge-1 tri-a-2 tri-a-0) + (vopmula.xyz acc edge-0 edge-1) + (vopmsub.xyz edge-0 edge-1 edge-0) + (vmul.xyz edge-0 edge-0 light-dir) + (vaddx.y edge-0 edge-0 edge-0) + (vaddz.y edge-0 edge-0 edge-0) + (m facing-mark edge-0) + (nop!) + (b.ge facing-mark r0 next-remainder-tri :delay (add.i facing-mark r0 1)) + (s.w tri list) + (add.i list list 4) + (s.b facing-mark (-> tri faces)) + (label next-remainder-tri) + (add.i count count -1) + (b.nz count classify-one-tri :delay (add.i tri tri (type-size shadow-tri)))) + ;; Record the list for shadow-add-facing-single-tris and hand the arena back grown by it. + (asm-block publish-single-tri-list + (label single-tris-done) + (sub vertex-base list list-base) + (sra vertex-base vertex-base 2) + (s.w vertex-base (-> work num-facing-single-tris)) + (s.w list-base (-> work single-tri-list)) + (s.w list (-> work dcache-top)) + (m result r0) + (l.d ra sp) + (l.q gp sp 48) + (l.q s5 sp 32) + (l.q s4 sp 16) + (jr ra :delay (add.i sp sp 64)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-find-single-edges () + "Collect the silhouette of a single-sided mesh from its edge adjacency table." + (declare (asm-func none) (allow-saved-regs)) + ;; An edge is on the silhouette when the light can see exactly one of the two triangles that share + ;; it. tri-1 of 255 means there is no second triangle at all -- an open boundary -- and such an edge + ;; is on the silhouette whenever its one face is lit. + ;; + ;; Each collected edge is recorded as its byte offset in the source table plus a flag saying which + ;; endpoint to emit first, so that the quad VU1 builds from it always winds outward. The flag is + ;; "tri-0 was the unlit one", which is exactly when the pair has to be swapped; a boundary edge + ;; needs no swap and gets zero, dropped in the branch delay slot before the test even runs. + (rlet ((geo :reg a0 :type shadow-header) + (work :reg a1 :type shadow-dcache) + (single-tris :reg a0 :type shadow-tri) + (list-base :reg a2) + (list :reg v1) + (count :reg a3) + (edge-table :reg t0) + (edge :reg t1 :type shadow-edge) + (no-neighbor :reg t2) + (flip :reg t3) + ;; t3, t4 and t5 each hold a triangle's address and then the faces byte read out of it, so + ;; each has both a pointer name and a value name. + (tri-a :reg t3 :type shadow-tri) + (neighbor :reg t4 :type shadow-tri) + (tri-b :reg t5 :type shadow-tri) + (faces-0 :reg t4) + (faces-1 :reg t5) + (result :reg v0)) + (asm-block find-tables + (l.w list-base (-> work dcache-top)) + (l.h count (-> geo num-single-edges)) + (m list list-base) + (l.w edge-table (-> geo ofs-single-edges)) + (b.z count single-edges-done :delay (l.w edge (-> geo ofs-single-tris))) + (add edge-table edge-table geo) + (s.w list-base (-> work single-edge-list)) + (add single-tris edge single-tris) + (s.w edge-table (-> work single-edge-table)) + (m edge edge-table) + (add.i no-neighbor r0 255) + (nop!)) + (asm-block scan-single-edges + (label single-edge-loop) + (add.i count count -1) + (l.bu faces-0 (-> edge tri-1)) + (nop!) + (l.bu faces-1 (-> edge tri-0)) + (b.eq faces-0 no-neighbor single-edge-boundary :delay (m flip r0)) + (sll flip faces-1 2) + (sll faces-0 faces-0 2) + (add flip flip single-tris) + (add faces-1 faces-0 single-tris) + (nop!) + (l.bu faces-0 (-> tri-a faces)) + (nop!) + (l.bu faces-1 (-> tri-b faces)) + (sltiu flip faces-0 1) + (nop!) + (b.eq faces-0 faces-1 single-edge-next :delay (nop!)) + (b single-edge-keep :delay (nop!))) + ;; No second triangle. Keep the edge only if its one face is lit. + (asm-block boundary-edge + (label single-edge-boundary) + (sll faces-0 faces-1 2) + (nop!) + (add faces-0 faces-0 single-tris) + (nop!) + (nop!) + (l.bu faces-0 (-> neighbor faces)) + (b.z faces-0 single-edge-next :delay (nop!))) + (asm-block keep-edge + (label single-edge-keep) + (sub faces-0 edge edge-table) + (s.h flip list 2) + (s.h faces-0 list) + (add.i list list 4)) + (asm-block next-edge + (label single-edge-next) + (b.nz count single-edge-loop :delay (add.i edge edge (type-size shadow-edge)))) + (asm-block publish-single-edge-list + (label single-edges-done) + (sub single-tris list list-base) + (sra single-tris single-tris 2) + (s.w single-tris (-> work num-single-edges)) + (s.w list (-> work dcache-top)) + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-find-facing-double-tris () + "Choose the winding of every double-sided triangle that faces the light and record it in the + triangle's own record." + (declare (asm-func none) (allow-saved-regs)) + ;; Double-sided geometry -- a flag, a cape, a leaf -- always casts, whichever way it is turned, so + ;; there is nothing to reject here. What matters is which of the two windings faces the light, and + ;; that answer is stored in the faces byte for the edge pass and for VU1, which reverses two of the + ;; three indices when it draws the cap. No list is built: every double-sided triangle is emitted. + ;; + ;; The four-at-a-time addressing is the same trick as the single-sided classifier; see the comment + ;; there for what the multimedia sequence computes. + (rlet ((geo :reg a0 :type shadow-header) + ;; a0 becomes the vertex table base, broadcast into all four packed words. + (vertex-base :reg a0) + (work :reg a1 :type shadow-dcache) + (count :reg a1) + (tri :reg v1 :type shadow-tri) + (facing-mark :reg a2) + (indices :reg t0) + (light-dir :reg vf1) + (tri-a-0 :reg vf2) + (tri-a-1 :reg vf3) + (tri-a-2 :reg vf4) + (edge-0 :reg vf5) + (edge-1 :reg vf6) + (tri-b-0 :reg vf7) + (tri-b-1 :reg vf8) + (tri-b-2 :reg vf9) + (tri-c-0 :reg vf12) + (tri-c-1 :reg vf13) + (tri-c-2 :reg vf14) + (tri-d-0 :reg vf17) + (tri-d-1 :reg vf18) + (tri-d-2 :reg vf19) + (facing-a :reg vf22) + (facing-b :reg vf23) + (facing-c :reg vf24) + (facing-d :reg vf25) + (result :reg v0)) + ;; a3 and t1 through t9 plus ra are the address shuttle and keep physical names. + (asm-block enter-classifier + (add.i sp sp -16) + (s.d ra sp) + (l.h facing-mark (-> geo num-double-tris)) + (l.w tri (-> geo ofs-double-tris)) + (add vertex-base tri geo) + (m tri vertex-base) + (s.w vertex-base (-> work double-tri-table)) + (l.vf light-dir (-> work light-dir)) + ;; VU0 entry 0 wants only the light direction; these two are overwritten before they are read. + (l.vf tri-a-0 (-> work center)) + (l.vf vf11 (-> work plane)) + (l.w vertex-base (-> work vtx-table)) + (pextlw vertex-base vertex-base vertex-base) + (pextlw vertex-base vertex-base vertex-base) + (add.i count facing-mark -4) + (add.i facing-mark r0 1) + (b.lt count r0 double-classify-remainder :delay (add.i count count 4)) + (nop!) + (l.q indices tri) + (nop!) + (nop!)) + (asm-block address-four-triangles + (pextub a3 r0 indices) + (mmi-nop!) + (pextlb indices r0 indices) + (mmi-nop!) + (sll.h a3 a3 4) + (mmi-nop!) + (sll.h t1 indices 4) + (mmi-nop!) + (pextuh indices r0 t1) + (mmi-nop!) + (pextlh t1 r0 t1) + (mmi-nop!) + (pextuh t4 r0 a3) + (mmi-nop!) + (pextlh t2 r0 a3) + (mmi-nop!) + (add.w t3 t1 vertex-base) + (mmi-nop!) + (pcpyud t1 t3 r0) + (l.q a3 t3) + (add.w t5 indices vertex-base) + (l.q indices t1) + (pcpyud t6 t5 r0) + (l.q t1 t5) + (dsra32 t3 t3 0) + (dsra32 t5 t5 0) + (add.w t9 t2 vertex-base) + (l.q t2 t6) + (pcpyud t6 t9 r0) + (l.q t3 t3) + (add.w t8 t4 vertex-base) + (l.q t4 t5) + (pcpyud t7 t8 r0) + (l.q t5 t9) + (dsra32 t9 t9 0) + (dsra32 ra t8 0) + (nop!) + (l.q t9 t9) + (nop!) + (l.q t6 t6) + (nop!) + (l.q t8 t8) + (nop!) + (l.q ra ra) + (nop!) + (l.q t7 t7) + (nop!) + (m.ni tri-a-0 a3) + (nop!) + (m.ni tri-a-1 t3) + (nop!) + (m.ni tri-a-2 indices) + (nop!) + (m.ni tri-b-0 t1) + (nop!) + (m.ni tri-b-1 t4) + (nop!) + (m.ni tri-b-2 t2) + (nop!) + (m.ni tri-c-0 t5) + (nop!) + (m.ni tri-c-1 t9) + (nop!) + (m.ni tri-c-2 t6) + (nop!) + (m.ni tri-d-0 t8) + (nop!) + (m.ni tri-d-1 ra) + (nop!) + (m.ni tri-d-2 t7)) + ;; A negative dot means the authored winding already faces the light, and the mark goes in; a + ;; nonnegative one leaves the byte at the zero the packed data shipped with, and VU1 reverses two + ;; of the three indices when it draws that cap. + (asm-block classify-triangle-groups + (label double-classify-four-tris) + (nop!) + (l.q indices tri 16) + (add.i count count -4) + (callms SHADOW-VU0-CLASSIFY-TRIS) + (pextub a3 r0 indices) + (mmi-nop!) + (pextlb indices r0 indices) + (mmi-nop!) + (sll.h a3 a3 4) + (mmi-nop!) + (sll.h t1 indices 4) + (mmi-nop!) + (pextuh indices r0 t1) + (mmi-nop!) + (pextlh t1 r0 t1) + (mmi-nop!) + (pextuh t4 r0 a3) + (mmi-nop!) + (pextlh t2 r0 a3) + (mmi-nop!) + (add.w t3 t1 vertex-base) + (mmi-nop!) + (pcpyud t1 t3 r0) + (l.q a3 t3) + (add.w t5 indices vertex-base) + (l.q indices t1) + (pcpyud t6 t5 r0) + (l.q t1 t5) + (dsra32 t3 t3 0) + (dsra32 t5 t5 0) + (add.w t9 t2 vertex-base) + (l.q t2 t6) + (pcpyud t6 t9 r0) + (l.q t3 t3) + (add.w t8 t4 vertex-base) + (l.q t4 t5) + (pcpyud t7 t8 r0) + (l.q t5 t9) + (dsra32 t9 t9 0) + (dsra32 ra t8 0) + (nop!) + (l.q t9 t9) + (nop!) + (l.q t6 t6) + (nop!) + (l.q t8 t8) + (nop!) + (l.q ra ra) + (nop!) + (l.q t7 t7) + (nop!) + (m.ni tri-a-0 a3) + (nop!) + (m.ni tri-a-1 t3) + (nop!) + (m.ni tri-a-2 indices) + (nop!) + (m.ni tri-b-0 t1) + (nop!) + (m.ni tri-b-1 t4) + (nop!) + (m.ni tri-b-2 t2) + (nop!) + (m.ni tri-c-0 t5) + (nop!) + (m.ni tri-c-1 t9) + (nop!) + (m.ni tri-c-2 t6) + (nop!) + (m.ni tri-d-0 t8) + (nop!) + (m.ni tri-d-1 ra) + (nop!) + (m.ni tri-d-2 t7) + (nop!) + (m.ni indices facing-a) + (nop!) + (m.ni t1 facing-b) + (nop!) + (m.ni a3 facing-c) + (b.ge indices r0 double-checked-tri-0 :delay (m.ni indices facing-d)) + (s.b facing-mark (-> tri faces)) + (nop!) + (label double-checked-tri-0) + (b.ge t1 r0 double-checked-tri-1 :delay (add.i tri tri (type-size shadow-tri))) + (s.b facing-mark (-> tri faces)) + (nop!) + (label double-checked-tri-1) + (b.ge a3 r0 double-checked-tri-2 :delay (add.i tri tri (type-size shadow-tri))) + (s.b facing-mark (-> tri faces)) + (nop!) + (label double-checked-tri-2) + (b.ge indices r0 double-checked-tri-3 :delay (add.i tri tri (type-size shadow-tri))) + (s.b facing-mark (-> tri faces)) + (nop!) + (label double-checked-tri-3) + (b.gt count r0 double-classify-four-tris :delay (add.i tri tri (type-size shadow-tri)))) + (asm-block classify-remaining-triangles + (label double-classify-remainder) + (b.le count r0 double-tris-done :delay (nop!)) + (label double-classify-one-tri) + (l.bu a3 (-> tri ind-0)) + (l.bu indices (-> tri ind-1)) + (l.bu facing-mark (-> tri ind-2)) + (sll a3 a3 4) + (sll indices indices 4) + (sll facing-mark facing-mark 4) + (add a3 a3 vertex-base) + (add indices indices vertex-base) + (add facing-mark facing-mark vertex-base) + (l.vf tri-a-0 a3) + (l.vf tri-a-1 indices) + (l.vf tri-a-2 facing-mark) + (vsub.xyzw edge-0 tri-a-1 tri-a-0) + (vsub.xyzw edge-1 tri-a-2 tri-a-0) + (vopmula.xyz acc edge-0 edge-1) + (vopmsub.xyz edge-0 edge-1 edge-0) + (vmul.xyz edge-0 edge-0 light-dir) + (vaddx.y edge-0 edge-0 edge-0) + (vaddz.y edge-0 edge-0 edge-0) + (m facing-mark edge-0) + (nop!) + (b.ge facing-mark r0 double-next-remainder-tri :delay (add.i facing-mark r0 1)) + (s.b facing-mark (-> tri faces)) + (label double-next-remainder-tri) + (add.i count count -1) + (b.nz count double-classify-one-tri :delay (add.i tri tri (type-size shadow-tri)))) + (asm-block return + (label double-tris-done) + (m result r0) + (l.d ra sp) + (jr ra :delay (add.i sp sp 16)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-find-double-edges () + "Collect the silhouette of a double-sided mesh from its edge adjacency table." + (declare (asm-func none) (allow-saved-regs)) + ;; Double-sided faces always cast, so an interior edge is on the silhouette when its two triangles + ;; chose *different* windings -- the surface folds away from the light there. Such an edge needs two + ;; quads, one for each of the two sheets meeting at it, and gets two records with opposite + ;; orientation flags; an open boundary edge needs only one. + ;; + ;; This is the one place a silhouette record is emitted more than once per edge, and it is why the + ;; double-sided edge list can be twice as long as the source table. + (rlet ((geo :reg a0 :type shadow-header) + (work :reg a1 :type shadow-dcache) + (edge-table :reg a0 :type shadow-edge) + (list-base :reg a2) + (list :reg v1) + (count :reg a3) + (double-tris :reg t0 :type shadow-tri) + (edge :reg t1 :type shadow-edge) + (no-neighbor :reg t2) + ;; t3 and t4 each hold a triangle's address and then the faces byte read out of it. + (tri-b :reg t3 :type shadow-tri) + (tri-a :reg t4 :type shadow-tri) + (faces-1 :reg t3) + (faces-0 :reg t4) + (flip :reg t5) + (result :reg v0)) + (asm-block find-tables + (l.w list-base (-> work dcache-top)) + (l.h count (-> geo num-double-edges)) + (m list list-base) + (l.w edge (-> geo ofs-double-edges)) + (b.z count double-edges-done :delay (l.w double-tris (-> work double-tri-table))) + (add edge-table edge geo) + (s.w list-base (-> work double-edge-list)) + (s.w edge-table (-> work double-edge-table)) + (m edge edge-table) + (add.i no-neighbor r0 255)) + (asm-block scan-double-edges + (label double-edge-loop) + (add.i count count -1) + (l.bu faces-1 (-> edge tri-1)) + (nop!) + (l.bu faces-0 (-> edge tri-0)) + (b.eq faces-1 no-neighbor double-edge-boundary :delay (m flip r0)) + (sll faces-0 faces-0 2) + (sll faces-1 faces-1 2) + (add faces-0 faces-0 double-tris) + (add faces-1 faces-1 double-tris) + (nop!) + (l.bu faces-0 (-> tri-a faces)) + (nop!) + (l.bu faces-1 (-> tri-b faces)) + (b.eq faces-0 faces-1 double-edge-next :delay (nop!)) + ;; The two records get complementary flags, so the two sheets wind away from each other. + (sltiu faces-0 faces-0 1) + (nop!) + (sltu faces-1 r0 faces-1) + (nop!) + (nop!) + (s.h faces-0 list 2) + (sub faces-0 edge edge-table) + (s.h faces-1 list 6) + (nop!) + (s.h faces-0 list) + (nop!) + (s.h faces-0 list 4) + (b double-edge-next :delay (add.i list list 8))) + ;; One face only: keep the edge unconditionally, oriented by whether that face was reversed. + (asm-block boundary-edge + (label double-edge-boundary) + (sll faces-1 faces-0 2) + (nop!) + (add faces-1 faces-1 double-tris) + (nop!) + (nop!) + (l.bu faces-1 (-> tri-b faces)) + (nop!) + (nop!) + (nop!) + (sltiu faces-1 faces-1 1) + (sub faces-0 edge edge-table) + (s.h faces-1 list 2) + (s.h faces-0 list) + (add.i list list 4)) + (asm-block next-edge + (label double-edge-next) + (b.nz count double-edge-loop :delay (add.i edge edge (type-size shadow-edge)))) + (asm-block publish-double-edge-list + (label double-edges-done) + (sub edge-table list list-base) + (sra edge-table edge-table 2) + (s.w edge-table (-> work num-double-edges)) + (s.w list (-> work dcache-top)) + (m result r0) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-add-verts () + "Append the two vertex uploads -- the skinned mesh and its duals -- to the output packet." + (declare (asm-func none) (allow-saved-regs)) + ;; Both uploads are plain V4-32 unpacks, one landing at SHADOW-VU1-VERTS and one at + ;; SHADOW-VU1-DUAL-VERTS. The two areas are the same size and adjacent, which is what lets the + ;; silhouette-quad path reach a vertex and its dual from one index register. + ;; + ;; The first upload carries the template's FLUSHA, so VU1 is finished with the previous caster's + ;; vertices before this one's overwrite them. The second clears vif0, since by then the wait has + ;; already happened. + ;; + ;; Each copy moves four quadwords per iteration and finishes with four separately branched single + ;; quadwords instead of a loop: a shadow mesh is small enough that the remainder is often most of + ;; it, and a taken branch per quadword beats a loop's bookkeeping at these lengths. + (rlet ((geo :reg a0 :type shadow-header) + (vertex-count :reg a0) + (work :reg a1 :type shadow-dcache) + ;; a1 becomes the base of the second unpack once the workspace has been read. + (dual-packet :reg a1) + (packet :reg a2) + ;; The same cursor, while the DMA and VIF tag quadword is being written through it. + (tag :reg a2 :type dma-packet) + (dual-dest :reg a2) + (shadow-state :reg v1 :type shadow-data) + (vertex-bytes :reg v1) + (template :reg a3) + (dual-source :reg a3) + (result :reg v0)) + ;; t0 through t6 are the copy cursors and quadword shuttles. Their roles differ between the two + ;; copies, so they keep physical names and each block says what they hold. + (asm-block upload-skinned-vertices + ;; t0 is the VU destination and then the countdown, t1 the output cursor, t2 the source, and + ;; t3 through t6 the quadwords in flight. + (m! shadow-state *shadow-data*) + (m shadow-state shadow-state) + (l.h vertex-count (-> geo num-verts)) + (add.i t0 r0 SHADOW-VU1-VERTS) + (l.q template (-> shadow-state dma-unpack-template)) + (nop!) + (s.q template packet) + (s.h vertex-count packet) + (s.b vertex-count packet 14) + (s.h t0 packet 12) + (add.i packet packet 16) + (l.w t2 (-> work vtx-table)) + (sll vertex-bytes vertex-count 4) + (m t1 packet) + (m t0 vertex-count) + (add.i t3 t0 -4) + (m t1 t1) + (b.lt t3 r0 copy-vertex-tail :delay (m t2 t2)) + (label copy-four-vertices) + (nop!) + (l.q t6 t2) + (nop!) + (l.q t3 t2 16) + (add.i t0 t0 -4) + (l.q t4 t2 32) + (add.i t1 t1 64) + (l.q t5 t2 48) + (add.i t2 t2 64) + (s.q t6 t1 -64) + (add.i t6 t0 -4) + (s.q t3 t1 -48) + (nop!) + (s.q t4 t1 -32) + (b.ge t6 r0 copy-four-vertices :delay (s.q t5 t1 -16)) + (label copy-vertex-tail) + (b.z t0 vertices-copied :delay (l.q t3 t2)) + (add.i t2 t2 16) + (add.i t1 t1 16) + (add.i t0 t0 -1) + (s.q t3 t1 -16) + (b.z t0 vertices-copied :delay (l.q t3 t2)) + (add.i t2 t2 16) + (add.i t1 t1 16) + (add.i t0 t0 -1) + (s.q t3 t1 -16) + (b.z t0 vertices-copied :delay (l.q t3 t2)) + (add.i t2 t2 16) + (add.i t1 t1 16) + (add.i t0 t0 -1) + (s.q t3 t1 -16) + (b.z t0 vertices-copied :delay (l.q t3 t2)) + (add.i t2 t2 16) + (add.i t1 t1 16) + (add.i t0 t0 -1) + (s.q t3 t1 -16)) + (asm-block upload-dual-vertices + ;; Same shape, one register to the left: t0 through t3 are the quadwords, the cursors moved + ;; into a2 and a3. + (label vertices-copied) + (m t0 r0) + (add packet packet vertex-bytes) + (add.i t0 r0 SHADOW-VU1-DUAL-VERTS) + (s.q template packet) + (s.h vertex-count packet) + (s.b vertex-count packet 14) + (s.w r0 (-> tag vif0)) + (s.h t0 packet 12) + (l.w dual-source (-> work ptr-dual-verts)) + (add.i dual-packet packet 16) + (m dual-dest dual-packet) + (nop!) + (add.i t0 vertex-count -4) + (m dual-dest dual-dest) + (b.lt t0 r0 copy-dual-tail :delay (m dual-source dual-source)) + (label copy-four-duals) + (nop!) + (l.q t3 dual-source) + (nop!) + (l.q t0 dual-source 16) + (add.i vertex-count vertex-count -4) + (l.q t1 dual-source 32) + (add.i dual-dest dual-dest 64) + (l.q t2 dual-source 48) + (add.i dual-source dual-source 64) + (s.q t3 dual-dest -64) + (add.i t3 vertex-count -4) + (s.q t0 dual-dest -48) + (nop!) + (s.q t1 dual-dest -32) + (b.ge t3 r0 copy-four-duals :delay (s.q t2 dual-dest -16)) + (label copy-dual-tail) + (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) + (add.i dual-source dual-source 16) + (add.i dual-dest dual-dest 16) + (add.i vertex-count vertex-count -1) + (s.q t0 dual-dest -16) + (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) + (add.i dual-source dual-source 16) + (add.i dual-dest dual-dest 16) + (add.i vertex-count vertex-count -1) + (s.q t0 dual-dest -16) + (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) + (add.i dual-source dual-source 16) + (add.i dual-dest dual-dest 16) + (add.i vertex-count vertex-count -1) + (s.q t0 dual-dest -16) + (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) + (add.i dual-source dual-source 16) + (add.i dual-dest dual-dest 16) + (add.i vertex-count vertex-count -1) + (s.q t0 dual-dest -16)) + (asm-block return + (label duals-copied) + (m vertex-count r0) + (add result dual-packet vertex-bytes) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!))) + ) + + (defun shadow-add-facing-single-tris () + "Append the single-sided cap list and the MSCALF that draws it." + (declare (asm-func none) (allow-saved-regs)) + ;; The list VU1 reads is a count quadword followed by one four-byte record per primitive, unpacked + ;; as unsigned V4-8 so that each record arrives as four separate integer lanes. A cap record is the + ;; triangle's own word: three vertex indices and the faces byte the classifier wrote. + ;; + ;; The record area is rounded up to a whole quadword and the last quadword is zeroed before the + ;; records go in, so a partially filled tail cannot present VU1 with stale indices. + (rlet ((shadow-state :reg v1 :type shadow-data) + (count :reg v1) + (work :reg a1 :type shadow-dcache) + (record-count :reg a1) + (list :reg a0) + (packet :reg a2) + (tag :reg a2 :type dma-packet) + (state :reg a3 :type shadow-data) + (tri :reg a3 :type shadow-tri) + (records :reg t0) + (qwc :reg t0) + (unpack-imm :reg t0) + (dma-tag :reg t1) + (list-end :reg v1) + (result :reg v0)) + (asm-block size-the-upload + (m! shadow-state *shadow-data*) + (m state shadow-state) + (l.w count (-> work num-facing-single-tris)) + (l.w list (-> work single-tri-list)) + ;; One record for the count itself, then round up to a quadword. + (b.z count no-single-tris :delay (add.i record-count count 1)) + (add.i record-count record-count 3) + (sra qwc record-count 2) + (sll record-count qwc 2) + (add.i qwc qwc 1) + (l.d dma-tag (-> state dma-cnt)) + (add qwc dma-tag qwc) + (l.w state (-> state vif-unpack-v4-8)) + (s.d qwc packet) + (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-TRI-LIST)) + (s.w r0 (-> tag vif0)) + (s.w state (-> tag vif1)) + (s.b record-count packet 14) + (sll record-count record-count 2) + (s.h unpack-imm packet 12) + (add.i packet packet 16) + (add record-count packet record-count) + ;; Clear the tail quadword before anything real is written into it. + (s.q r0 record-count -16) + (s.w count packet) + (add.i packet packet 4)) + (asm-block copy-cap-records + (label copy-single-tri-loop) + (l.w tri list) + (add.i list list 4) + (l.w tri tri) + (add.i count count -1) + (s.w tri packet) + (add.i packet packet 4) + (b.gt count r0 copy-single-tri-loop :delay (nop!))) + (asm-block start-vu1 + (m list-end record-count) + (lui list VIF-MSCALF-WORD) + (ori list list SHADOW-VU1-ENTRY-SINGLE-TRIS) + (s.q r0 list-end) + (s.w list list-end 12) + (add.i packet list-end 16)) + (asm-block return + (label no-single-tris) + (m result packet) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-add-single-edges () + "Append the single-sided silhouette list and the MSCALF that draws its quads." + (declare (asm-func none) (allow-saved-regs)) + ;; A silhouette record is two vertex indices and an orientation flag. The two indices are lifted + ;; straight out of the source edge as one halfword -- shadow-edge keeps ind-0 and ind-1 adjacent + ;; for exactly this -- and the flag follows in the third byte. VU1 turns each record into the quad + ;; running from those two vertices to their two duals. + (rlet ((shadow-state :reg v1 :type shadow-data) + (count :reg v1) + (work :reg a1 :type shadow-dcache) + (edge-table :reg a1) + (list :reg a0) + (packet :reg a2) + (tag :reg a2 :type dma-packet) + (state :reg a3 :type shadow-data) + (record-bytes :reg a3) + (record :reg a3) + (record-count :reg t0) + (records :reg t0) + (qwc :reg t1) + (dma-tag :reg t2) + (unpack-imm :reg t1) + (indices :reg t1) + (flip :reg t0) + (list-end :reg v1) + (result :reg v0)) + (asm-block size-the-upload + (m! shadow-state *shadow-data*) + (m state shadow-state) + (l.w count (-> work num-single-edges)) + (l.w list (-> work single-edge-list)) + (l.w edge-table (-> work single-edge-table)) + (b.z count no-single-edges :delay (add.i record-count count 1)) + (add.i record-count record-count 3) + (sra qwc record-count 2) + (sll record-count qwc 2) + (add.i qwc qwc 1) + (l.d dma-tag (-> state dma-cnt)) + (add qwc dma-tag qwc) + (l.w state (-> state vif-unpack-v4-8)) + (s.d qwc packet) + (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-EDGE-LIST)) + (s.w r0 (-> tag vif0)) + (s.w state (-> tag vif1)) + (s.b record-count packet 14) + (sll record-bytes record-count 2) + (s.h unpack-imm packet 12) + (add.i records packet 16) + (add packet records record-bytes) + (s.q r0 packet -16) + (s.w count records) + (add.i record records 4)) + (asm-block copy-silhouette-records + (label copy-single-edge-loop) + (l.h indices list) + (l.h flip list 2) + (add.i list list 4) + (add indices indices edge-table) + ;; ind-0 and ind-1 travel together as one halfword. + (l.hu indices indices) + (s.h flip record 2) + (s.h indices record) + (add.i count count -1) + (add.i record record 4) + (b.gt count r0 copy-single-edge-loop :delay (nop!))) + (asm-block start-vu1 + (m list-end packet) + (lui list VIF-MSCALF-WORD) + (ori list list SHADOW-VU1-ENTRY-EDGES) + (s.q r0 list-end) + (s.w list list-end 12) + (add.i packet list-end 16)) + (asm-block return + (label no-single-edges) + (m result packet) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-add-double-tris () + "Append the double-sided cap list and the MSCALF that draws it." + (declare (asm-func none) (allow-saved-regs)) + ;; Every double-sided triangle is emitted, so the count comes straight from the header rather than + ;; from a collected list, and the records are read out of the classifier's table in order. The + ;; faces byte in each record tells VU1 which way to wind the cap. + (rlet ((shadow-state :reg v1 :type shadow-data) + (count :reg v1) + (geo :reg a0 :type shadow-header) + (work :reg a1 :type shadow-dcache) + (record-count :reg a1) + (tris :reg a0) + (packet :reg a2) + (tag :reg a2 :type dma-packet) + (state :reg a3 :type shadow-data) + (tri :reg a3) + (qwc :reg t0) + (unpack-imm :reg t0) + (dma-tag :reg t1) + (list-end :reg v1) + (result :reg v0)) + (asm-block size-the-upload + (m! shadow-state *shadow-data*) + (m state shadow-state) + (l.h count (-> geo num-double-tris)) + (l.w tris (-> work double-tri-table)) + (b.z count no-double-tris :delay (add.i record-count count 1)) + (add.i record-count record-count 3) + (sra qwc record-count 2) + (sll record-count qwc 2) + (add.i qwc qwc 1) + (l.d dma-tag (-> state dma-cnt)) + (add qwc dma-tag qwc) + (l.w state (-> state vif-unpack-v4-8)) + (s.d qwc packet) + (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-TRI-LIST)) + (s.w r0 (-> tag vif0)) + (s.w state (-> tag vif1)) + (s.b record-count packet 14) + (sll record-count record-count 2) + (s.h unpack-imm packet 12) + (add.i packet packet 16) + (add record-count packet record-count) + (s.q r0 record-count -16) + (s.w count packet) + (add.i packet packet 4)) + (asm-block copy-cap-records + (label copy-double-tri-loop) + (l.w tri tris) + (add.i tris tris 4) + (add.i count count -1) + (s.w tri packet) + (add.i packet packet 4) + (b.gt count r0 copy-double-tri-loop :delay (nop!))) + (asm-block start-vu1 + (m list-end record-count) + (lui tris VIF-MSCALF-WORD) + (ori tris tris SHADOW-VU1-ENTRY-DOUBLE-TRIS) + (s.q r0 list-end) + (s.w tris list-end 12) + (add.i packet list-end 16)) + (asm-block return + (label no-double-tris) + (m result packet) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!))) + ) + + (defun shadow-add-double-edges () + "Append the double-sided silhouette list and the MSCALF that draws its quads." + (declare (asm-func none) (allow-saved-regs)) + ;; Identical in form to the single-sided silhouette list, and it starts the same VU1 entry: by this + ;; point a silhouette record has forgotten which kind of mesh it came from. The only difference is + ;; the table it indexes and the fact that an interior edge contributed two records. + (rlet ((shadow-state :reg v1 :type shadow-data) + (count :reg v1) + (work :reg a1 :type shadow-dcache) + (edge-table :reg a1) + (list :reg a0) + (packet :reg a2) + (tag :reg a2 :type dma-packet) + (state :reg a3 :type shadow-data) + (record-bytes :reg a3) + (record :reg a3) + (record-count :reg t0) + (records :reg t0) + (qwc :reg t1) + (dma-tag :reg t2) + (unpack-imm :reg t1) + (indices :reg t1) + (flip :reg t0) + (list-end :reg v1) + (result :reg v0)) + (asm-block size-the-upload + (m! shadow-state *shadow-data*) + (m state shadow-state) + (l.w count (-> work num-double-edges)) + (l.w list (-> work double-edge-list)) + (l.w edge-table (-> work double-edge-table)) + (b.z count no-double-edges :delay (add.i record-count count 1)) + (add.i record-count record-count 3) + (sra qwc record-count 2) + (sll record-count qwc 2) + (add.i qwc qwc 1) + (l.d dma-tag (-> state dma-cnt)) + (add qwc dma-tag qwc) + (l.w state (-> state vif-unpack-v4-8)) + (s.d qwc packet) + (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-EDGE-LIST)) + (s.w r0 (-> tag vif0)) + (s.w state (-> tag vif1)) + (s.b record-count packet 14) + (sll record-bytes record-count 2) + (s.h unpack-imm packet 12) + (add.i records packet 16) + (add packet records record-bytes) + (s.q r0 packet -16) + (s.w count records) + (add.i record records 4)) + (asm-block copy-silhouette-records + (label copy-double-edge-loop) + (l.h indices list) + (l.h flip list 2) + (add.i list list 4) + (add indices indices edge-table) + (l.hu indices indices) + (s.h flip record 2) + (s.h indices record) + (add.i count count -1) + (add.i record record 4) + (b.gt count r0 copy-double-edge-loop :delay (nop!))) + (asm-block start-vu1 + (m list-end packet) + (lui list VIF-MSCALF-WORD) + (ori list list SHADOW-VU1-ENTRY-EDGES) + (s.q r0 list-end) + (s.w list list-end 12) + (add.i packet list-end 16)) + (asm-block return + (label no-double-edges) + (m result packet) + (jr ra :delay (add sp sp r0)) + (nop!) + (nop!) + (nop!))) + ) + + (defun shadow-execute () + "Draw every caster in one shadow-queue run, appending their VIF packets to the output pointer and + returning where it ended up." + (declare (asm-func none) (allow-saved-regs)) + ;; One pass over the run's chain. Each caster's settings, geometry, and bone matrices come into the + ;; scratchpad in a single toSPR source chain; the whole per-caster pipeline then runs out of the + ;; scratchpad and the shared workspace, and the four VIF packets go straight into the frame's DMA + ;; buffer. The chain's next link is read out of the tag quadword that toSPR itself deposited, so + ;; walking the list costs nothing extra. + ;; + ;; The cache is written back once up front, before the first transfer, because the packets that were + ;; just built are still sitting in the data cache and the DMA controller does not see it. + (rlet ((caster :reg s4 :type shadow-dma-packet) + ;; The same register addresses the copy that landed in the scratchpad. + (geo :reg s4 :type shadow-header) + (work :reg s3 :type shadow-dcache) + (settings :reg s2 :type shadow-settings) + (stall-count :reg s5) + (output :reg gp) + (culled? :reg s1) + (scratch-base :reg at) + ;; The function's own address, kept so the two float constants below it can be reached. + (code-base :reg fp) + (to-spr :reg v1 :type dma-bank-spr) + (to-spr-poll :reg a1 :type dma-bank-spr) + (camera :reg v1 :type math-camera) + (cam-pos :reg v1 :type vector) + ;; The scratchpad destination, which then doubles as the value written to chcr. + (spr-dest :reg a0) + (result :reg v0)) + (asm-block enter-shadow-pass + (add.i sp sp -112) + (s.d ra sp) + (s.d fp sp 8) + (m code-base t9) + (s.q s1 sp 16) + (s.q s2 sp 32) + (s.q s3 sp 48) + (s.q s4 sp 64) + (s.q s5 sp 80) + (s.q gp sp 96) + (m output a1) + (m caster a0) + (add.i v1 r0 0) + (lui v1 #x7000) + (add.i stall-count v1 (shadow-spr-offset shadow-data waits)) + (m! t9 flush-cache) + (add.i a0 r0 0) + (jalr ra t9 :delay (sll v0 ra 0)) + (b test-next-caster :delay (nop!))) + ;; Pull one caster into the scratchpad. The first wait keeps the channel from being reprogrammed + ;; while it is still running and is not counted; the second and third are. + (asm-block upload-caster + (label next-caster) + (lui v1 #x1000) + (ori v1 v1 #xd400) + (add.i spr-dest r0 SHADOW-SPR-INPUT) + (m to-spr-poll to-spr) + (label wait-for-idle) + (l.w a2 (-> to-spr-poll chcr)) + (nop! :count 3) + (and.i a2 a2 DMA-CHCR-STR) + (nop!) + (b.z a2 to-spr-idle :delay (nop!)) + (nop! :count 10) + (b wait-for-idle :delay (nop!)) + (label to-spr-idle) + (m a1 r0) + (m to-spr-poll to-spr) + (m a2 stall-count) + (shadow-wait-counting-stalls! a3 t0 to-spr-poll a2 count-idle-stall input-idle) + (m a1 r0) + (s.w spr-dest (-> to-spr sadr)) + (s.w caster (-> to-spr tadr)) + (s.w r0 (-> to-spr qwc)) + (sync.l) + (add.i spr-dest r0 DMA-CHCR-START-SPR-CHAIN) + (s.w spr-dest (-> to-spr chcr)) + (sync.l) + (m spr-dest stall-count) + (shadow-wait-counting-stalls! a1 a2 to-spr spr-dest count-upload-stall input-ready) + (m v1 r0)) + ;; The settings and the geometry are now at fixed scratchpad addresses. + (asm-block bind-caster + (lui at #x7000) + (add.i v1 r0 SHADOW-SPR-GEO) + (add geo v1 scratch-base) + (m! work *gsf-buffer*) + (add.i settings scratch-base SHADOW-SPR-SETTINGS) + (nop!) + (l.w v1 (-> settings flags)) + (and.i v1 v1 (shadow-flags disable-draw)) + (b.nz v1 advance-chain :delay (m v1 s7))) + ;; locus = center + shadow-dir * dist-to-locus. Everything projects away from this point. + (asm-block place-light-locus + (l.s f0 (-> settings center x)) + (l.s f1 (-> settings shadow-dir x)) + (l.s f2 (-> settings dist-to-locus)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (s.s f0 (-> work center x)) + (l.s f0 (-> settings center y)) + (l.s f1 (-> settings shadow-dir y)) + (l.s f2 (-> settings dist-to-locus)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (s.s f0 (-> work center y)) + (l.s f0 (-> settings center z)) + (l.s f1 (-> settings shadow-dir z)) + (l.s f2 (-> settings dist-to-locus)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (s.s f0 (-> work center z)) + (l.s f0 code-base shadow-one) + (s.s f0 (-> work center w))) + ;; A caster that authored its planes in world space gets them verbatim; otherwise they are + ;; written relative to the shadow joint and have to be lowered by its height here. + (asm-block place-clipping-planes + (l.w v1 (-> settings flags)) + (and.i v1 v1 (shadow-flags world-space-planes)) + (b.z v1 offset-planes-by-center :delay (nop!)) + (l.q v1 (-> settings bot-plane)) + (s.q v1 (-> work plane)) + (l.q v1 (-> settings top-plane)) + (s.q v1 (-> work top-plane)) + (b planes-ready :delay (nop!)) + (label offset-planes-by-center) + (l.s f0 (-> settings bot-plane x)) + (s.s f0 (-> work plane x)) + (l.s f0 (-> settings bot-plane y)) + (s.s f0 (-> work plane y)) + (l.s f0 (-> settings bot-plane z)) + (s.s f0 (-> work plane z)) + (l.s f0 (-> settings bot-plane w)) + (l.s f1 (-> settings center y)) + (sub.s f0 f0 f1) + (s.s f0 (-> work plane w)) + (l.s f0 (-> settings top-plane x)) + (s.s f0 (-> work top-plane x)) + (l.s f0 (-> settings top-plane y)) + (s.s f0 (-> work top-plane y)) + (l.s f0 (-> settings top-plane z)) + (s.s f0 (-> work top-plane z)) + (l.s f0 (-> settings top-plane w)) + (l.s f1 (-> settings center y)) + (sub.s f0 f0 f1) + (s.s f0 (-> work top-plane w)) + (m v1 f0)) + ;; With camera-cull set, a camera below the ground plane means the volume would be seen from + ;; inside and the counting would come out inverted, so the whole caster is dropped. + (asm-block cull-against-ground-plane + (label planes-ready) + (m culled? s7) + (l.w v1 (-> settings flags)) + (and.i v1 v1 (shadow-flags camera-cull)) + (b.z v1 culling-done :delay (m v1 s7)) + (m! t9 camera-pos) + (jalr ra t9 :delay (sll v0 ra 0)) + (m v1 v0) + (l.s f0 (-> cam-pos x)) + (l.s f1 (-> work plane x)) + (mul.s f0 f0 f1) + (l.s f1 (-> cam-pos y)) + (l.s f2 (-> work plane y)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (l.s f1 (-> cam-pos z)) + (l.s f2 (-> work plane z)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (l.s f1 (-> work plane w)) + (add.s f0 f0 f1) + (m f1 r0) + (c.lt.s f0 f1) + (b.fpf culling-done :delay (m v1 s7)) + (add.i culled? s7 #t) + (m v1 culled?) + (label culling-done) + (b.ne s7 culled? advance-chain :delay (m v1 s7))) + ;; If the light itself has ended up at or below the ground plane the projection would run the + ;; wrong way, so the plane is moved to pass exactly through the locus. The volume collapses to + ;; nothing rather than turning inside out. + (asm-block keep-locus-above-plane + (l.s f0 (-> work center x)) + (l.s f1 (-> work plane x)) + (mul.s f0 f0 f1) + (l.s f1 (-> work center y)) + (l.s f2 (-> work plane y)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (l.s f1 (-> work center z)) + (l.s f2 (-> work plane z)) + (mul.s f1 f1 f2) + (add.s f0 f0 f1) + (m f1 r0) + (l.s f2 (-> work plane w)) + (add.s f2 f0 f2) + (c.lt.s f1 f2) + (b.fpf locus-checked :delay (m v1 s7)) + (neg.s f0 f0) + (s.s f0 (-> work plane w)) + (m v1 f0)) + ;; near-plane is (0, 0, 1, -2d): the camera-space plane at twice the near-plane distance. Nothing + ;; here needs a rotation, so it is built directly rather than transformed with the others. + (asm-block set-up-workspace + (label locus-checked) + (l.q v1 (-> settings shadow-dir)) + (s.q v1 (-> work light-dir)) + (m f0 r0) + (s.s f0 (-> work near-plane x)) + (m f0 r0) + (s.s f0 (-> work near-plane y)) + (l.s f0 code-base shadow-one) + (s.s f0 (-> work near-plane z)) + (l.s f0 code-base shadow-negative-two) + (m! v1 *math-camera*) + (l.s f1 (-> camera d)) + (mul.s f0 f0 f1) + (s.s f0 (-> work near-plane w)) + (add.i v1 work (offset-of shadow-dcache data)) + (s.w v1 (-> work dcache-top))) + ;; Skin, move the projection into camera space, and build the duals. + (asm-block build-shadow-volume + (m! t9 shadow-xform-verts) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 shadow-init-vars) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 shadow-calc-dual-verts) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (l.w v1 (-> settings flags)) + (and.i v1 v1 (shadow-flags scissor-top)) + (b.z v1 volume-clipped :delay (m v1 s7)) + (m! t9 shadow-scissor-top) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m v1 v0) + (label volume-clipped) + (m! t9 shadow-scissor-edges) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0))) + ;; Classify both kinds of face and collect both silhouettes. + (asm-block find-silhouettes + (m! t9 shadow-find-facing-single-tris) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 shadow-find-single-edges) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 shadow-find-facing-double-tris) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m! t9 shadow-find-double-edges) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0))) + ;; The vertices have to be uploaded before any list that indexes them, and each builder returns + ;; the advanced output pointer for the next. + (asm-block append-vif-packets + (m! t9 shadow-add-verts) + (m a0 geo) + (m a1 work) + (m a2 output) + (jalr ra t9 :delay (sll v0 ra 0)) + (m a2 v0) + (m! t9 shadow-add-facing-single-tris) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m a2 v0) + (m! t9 shadow-add-single-edges) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m a2 v0) + (m! t9 shadow-add-double-tris) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m a2 v0) + (m! t9 shadow-add-double-edges) + (m a0 geo) + (m a1 work) + (jalr ra t9 :delay (sll v0 ra 0)) + (m output v0) + (m v1 output)) + ;; toSPR deposited the packet's own tag quadword ahead of the settings, so the next caster's + ;; address is already in the scratchpad. A culled caster lands here too and the chain goes on. + (asm-block advance-chain + (label advance-chain) + (l.w caster scratch-base (+ SHADOW-SPR-INPUT (offset-of generic-merc-tag next-ptr))) + (label test-next-caster) + (b.nz caster next-caster :delay (nop!)) + (m v1 s7)) + (asm-block return + (m result output) + (l.d ra sp) + (l.d fp sp 8) + (l.q gp sp 96) + (l.q s5 sp 80) + (l.q s4 sp 64) + (l.q s3 sp 48) + (l.q s2 sp 32) + (l.q s1 sp 16) + (jr ra :delay (add.i sp sp 112)) + (nop!) + (nop!))) + ) + + ) \ No newline at end of file diff --git a/goal_src/jak1/engine/gfx/shadow/shadow-cpu.gc b/goal_src/jak1/engine/gfx/shadow/shadow-cpu.gc index e6edd86957..557ae9ebcf 100644 --- a/goal_src/jak1/engine/gfx/shadow/shadow-cpu.gc +++ b/goal_src/jak1/engine/gfx/shadow/shadow-cpu.gc @@ -11,6 +11,10 @@ (require "engine/gfx/background/subdivide-h.gc") (require "engine/collide/pat-h.gc") +;; NOTE: this file is a bit of a mess now. +;; I'm planning to remove most of it and replace with PC shadow +;; renderer very soon. + ;; ------------------------------------------------------------------------------------------------ ;; What the shadow renderer draws ;; ------------------------------------------------------------------------------------------------ @@ -196,15 +200,8 @@ (new 'static 'vif-tag :cmd (vif-cmd unpack-v4-8)))) (defun shadow-dma-init ((dma-buf dma-buffer)) - "Open the shadow pass: set the GS up to count volume crossings in the frame buffer's alpha plane and - clear the whole window to SHADOW-ALPHA-CLEAR with sixteen full-height sprites." - ;; FBMSK protects RGB and ZMSK protects depth for the whole pass, so nothing the volume draws is - ;; visible; only alpha moves. TEX0 is pointed at the frame buffer being drawn so the volume can read - ;; its own alpha back, TFX is left at MODULATE, and CLAMP keeps the region sampling inside the window. - ;; - ;; XYOFFSET is also rewritten here without the interlace half-line that the rest of the frame uses. - ;; The stencil only works if the pixel a polygon writes is the pixel it samples, and half a scanline - ;; of Y offset would sample the wrong one. shadow-dma-end puts the half-line back. + "Append GS state and full-screen strips which initialize framebuffer alpha for the shadow-volume + pass while masking color and depth writes." (let* ((v1-0 *display*) (framebuffer-page (-> v1-0 frames (-> v1-0 on-screen) draw frame1 fbp))) (* framebuffer-page 32) @@ -217,17 +214,8 @@ (strip-count (/ texture-width 32)) (y-offset (* screen-min-y 16)) (chain-start (-> dma-buf base))) - (let* ((t4-0 dma-buf) - (t6-0 (the-as object (-> t4-0 base)))) - (set! (-> (the-as dma-packet t6-0) dma) (new 'static 'dma-tag :id (dma-tag-id cnt))) - (set! (-> (the-as dma-packet t6-0) vif0) (new 'static 'vif-tag)) - (set! (-> (the-as dma-packet t6-0) vif1) (new 'static 'vif-tag :cmd (vif-cmd direct) :msk #x1)) - (set! (-> t4-0 base) (&+ (the-as pointer t6-0) 16))) - (let* ((t4-1 dma-buf) - (t6-2 (the-as object (-> t4-1 base)))) - (set! (-> (the-as gs-gif-tag t6-2) tag) (new 'static 'gif-tag64 :nloop #xa :eop #x1 :nreg #x1)) - (set! (-> (the-as gs-gif-tag t6-2) regs) (new 'static 'gif-tag-regs :regs0 (gif-reg-id a+d))) - (set! (-> t4-1 base) (&+ (the-as pointer t6-2) 16))) + (dma-buffer-add-cnt-vif2 dma-buf 0 (new 'static 'vif-tag) (new 'static 'vif-tag :cmd (vif-cmd direct) :msk #x1)) + (dma-buffer-add-gif-tag dma-buf (new 'static 'gif-tag64 :nloop #xa :eop #x1 :nreg #x1) (gs-reg-list a+d)) (let ((t4-2 (-> dma-buf base))) (set! (-> (the-as (pointer gs-reg) t4-2) 8) (gs-reg texflush)) (set! (-> (the-as (pointer gs-reg) t4-2) 24) (gs-reg test-1)) @@ -242,7 +230,8 @@ (set! (-> (the-as (pointer gs-reg) t4-2) 88) (gs-reg xyoffset-1)) (set! (-> (the-as (pointer gs-xy-offset) t4-2) 10) (new 'static 'gs-xy-offset :ofx (* x-offset 16) :ofy y-offset)) (set! (-> (the-as (pointer gs-reg) t4-2) 104) (gs-reg tex0-1)) - (set! (-> (the-as (pointer gs-tex0) t4-2) 12) (new 'static 'gs-tex0 :tbw #x8 :tw #x9 :th #x8 :tcc #x1 :tbp0 (* framebuffer-page 32))) + (set! (-> (the-as (pointer gs-tex0) t4-2) 12) + (new 'static 'gs-tex0 :tbw #x8 :tw #x9 :th #x8 :tcc #x1 :tbp0 (* framebuffer-page 32))) (set! (-> (the-as (pointer gs-reg) t4-2) 120) (gs-reg tex1-1)) (set! (-> (the-as (pointer gs-tex1) t4-2) 14) (new 'static 'gs-tex1)) (set! (-> (the-as (pointer gs-reg) t4-2) 136) (gs-reg miptbp1-1)) @@ -258,35 +247,28 @@ :maxu (+ texture-width -1)))) (&+! (-> dma-buf base) 160) - (let* ((t2-7 dma-buf) - (t3-5 (the-as object (-> t2-7 base)))) - (set! (-> (the-as gs-gif-tag t3-5) tag) (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :flg (gif-flag reg-list) :nreg #x2)) - (set! (-> (the-as gs-gif-tag t3-5) regs) (new 'static 'gif-tag-regs :regs1 (gif-reg-id rgbaq))) - (set! (-> t2-7 base) (&+ (the-as pointer t3-5) 16))) + (dma-buffer-add-gif-tag dma-buf + (new 'static 'gif-tag64 :nloop #x1 :eop #x1 :flg (gif-flag reg-list) :nreg #x2) + (new 'static 'gif-tag-regs :regs1 (gif-reg-id rgbaq))) (let* ((t2-8 dma-buf) (t3-7 (-> t2-8 base))) (set! (-> (the-as (pointer gs-prim) t3-7) 0) (new 'static 'gs-prim :prim (gs-prim-type sprite))) (set! (-> (the-as (pointer gs-rgbaq) t3-7) 1) (new 'static 'gs-rgbaq :a #x60)) (set! (-> t2-8 base) (&+ t3-7 16))) - (let* ((t2-9 dma-buf) - (t3-9 (the-as object (-> t2-9 base)))) - (set! (-> (the-as gs-gif-tag t3-9) tag) (new 'static 'gif-tag64 :eop #x1 :flg (gif-flag reg-list) :nreg #x2 :nloop strip-count)) - (set! (-> (the-as gs-gif-tag t3-9) regs) (new 'static 'gif-tag-regs :regs0 (gif-reg-id xyz2) :regs1 (gif-reg-id xyz2))) - (set! (-> t2-9 base) (&+ (the-as pointer t3-9) 16))) + (dma-buffer-add-gif-tag dma-buf + (new 'static 'gif-tag64 :eop #x1 :flg (gif-flag reg-list) :nreg #x2 :nloop strip-count) + (gs-reg-list xyz2 xyz2)) (let ((x 0)) (dotimes (i strip-count) (let* ((t4-10 dma-buf) (t5-12 (-> t4-10 base))) - (set! (-> (the-as (pointer gs-xyzf) t5-12) 0) (new 'static 'gs-xyzf :z #x1ffff :y (* screen-min-y 16) :x (* (+ x-offset x) 16))) + (set! (-> (the-as (pointer gs-xyzf) t5-12) 0) + (new 'static 'gs-xyzf :z #x1ffff :y (* screen-min-y 16) :x (* (+ x-offset x) 16))) (set! (-> (the-as (pointer gs-xyzf) t5-12) 1) (new 'static 'gs-xyzf :z #x1ffff :y (* screen-max-y 16) :x (* (+ x 32 x-offset) 16))) (set! (-> t4-10 base) (&+ t5-12 16))) (+! x 32))) - (let* ((a2-3 dma-buf) - (a3-1 (the-as object (-> a2-3 base)))) - (set! (-> (the-as gs-gif-tag a3-1) tag) (new 'static 'gif-tag64 :nloop #x4 :eop #x1 :nreg #x1)) - (set! (-> (the-as gs-gif-tag a3-1) regs) (new 'static 'gif-tag-regs :regs0 (gif-reg-id a+d))) - (set! (-> a2-3 base) (&+ (the-as pointer a3-1) 16))) + (dma-buffer-add-gif-tag dma-buf (new 'static 'gif-tag64 :nloop #x4 :eop #x1 :nreg #x1) (gs-reg-list a+d)) (cond (*shadow-debug* (let* ((a2-5 dma-buf) @@ -319,7 +301,8 @@ ((nonzero? qwc) (logior! (-> (the-as (pointer uint64) chain-start) 0) (shr (shl qwc 48) 48)) (logior! (-> (the-as (pointer uint64) chain-start) 1) (shl (shr (shl qwc 48) 48) 32))) - (else (set! (-> dma-buf base) chain-start))))))) + (else + (set! (-> dma-buf base) chain-start))))))) (none)) (defun shadow-dma-end ((dma-buf dma-buffer)) @@ -540,154 +523,6 @@ ;; Shadow classification and edge construction are performed by the native implementation. (define shadow-vu0-block (new 'static 'vu-function :length #x0 :qlength #x0))) -(#unless PC_PORT - ;; VU0 does the two pieces of per-vertex arithmetic that the EE would spend its whole frame on: - ;; deciding which way a triangle faces the light, and finding where a vertex projects onto the - ;; ground plane. Both are batched four at a time so that the four sequential divides and the four - ;; cross products fill the pipeline instead of stalling on each other. - ;; - ;; SHADOW-VU0-CLASSIFY-TRIS takes four triangles in vf02-vf04, vf07-vf09, vf12-vf14, vf17-vf19 and - ;; leaves the sign of each cross product dotted with the light direction in the y lane of vf22-vf25. - ;; The y lane is deliberate: the EE reads the result back with qmfc2 and tests bit 63 of the - ;; general-purpose register, which is the sign of lane y. - ;; - ;; SHADOW-VU0-PROJECT-VERTS takes four vertices in vf03-vf06 and returns the four projected duals in - ;; vf23-vf26 -- but one call late. Each call copies its inputs into vf07-vf10, starts the four - ;; divides for this group, and finishes the *previous* group, whose vertices are still sitting in - ;; vf07-vf10 from last time. The EE loop is written to match: it hands over a group, then reads the - ;; results of the group before it. SHADOW-VU0-PROJECT-DRAIN is the same code with the arithmetic that - ;; sets up a new group deleted, and squeezes the final group out. - (defvu0 shadow-vu0-block - ;; The four triangles, their two edge vectors, and the facing results. Each triangle's second edge - ;; register is reused for the cross product and then for the running dot product. - (rlet ((light-dir :reg vf01) - (tri-a-0 :reg vf02) (tri-a-1 :reg vf03) (tri-a-2 :reg vf04) - (edge-a-0 :reg vf05) (edge-a-1 :reg vf06) - (tri-b-0 :reg vf07) (tri-b-1 :reg vf08) (tri-b-2 :reg vf09) - (edge-b-0 :reg vf10) (edge-b-1 :reg vf11) - (tri-c-0 :reg vf12) (tri-c-1 :reg vf13) (tri-c-2 :reg vf14) - (edge-c-0 :reg vf15) (edge-c-1 :reg vf16) - (tri-d-0 :reg vf17) (tri-d-1 :reg vf18) (tri-d-2 :reg vf19) - (edge-d-0 :reg vf20) (edge-d-1 :reg vf21) - (facing-a :reg vf22) (facing-b :reg vf23) (facing-c :reg vf24) (facing-d :reg vf25)) - (label classify-tris) - (vu-pair (nop) (sub.xyzw edge-a-0 tri-a-1 tri-a-0)) ;; #x000 - (vu-pair (nop) (sub.xyzw edge-a-1 tri-a-2 tri-a-0)) ;; #x001 - (vu-pair (nop) (sub.xyzw edge-b-0 tri-b-1 tri-b-0)) ;; #x002 - (vu-pair (nop) (sub.xyzw edge-b-1 tri-b-2 tri-b-0)) ;; #x003 - (vu-pair (nop) (sub.xyzw edge-c-0 tri-c-1 tri-c-0)) ;; #x004 - (vu-pair (nop) (sub.xyzw edge-c-1 tri-c-2 tri-c-0)) ;; #x005 - (vu-pair (nop) (sub.xyzw edge-d-0 tri-d-1 tri-d-0)) ;; #x006 - (vu-pair (nop) (sub.xyzw edge-d-1 tri-d-2 tri-d-0)) ;; #x007 - ;; Four face normals, back to back, so no cross product waits on the one before it. - (vu-pair (nop) (opmula.xyz ACC edge-a-0 edge-a-1)) ;; #x008 - (vu-pair (nop) (opmsub.xyz edge-a-0 edge-a-1 edge-a-0)) ;; #x009 - (vu-pair (nop) (opmula.xyz ACC edge-b-0 edge-b-1)) ;; #x00a - (vu-pair (nop) (opmsub.xyz edge-b-0 edge-b-1 edge-b-0)) ;; #x00b - (vu-pair (nop) (opmula.xyz ACC edge-c-0 edge-c-1)) ;; #x00c - (vu-pair (nop) (opmsub.xyz edge-c-0 edge-c-1 edge-c-0)) ;; #x00d - (vu-pair (nop) (opmula.xyz ACC edge-d-0 edge-d-1)) ;; #x00e - (vu-pair (nop) (opmsub.xyz edge-d-0 edge-d-1 edge-d-0)) ;; #x00f - (vu-pair (nop) (mul.xyz edge-a-0 edge-a-0 light-dir)) ;; #x010 - (vu-pair (nop) (mul.xyz edge-b-0 edge-b-0 light-dir)) ;; #x011 - (vu-pair (nop) (mul.xyz edge-c-0 edge-c-0 light-dir)) ;; #x012 - (vu-pair (nop) (mul.xyz edge-d-0 edge-d-0 light-dir)) ;; #x013 - ;; Fold x+y and then +z into the y lane. Landing the dot product in y rather than x is what lets - ;; the EE test it with an ordinary bltz on the register it reads back. - (vu-pair (nop) (addx.y edge-a-0 edge-a-0 edge-a-0)) ;; #x014 - (vu-pair (nop) (addx.y edge-b-0 edge-b-0 edge-b-0)) ;; #x015 - (vu-pair (nop) (addx.y edge-c-0 edge-c-0 edge-c-0)) ;; #x016 - (vu-pair (nop) (addx.y edge-d-0 edge-d-0 edge-d-0)) ;; #x017 - (vu-pair (nop) (addz.y facing-a edge-a-0 edge-a-0)) ;; #x018 - (vu-pair (nop) (addz.y facing-b edge-b-0 edge-b-0)) ;; #x019 - ;; The E bit rides the second-to-last pair, so the fourth result still gets written. - (vu-pair (nop) (addz.y facing-c edge-c-0 edge-c-0 :e)) ;; #x01a - (vu-pair (nop) (addz.y facing-d edge-d-0 edge-d-0))) ;; #x01b - ;; Projection. A vertex's dual is where the ray from the locus through it crosses the ground plane: - ;; - ;; dual = v - (locus - v) * dist(v) / dot(locus - v, n) - ;; - ;; dist and the denominator are folded into lane x of their own register so that the four divides - ;; can be issued one after another; held-0..held-3 keep the previous group's vertices alive while - ;; those divides finish. - (rlet ((locus :reg vf01) - (ground-plane :reg vf02) - (vertex-0 :reg vf03) (vertex-1 :reg vf04) (vertex-2 :reg vf05) (vertex-3 :reg vf06) - (held-0 :reg vf07) (held-1 :reg vf08) (held-2 :reg vf09) (held-3 :reg vf10) - (dist-0 :reg vf11) (dist-1 :reg vf12) (dist-2 :reg vf13) (dist-3 :reg vf14) - (denom-0 :reg vf15) (denom-1 :reg vf16) (denom-2 :reg vf17) (denom-3 :reg vf18) - (ray-0 :reg vf19) (ray-1 :reg vf20) (ray-2 :reg vf21) (ray-3 :reg vf22) - (dual-0 :reg vf23) (dual-1 :reg vf24) (dual-2 :reg vf25) (dual-3 :reg vf26) - ;; ray-1..ray-3 are still needed after their scaling, so the scaled copies get their own - ;; registers; ray-0 is scaled in place because nothing reads it again. - (scaled-ray-1 :reg vf27) (scaled-ray-2 :reg vf28) (scaled-ray-3 :reg vf29)) - (label project-verts) - (vu-pair (nop) (mul.xyzw scaled-ray-1 ray-1 Q)) ;; #x01c - (vu-pair (div Q dist-2.x denom-2.x) (sub.xyzw ray-0 locus vertex-0)) ;; #x01d - (vu-pair (move.xyzw dual-0 held-0) (sub.xyzw ray-1 locus vertex-1)) ;; #x01e - (vu-pair (nop) (sub.xyzw ray-2 locus vertex-2)) ;; #x01f - (vu-pair (move.xyzw dual-2 held-2) (sub.xyzw ray-3 locus vertex-3)) ;; #x020 - (vu-pair (move.xyzw dual-3 held-3) (sub.xyzw dual-1 held-1 scaled-ray-1)) ;; #x021 - (vu-pair (nop) (mul.xyzw dist-0 vertex-0 ground-plane)) ;; #x022 - (vu-pair (nop) (mul.xyz denom-0 ray-0 ground-plane)) ;; #x023 - (vu-pair (div Q dist-3.x denom-3.x) (mul.xyzw dist-1 vertex-1 ground-plane)) ;; #x024 - (vu-pair (move.xyzw held-0 vertex-0) (mul.xyzw scaled-ray-2 scaled-ray-2 Q)) ;; #x025 - (vu-pair (move.xyzw held-1 vertex-1) (mul.xyz denom-1 ray-1 ground-plane)) ;; #x026 - (vu-pair (move.xyzw held-2 vertex-2) (addy.x dist-0 dist-0 dist-0)) ;; #x027 - (vu-pair (move.xyzw held-3 vertex-3) (addy.x denom-0 denom-0 denom-0)) ;; #x028 - (vu-pair (nop) (sub.xyzw dual-2 dual-2 scaled-ray-2)) ;; #x029 - (vu-pair (nop) (addy.x dist-1 dist-1 dist-1)) ;; #x02a - (vu-pair (nop) (mul.xyzw scaled-ray-3 scaled-ray-3 Q)) ;; #x02b - (vu-pair (nop) (addy.x denom-1 denom-1 denom-1)) ;; #x02c - (vu-pair (nop) (addz.x dist-0 dist-0 dist-0)) ;; #x02d - (vu-pair (nop) (addz.x denom-0 denom-0 denom-0)) ;; #x02e - (vu-pair (nop) (sub.xyzw dual-3 dual-3 scaled-ray-3)) ;; #x02f - (vu-pair (nop) (addz.x dist-1 dist-1 dist-1)) ;; #x030 - (vu-pair (nop) (addz.x denom-1 denom-1 denom-1)) ;; #x031 - ;; The plane's d rides in w, and a vertex's w is one, so folding w in finishes the distance. - (vu-pair (nop) (addw.x dist-0 dist-0 dist-0)) ;; #x032 - (vu-pair (nop) (mul.xyzw dist-2 held-2 ground-plane)) ;; #x033 - (vu-pair (nop) (addw.x dist-1 dist-1 dist-1)) ;; #x034 - (vu-pair (nop) (mul.xyz denom-2 ray-2 ground-plane)) ;; #x035 - (vu-pair (nop) (mul.xyzw dist-3 held-3 ground-plane)) ;; #x036 - (vu-pair (div Q dist-0.x denom-0.x) (mul.xyz denom-3 ray-3 ground-plane)) ;; #x037 - (vu-pair (nop) (addy.x dist-2 dist-2 dist-2)) ;; #x038 - (vu-pair (nop) (addy.x denom-2 denom-2 denom-2)) ;; #x039 - (vu-pair (nop) (addy.x dist-3 dist-3 dist-3)) ;; #x03a - (vu-pair (nop) (addy.x denom-3 denom-3 denom-3)) ;; #x03b - (vu-pair (nop) (addz.x dist-2 dist-2 dist-2)) ;; #x03c - (vu-pair (nop) (addz.x denom-2 denom-2 denom-2)) ;; #x03d - (vu-pair (div Q dist-1.x denom-1.x) (addz.x dist-3 dist-3 dist-3)) ;; #x03e - (vu-pair (nop) (mul.xyzw ray-0 ray-0 Q)) ;; #x03f - (vu-pair (move.xyzw scaled-ray-2 ray-2) (addz.x denom-3 denom-3 denom-3)) ;; #x040 - (vu-pair (move.xyzw scaled-ray-3 ray-3) (addw.x dist-2 dist-2 dist-2)) ;; #x041 - (vu-pair (nop) (addw.x dist-3 dist-3 dist-3 :e)) ;; #x042 - (vu-pair (nop) (sub.xyzw held-0 held-0 ray-0)) ;; #x043 - ;; The drain. Every instruction that would start a new group is gone; what is left finishes the - ;; group still in flight and writes all four duals. - (label project-verts-drain) - (vu-pair (nop) (mul.xyzw scaled-ray-1 ray-1 Q)) ;; #x044 - (vu-pair (div Q dist-2.x denom-2.x) (nop)) ;; #x045 - (vu-pair (move.xyzw dual-0 held-0) (nop)) ;; #x046 - (vu-pair (nop) (nop)) ;; #x047 - (vu-pair (move.xyzw dual-2 held-2) (nop)) ;; #x048 - (vu-pair (move.xyzw dual-3 held-3) (sub.xyzw dual-1 held-1 scaled-ray-1)) ;; #x049 - (vu-pair (nop) (nop)) ;; #x04a - (vu-pair (nop) (nop)) ;; #x04b - (vu-pair (div Q dist-3.x denom-3.x) (nop)) ;; #x04c - (vu-pair (nop) (mul.xyzw scaled-ray-2 scaled-ray-2 Q)) ;; #x04d - (vu-pair (nop) (nop)) ;; #x04e - (vu-pair (nop) (nop)) ;; #x04f - (vu-pair (nop) (nop)) ;; #x050 - (vu-pair (nop) (sub.xyzw dual-2 dual-2 scaled-ray-2)) ;; #x051 - (vu-pair (nop) (nop)) ;; #x052 - (vu-pair (nop) (mul.xyzw scaled-ray-3 scaled-ray-3 Q)) ;; #x053 - (vu-pair (nop) (nop)) ;; #x054 - (vu-pair (nop) (nop)) ;; #x055 - (vu-pair (nop) (nop :e)) ;; #x056 - (vu-pair (nop) (sub.xyzw dual-3 dual-3 scaled-ray-3)))) ;; #x057 - ) - (#when PC_PORT (def-mips2c shadow-xform-verts function) (def-mips2c shadow-execute (function shadow-dma-packet pointer pointer)) @@ -734,2161 +569,6 @@ "Append the double-sided silhouette list." (none))) -(#unless PC_PORT - ;; The workspace is a bump arena. dcache-top starts at data and each stage appends its own list to - ;; it: the dual vertices first, then the facing single-sided triangles, the single-sided silhouette - ;; edges, and the double-sided silhouette edges. Nothing is ever freed, and nothing is reordered, - ;; because every stage's table pointer is recorded in the header for the packet builders that follow. - ;; - ;; The mesh itself stays in the scratchpad where toSPR left it: skinning writes over the source - ;; vertices and triangle classification writes each face's answer into the spare byte of its own - ;; shadow-tri record, so the arena only holds what the geometry has no room for. - (asm-data - (label shadow-negative-two) - (word #xc0000000) - (label shadow-one) - (word #x3f800000)) - - (defun shadow-xform-verts () - "Skin the caster's shadow mesh in place in the scratchpad. A vertex names either one joint, or two - joints and a blend weight in its w lane, and never more." - (declare (asm-func none) (allow-saved-regs)) - ;; The mesh arrives packed: a header, the vertices, then a stream of shadow-matrix-ref pairs, one - ;; per vertex, with every single-joint vertex ahead of every two-joint vertex so the two cases can - ;; be separate loops. The joint transforms followed the geometry into the scratchpad, one pris-mtx - ;; each; only the t-mtx half is wanted here. - (rlet ((geo :reg a0 :type shadow-header) - ;; a0 gives up the header as soon as the two-joint count has been read out of it. - (two-count :reg a0) - (work :reg a1 :type shadow-dcache) - ;; a1 then walks the reference stream. - (joint-ref :reg a1 :type shadow-matrix-ref) - (vertex :reg a2 :type shadow-vertex) - (matrix-table :reg v1) - (single-count :reg a3) - ;; The two joint transforms. Each register briefly holds a joint index before it becomes the - ;; address of that joint's record. - (matrix-a :reg t0 :type pris-mtx) - (matrix-b :reg a3 :type pris-mtx) - (mtx-a-0 :reg vf1) - (mtx-a-1 :reg vf2) - (mtx-a-2 :reg vf3) - (mtx-a-3 :reg vf4) - (mtx-b-0 :reg vf5) - (mtx-b-1 :reg vf6) - (mtx-b-2 :reg vf7) - (mtx-b-3 :reg vf8) - (skinned :reg vf9) - (blend :reg vf10) - (result :reg v0)) - (asm-block locate-mesh - (nop!) - (l.w matrix-table (-> geo qwc-data)) - (nop!) - (l.w vertex (-> geo ofs-verts)) - (sll matrix-table matrix-table 4) - (l.w matrix-a (-> geo ofs-refs)) - (add vertex vertex geo) - (l.h single-count (-> geo num-verts)) - (add matrix-a matrix-a geo) - ;; num-joints only sized the matrix transfer; skinning follows the per-vertex references. - (l.w t1 (-> geo num-joints)) - (add matrix-table geo matrix-table) - ;; The rest of the renderer reads the vertices out of the scratchpad, not the arena. - (s.w vertex (-> work vtx-table)) - (add.i matrix-table matrix-table SHADOW-MATRIX-BIAS) - (nop!) - (m joint-ref matrix-a) - (l.h matrix-a (-> geo num-twos)) - (m vertex vertex) - (nop!) - (sub single-count single-count matrix-a) - ;; This scratchpad address is never read: both loops load t0 before using it. - (lui matrix-a #x7000) - (ori matrix-a matrix-a 2608) - (b.z single-count xform-two-joint-verts :delay (nop!))) - ;; One joint. The masked multiply-add leaves w alone, so the vertex keeps its weight -- harmless - ;; here, and the edge and triangle tables index this table by position either way. - (asm-block skin-one-joint-vertices - (label xform-one-joint-loop) - (add.i single-count single-count -1) - (l.bu matrix-a (-> joint-ref joint-0)) - (nop!) - ;; joint-1 comes along with it and is ignored. - (l.bu t1 (-> joint-ref joint-1)) - (nop!) - (add.i joint-ref joint-ref (type-size shadow-matrix-ref)) - ;; one pris-mtx per joint - (sll matrix-a matrix-a 7) - (nop!) - (add matrix-a matrix-a matrix-table) - (nop!) - (nop!) - (l.vf mtx-a-0 (-> matrix-a t-mtx vector 0)) - (nop!) - (l.vf mtx-a-1 (-> matrix-a t-mtx vector 1)) - (nop!) - (l.vf mtx-a-2 (-> matrix-a t-mtx vector 2)) - (nop!) - (l.vf mtx-a-3 (-> matrix-a t-mtx vector 3)) - (nop!) - (l.vf skinned vertex) - (vmulaw.xyzw acc mtx-a-3 vf0) - (nop!) - (vmaddax.xyzw acc mtx-a-0 skinned) - (nop!) - (vmadday.xyzw acc mtx-a-1 skinned) - (nop!) - (vmaddz.xyz skinned mtx-a-2 skinned) - (nop!) - (nop!) - (s.vf skinned vertex) - (add.i vertex vertex (type-size shadow-vertex)) - (nop!) - (b.nz single-count xform-one-joint-loop :delay (nop!))) - ;; Two joints. Both transforms are run on the same source vertex, then blended by the weight the - ;; vertex carries in w: - ;; - ;; out = a * weight + b * (1 - weight) - ;; - ;; The 1 - weight complement is taken before either transform runs, into the w lane of the - ;; register that will hold the first result, so nothing has to be materialized to hold it. The - ;; final vaddx.w rebuilds a w of one out of vf0 alone, which is the cheapest 1.0 the VU has. - (asm-block skin-two-joint-vertices - (label xform-two-joint-verts) - (nop!) - (l.h two-count (-> geo num-twos)) - (b.z two-count xform-done :delay (nop!)) - (label xform-two-joint-loop) - (add.i two-count two-count -1) - (l.bu matrix-a (-> joint-ref joint-0)) - (nop!) - (l.bu matrix-b (-> joint-ref joint-1)) - (sll matrix-a matrix-a 7) - (add.i joint-ref joint-ref (type-size shadow-matrix-ref)) - (sll matrix-b matrix-b 7) - (add matrix-a matrix-a matrix-table) - (add matrix-b matrix-b matrix-table) - (l.vf mtx-a-0 (-> matrix-a t-mtx vector 0)) - (nop!) - (l.vf mtx-a-1 (-> matrix-a t-mtx vector 1)) - (nop!) - (l.vf mtx-a-2 (-> matrix-a t-mtx vector 2)) - (nop!) - (l.vf mtx-a-3 (-> matrix-a t-mtx vector 3)) - (nop!) - (l.vf skinned vertex) - (nop!) - (l.vf mtx-b-0 (-> matrix-b t-mtx vector 0)) - (nop!) - (l.vf mtx-b-1 (-> matrix-b t-mtx vector 1)) - (nop!) - (l.vf mtx-b-2 (-> matrix-b t-mtx vector 2)) - (nop!) - (l.vf mtx-b-3 (-> matrix-b t-mtx vector 3)) - (vsubw.w blend vf0 skinned) - (nop!) - (vmulaw.xyzw acc mtx-a-3 vf0) - (nop!) - (vmaddax.xyzw acc mtx-a-0 skinned) - (nop!) - (vmadday.xyzw acc mtx-a-1 skinned) - (nop!) - (vmaddz.xyz blend mtx-a-2 skinned) - (nop!) - (vmulaw.xyzw acc mtx-b-3 vf0) - (nop!) - (vmaddax.xyzw acc mtx-b-0 skinned) - (nop!) - (vmadday.xyzw acc mtx-b-1 skinned) - (nop!) - (vmaddz.xyz skinned mtx-b-2 skinned) - (nop!) - (vmulaw.xyz acc blend skinned) - (nop!) - (vmaddw.xyz skinned skinned blend) - (nop!) - (vaddx.w skinned vf0 vf0) - (nop!) - (nop!) - (s.vf skinned vertex) - (add.i vertex vertex (type-size shadow-vertex)) - (nop!) - (b.nz two-count xform-two-joint-loop :delay (nop!)) - (nop!) - (nop!)) - (asm-block return - (label xform-done) - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-calc-dual-verts () - "Project every vertex of the caster's mesh along the ray from the light locus onto the ground - plane, and append the resulting dual vertices to the workspace arena." - (declare (asm-func none) (allow-saved-regs)) - ;; A vertex and its dual are the two ends of one extrusion, so a silhouette edge becomes the quad - ;; joining one pair to the next and a facing triangle becomes a cap on either end. VU0 takes four - ;; vertices per call and answers one call late, so this loop hands over a group and then reads back - ;; the group before it. The quadword loads run ahead of the count and can read up to three vertices - ;; past the end of the table; those extra results are computed and then not stored. - (rlet ((geo :reg a0 :type shadow-header) - (count :reg a0) - (work :reg a1 :type shadow-dcache) - (vertex :reg a2 :type shadow-vertex) - (dual :reg v1) - (dual-base :reg a3) - ;; The four vertices in flight, shuttled through EE registers because a coprocessor move is - ;; the only way into VU0's register file. - (quad-0 :reg a3) - (quad-1 :reg t0) - (quad-2 :reg t1) - (quad-3 :reg t2) - (locus :reg vf1) - (ground-plane :reg vf2) - (vertex-0 :reg vf3) - (vertex-1 :reg vf4) - (vertex-2 :reg vf5) - (vertex-3 :reg vf6) - (dual-0 :reg vf23) - (dual-1 :reg vf24) - (dual-2 :reg vf25) - (dual-3 :reg vf26) - (result :reg v0)) - (asm-block start-dual-vertex-table - (nop!) - (l.w dual (-> work dcache-top)) - (nop!) - (l.w vertex (-> work vtx-table)) - ;; Duals are written a quadword at a time, so round the arena cursor up before claiming it. - (add.i dual dual 15) - (l.vf locus (-> work center)) - (sra dual dual 4) - (l.vf ground-plane (-> work plane)) - (sll dual-base dual 4) - (l.h count (-> geo num-verts)) - (m dual dual-base) - (s.w dual-base (-> work ptr-dual-verts)) - (m vertex vertex) - (nop!) - (b.z count dual-verts-done :delay (nop!))) - ;; Two groups go in before the first comes out: one to fill the pipeline and one to run while the - ;; first is finishing. - (asm-block prime-vu0-pipeline - (nop!) - (l.q quad-0 vertex) - (nop!) - (l.q quad-1 vertex 16) - (nop!) - (l.q quad-2 vertex 32) - (nop!) - (l.q quad-3 vertex 48) - (add.i vertex vertex 64) - (m vertex-0 quad-0) - (nop!) - (m.ni vertex-1 quad-1) - (nop!) - (m.ni vertex-2 quad-2) - (nop!) - (m.ni vertex-3 quad-3) - (callms SHADOW-VU0-PROJECT-VERTS) - (nop!) - (add.i count count -4) - (l.q quad-0 vertex) - (b.le count r0 drain-vu0-pipeline :delay (l.q quad-1 vertex 16)) - (nop!) - (l.q quad-2 vertex 32) - (nop!) - (l.q quad-3 vertex 48) - (add.i vertex vertex 64) - (m vertex-0 quad-0) - (nop!) - (m.ni vertex-1 quad-1) - (nop!) - (m.ni vertex-2 quad-2) - (nop!) - (m.ni vertex-3 quad-3)) - ;; Start this group, then spend the divide latency reading out the previous group's four duals - ;; and fetching the next four vertices. The runs of no-ops are that latency: a VU0 vector - ;; register is not readable for seven instructions after the microprogram writes it, and the - ;; microprogram itself needs a head start before the first result exists at all. - (asm-block project-vertex-groups - (label project-group-loop) - (callms SHADOW-VU0-PROJECT-VERTS) - (nop! :count 10) - (m quad-0 dual-0) - (nop! :count 7) - (s.q quad-0 dual) - (nop! :count 3) - (m.ni quad-0 dual-1) - (nop! :count 7) - (s.q quad-0 dual 16) - (nop! :count 7) - (m.ni quad-0 dual-2) - (nop! :count 7) - (s.q quad-0 dual 32) - (nop! :count 7) - (m.ni quad-0 dual-3) - (nop! :count 7) - (s.q quad-0 dual 48) - (nop! :count 3) - (l.q quad-0 vertex) - (nop!) - (l.q quad-1 vertex 16) - (nop!) - (l.q quad-2 vertex 32) - (add.i count count -4) - (l.q quad-3 vertex 48) - (add.i vertex vertex 64) - (add.i dual dual 64) - (nop!) - (m.ni vertex-0 quad-0) - (nop!) - (m.ni vertex-1 quad-1) - (nop!) - (m.ni vertex-2 quad-2) - (b.gt count r0 project-group-loop :delay (m.ni vertex-3 quad-3))) - ;; Fewer than four vertices are left. The drain entry finishes the group in flight; a0 is zero or - ;; negative by exactly the number of vertices the last group overran, and a2, t0, and t2 hold it - ;; biased so that one test each picks the cut-off. - (asm-block drain-vu0-pipeline - (label drain-vu0-pipeline) - (callms SHADOW-VU0-PROJECT-DRAIN) - (nop!) - (vnop) - (nop!) - (add.i a2 count 3) - (m quad-0 dual-0) - (add.i t0 count 2) - (m t1 dual-1) - (add.i t2 count 1) - (m t3 dual-2) - (add.i count count 4) - (m t4 dual-3) - (b.z a2 dual-verts-advance :delay (s.q quad-0 dual)) - (b.z t0 dual-verts-advance :delay (s.q t1 dual 16)) - (b.z t2 dual-verts-advance :delay (s.q t3 dual 32)) - (nop!) - (s.q t4 dual 48)) - (asm-block finish-dual-vertex-table - (label dual-verts-advance) - (sll count count 4) - (nop!) - (add dual dual count) - (nop!) - (label dual-verts-done) - (s.w dual (-> work dcache-top)) - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-scissor-edges () - "Clip every extrusion against the camera's near plane so no shadow wall reaches behind the eye." - (declare (asm-func none) (allow-saved-regs)) - ;; near-plane is (0, 0, 1, -2d) in camera space, so a point's distance from it is just z - 2d: the - ;; volume is cut off at twice the near-plane distance, which keeps a wall that passes the camera - ;; from exploding across the whole screen after the perspective divide. - ;; - ;; The distance is deliberately copied into lane y before it is read back, because the EE tests the - ;; sign of the quadword's low doubleword -- lane y -- with an ordinary bltz. When exactly one end of - ;; an extrusion is outside, that end is replaced with the intersection point; the arithmetic is the - ;; same either way, only the destination changes, so the pair keeps its orientation. - (rlet ((geo :reg a0 :type shadow-header) - (work :reg a1 :type shadow-dcache) - ;; Both cursors take over argument registers once the tables have been found. - (dual :reg a0) - (vertex :reg a1) - (count :reg v1) - ;; a2 and a3 first carry the two table pointers and then the two plane distances. - (vertex-table :reg a2) - (dual-table :reg a3) - (vertex-dist :reg a2) - (dual-dist :reg a3) - (vertex-pos :reg vf1) - (dual-pos :reg vf2) - (near-plane :reg vf3) - (extrusion :reg vf4) - (numerator :reg vf5) - (denominator :reg vf6) - (vertex-test :reg vf7) - (dual-test :reg vf8) - (result :reg v0)) - (asm-block find-tables - (nop!) - (l.w dual-table (-> work ptr-dual-verts)) - (nop!) - (l.w vertex-table (-> work vtx-table)) - (nop!) - (l.vf near-plane (-> work near-plane)) - (nop!) - (l.h count (-> geo num-verts)) - (m dual dual-table) - (nop!) - (m vertex vertex-table) - (nop!) - (b.z count near-clip-done :delay (nop!))) - (asm-block near-clip-pairs - (label near-clip-loop) - (l.vf vertex-pos vertex) - (l.vf dual-pos dual) - (vaddw.z vertex-test vertex-pos near-plane) - (vaddw.z dual-test dual-pos near-plane) - (vsubz.z denominator vertex-pos dual-pos) - (vaddw.z numerator vertex-pos near-plane) - ;; Move each distance from z into y so a scalar sign test can find it. - (vaddz.y vertex-test vf0 vertex-test) - (vaddz.y dual-test vf0 dual-test) - (vsub.xyz extrusion dual-pos vertex-pos) - (m vertex-dist vertex-test) - (m dual-dist dual-test) - (b.lt vertex-dist r0 near-clip-vertex-outside :delay (nop!)) - (b.gt dual-dist r0 near-clip-next :delay (nop!)) - (b near-clip-move-dual :delay (nop!))) - ;; The original vertex is behind the plane. If the dual is too the whole extrusion is gone and - ;; nothing needs moving; otherwise the original slides forward onto the plane. - (asm-block near-clip-move-vertex - (label near-clip-vertex-outside) - (b.lt dual-dist r0 near-clip-next :delay (nop!)) - (vdiv Q numerator.z denominator.z) - (vwaitq) - (vmulq.xyzw extrusion extrusion Q) - (vnop) - (vnop) - (vnop) - (add.vf vertex-pos vertex-pos extrusion) - (b near-clip-next :delay (s.vf vertex-pos vertex))) - ;; The dual is the end that is behind the plane, so the same intersection point is written there - ;; instead. The wall keeps its winding either way. - (asm-block near-clip-move-dual - (label near-clip-move-dual) - (vdiv Q numerator.z denominator.z) - (vwaitq) - (vmulq.xyzw extrusion extrusion Q) - (vnop) - (vnop) - (vnop) - (add.vf vertex-pos vertex-pos extrusion) - (b near-clip-next :delay (s.vf vertex-pos dual))) - (asm-block next-pair - (label near-clip-next) - (add.i vertex vertex 16) - (add.i dual dual 16) - (add.i count count -1) - (b.nz count near-clip-loop :delay (nop!))) - (asm-block return - (label near-clip-done) - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!))) - ) - - (defun shadow-scissor-top () - "Pull any original vertex that stands above top-plane down onto it, shortening the volume from the - top without disturbing where it lands." - (declare (asm-func none) (allow-saved-regs)) - ;; Only the near end of each extrusion moves: the dual is already on the ground plane and must stay - ;; there. Sliding the original down its own extrusion keeps the wall planar, so the volume stays - ;; closed. This runs only when the caster asks for it with the scissor-top flag, and it is what - ;; keeps a tall character from casting shadow through a ceiling it is standing under. - (rlet ((geo :reg a0 :type shadow-header) - (work :reg a1 :type shadow-dcache) - (count :reg a0) - (dual :reg a1) - (vertex :reg v1) - (dist :reg a2) - (vertex-pos :reg vf1) - (dual-pos :reg vf2) - (top-plane :reg vf3) - (extrusion :reg vf4) - (numerator :reg vf5) - (denominator :reg vf6) - (result :reg v0)) - (asm-block find-tables - (nop!) - (l.w dist (-> work ptr-dual-verts)) - (nop!) - (l.w vertex (-> work vtx-table)) - (nop!) - (l.vf top-plane (-> work top-plane)) - (nop!) - (l.h count (-> geo num-verts)) - (m dual dist) - (nop!) - (m vertex vertex) - (nop!) - (b.z count top-clip-done :delay (nop!))) - (asm-block top-clip-pairs - (label top-clip-loop) - (l.vf vertex-pos vertex) - (l.vf dual-pos dual) - (vsub.xyzw extrusion dual-pos vertex-pos) - ;; The plane's d rides in w and a vertex's w is one, so folding all four lanes of the product - ;; gives the signed distance; the extrusion has no w, so only three lanes fold for the slope. - (mul.vf numerator vertex-pos top-plane) - (vmul.xyz denominator extrusion top-plane) - (vaddx.y numerator numerator numerator) - (vaddy.x denominator denominator denominator) - (vaddz.y numerator numerator numerator) - (vaddz.x denominator denominator denominator) - (vaddw.y numerator numerator numerator) - (m dist numerator) - (nop!) - (b.lt dist r0 top-clip-next :delay (nop!)) - (vdiv Q numerator.y denominator.x) - (vwaitq) - (vmulq.xyzw extrusion extrusion Q) - (vsub.xyzw vertex-pos vertex-pos extrusion) - (s.vf vertex-pos vertex)) - (asm-block next-pair - (label top-clip-next) - (add.i vertex vertex 16) - (add.i dual dual 16) - (add.i count count -1) - (b.nz count top-clip-loop :delay (nop!))) - (asm-block return - (label top-clip-done) - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!))) - ) - - (defun shadow-init-vars () - "Move the light locus, the light direction, and both clipping planes from world space into camera - space, and set up the arena cursor." - (declare (asm-func none) (allow-saved-regs)) - ;; Everything downstream of here -- projection, near clipping, top clipping, facing tests -- works - ;; in camera space, because that is the space VU1's perspective matrix expects and it is cheaper to - ;; move six quadwords once than every vertex twice. - ;; - ;; A direction only needs the rotation. A plane needs the rotation on its normal and then - ;; - ;; d' = d - dot(camera translation, rotated normal) - ;; - ;; which is the vsub?.w chain: the product of the translation row with the plane already has d - ;; sitting in w (the masked multiply-add never wrote it), so subtracting x, y, and z out of w in - ;; turn leaves exactly that. - (rlet ((camera :reg v1 :type math-camera) - (work :reg a1 :type shadow-dcache) - (cam-row-0 :reg vf7) - (cam-row-1 :reg vf8) - (cam-row-2 :reg vf9) - (cam-trans :reg vf10) - (light-dir :reg vf1) - (locus :reg vf2) - (ground-plane :reg vf11) - (top-plane :reg vf12) - (ground-fold :reg vf13) - (top-fold :reg vf14) - (result :reg v0)) - (asm-block camera-space-projection - (m! camera *math-camera*) - (m camera camera) - (l.vf cam-row-0 (-> camera camera-rot vector 0)) - (l.vf cam-row-1 (-> camera camera-rot vector 1)) - (l.vf cam-row-2 (-> camera camera-rot vector 2)) - (l.vf cam-trans (-> camera camera-rot vector 3)) - (l.vf light-dir (-> work light-dir)) - (l.vf ground-plane (-> work plane)) - (l.vf top-plane (-> work top-plane)) - (l.vf locus (-> work center)) - (vmulax.xyzw acc cam-row-0 light-dir) - (vmadday.xyzw acc cam-row-1 light-dir) - (vmaddz.xyzw light-dir cam-row-2 light-dir) - (vmulax.xyzw acc cam-row-0 ground-plane) - (vmadday.xyzw acc cam-row-1 ground-plane) - (vmaddz.xyz ground-plane cam-row-2 ground-plane) - (vmulax.xyzw acc cam-row-0 top-plane) - (vmadday.xyzw acc cam-row-1 top-plane) - (vmaddz.xyz top-plane cam-row-2 top-plane) - (mul.vf ground-fold cam-trans ground-plane) - (vmulaw.xyzw acc cam-trans vf0) - (vmaddax.xyzw acc cam-row-0 locus) - (mul.vf top-fold cam-trans top-plane) - (vsubx.w ground-fold ground-fold ground-fold) - (vsubx.w top-fold top-fold top-fold) - (vmadday.xyzw acc cam-row-1 locus) - (vmaddz.xyzw locus cam-row-2 locus) - (vsuby.w ground-fold ground-fold ground-fold) - (vsuby.w top-fold top-fold top-fold) - ;; Only w is written back into the plane; its rotated normal is already in xyz. - (vsubz.w ground-plane ground-fold ground-fold) - (vsubz.w top-plane top-fold top-fold) - (s.vf locus (-> work center)) - (s.vf light-dir (-> work light-dir)) - (s.vf ground-plane (-> work plane)) - (s.vf top-plane (-> work top-plane))) - (asm-block return - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-find-facing-single-tris () - "Mark every single-sided triangle that faces the light and collect the marked ones into the - workspace arena." - (declare (asm-func none) (allow-saved-regs)) - ;; A single-sided mesh casts from the faces the light can see, so each triangle's cross product is - ;; dotted with the light direction and the sign decides. The answer is written back into the spare - ;; faces byte of the triangle's own record, because the edge pass needs to ask a triangle which way - ;; it faced without keeping a parallel table. - ;; - ;; VU0 classifies four triangles at a time, so the addresses of twelve vertices have to be produced - ;; per call. That is done entirely in the multimedia unit: one quadword holds four shadow-tri - ;; records, and unpacking bytes to halfwords, shifting left by four to scale an index into a - ;; sixteen-byte vertex, unpacking halfwords to words, and adding the broadcast table base turns - ;; four records into three quadwords of four ready-made addresses. Only extracting them for the - ;; load instructions costs anything, which is what pcpyud and dsra32 are doing. - (rlet ((geo :reg a0 :type shadow-header) - ;; a0 becomes the vertex table base, broadcast into all four packed words. - (vertex-base :reg a0) - (work :reg a1 :type shadow-dcache) - (list-base :reg v1) - (list :reg a2) - (tri :reg a3 :type shadow-tri) - (count :reg t0) - ;; A triangle that faces the light gets a one in its faces byte; zero means it does not. - (facing-mark :reg t1) - (indices :reg t3) - (light-dir :reg vf1) - (tri-a-0 :reg vf2) - (tri-a-1 :reg vf3) - (tri-a-2 :reg vf4) - (edge-0 :reg vf5) - (edge-1 :reg vf6) - (tri-b-0 :reg vf7) - (tri-b-1 :reg vf8) - (tri-b-2 :reg vf9) - (tri-c-0 :reg vf12) - (tri-c-1 :reg vf13) - (tri-c-2 :reg vf14) - (tri-d-0 :reg vf17) - (tri-d-1 :reg vf18) - (tri-d-2 :reg vf19) - (facing-a :reg vf22) - (facing-b :reg vf23) - (facing-c :reg vf24) - (facing-d :reg vf25) - (result :reg v0)) - ;; t2 and t4 through t9, plus s4, s5, gp, and ra, are the address shuttle. They hold a different - ;; packed quantity every two instructions and are left with physical names for that reason; the - ;; block comment above describes what the sequence as a whole is doing. - (asm-block enter-classifier - (add.i sp sp -64) - (s.d ra sp) - (s.q s4 sp 16) - (s.q s5 sp 32) - (s.q gp sp 48) - (l.w list-base (-> work dcache-top)) - (l.h count (-> geo num-single-tris)) - (m list list-base) - (l.w tri (-> geo ofs-single-tris)) - (add vertex-base tri geo) - (m tri vertex-base) - ;; VU0 entry 0 wants only the light direction; these two are overwritten before they are read. - (l.vf tri-a-0 (-> work center)) - (l.vf light-dir (-> work light-dir)) - (l.vf vf11 (-> work plane)) - (l.w vertex-base (-> work vtx-table)) - (pextlw vertex-base vertex-base vertex-base) - (pextlw vertex-base vertex-base vertex-base) - (add.i count count -4) - (add.i facing-mark r0 1) - (b.lt count r0 classify-remainder :delay (add.i count count 4)) - (nop!) - (l.q indices tri) - (nop!) - (nop!)) - (asm-block address-four-triangles - (pextub t2 r0 indices) - (mmi-nop!) - (pextlb indices r0 indices) - (mmi-nop!) - (sll.h t2 t2 4) - (mmi-nop!) - (sll.h t4 indices 4) - (mmi-nop!) - (pextuh indices r0 t4) - (mmi-nop!) - (pextlh t4 r0 t4) - (mmi-nop!) - (pextuh t7 r0 t2) - (mmi-nop!) - (pextlh t5 r0 t2) - (mmi-nop!) - (add.w t6 t4 vertex-base) - (mmi-nop!) - (pcpyud t4 t6 r0) - (l.q t2 t6) - (add.w t8 indices vertex-base) - (l.q indices t4) - (pcpyud t9 t8 r0) - (l.q t4 t8) - (dsra32 t6 t6 0) - (dsra32 t8 t8 0) - (add.w s5 t5 vertex-base) - (l.q t5 t9) - (pcpyud t9 s5 r0) - (l.q t6 t6) - (add.w gp t7 vertex-base) - (l.q t7 t8) - (pcpyud ra gp r0) - (l.q t8 s5) - (dsra32 s5 s5 0) - (dsra32 s4 gp 0) - (nop!) - (l.q s5 s5) - (nop!) - (l.q t9 t9) - (nop!) - (l.q gp gp) - (nop!) - (l.q s4 s4) - (nop!) - (l.q ra ra) - (nop!) - (m.ni tri-a-0 t2) - (nop!) - (m.ni tri-a-1 t6) - (nop!) - (m.ni tri-a-2 indices) - (nop!) - (m.ni tri-b-0 t4) - (nop!) - (m.ni tri-b-1 t7) - (nop!) - (m.ni tri-b-2 t5) - (nop!) - (m.ni tri-c-0 t8) - (nop!) - (m.ni tri-c-1 s5) - (nop!) - (m.ni tri-c-2 t9) - (nop!) - (m.ni tri-d-0 gp) - (nop!) - (m.ni tri-d-1 s4) - (nop!) - (m.ni tri-d-2 ra)) - ;; Hand the loaded group to VU0 and immediately start addressing the next one; the previous - ;; group's four answers come back at the bottom. Reading a negative dot product means the face - ;; turns toward the light. - (asm-block classify-triangle-groups - (label classify-four-tris) - (nop!) - (l.q indices tri 16) - (add.i count count -4) - (callms SHADOW-VU0-CLASSIFY-TRIS) - (pextub t2 r0 indices) - (mmi-nop!) - (pextlb indices r0 indices) - (mmi-nop!) - (sll.h t2 t2 4) - (mmi-nop!) - (sll.h t4 indices 4) - (mmi-nop!) - (pextuh indices r0 t4) - (mmi-nop!) - (pextlh t4 r0 t4) - (mmi-nop!) - (pextuh t7 r0 t2) - (mmi-nop!) - (pextlh t5 r0 t2) - (mmi-nop!) - (add.w t6 t4 vertex-base) - (mmi-nop!) - (pcpyud t4 t6 r0) - (l.q t2 t6) - (add.w t8 indices vertex-base) - (l.q indices t4) - (pcpyud t9 t8 r0) - (l.q t4 t8) - (dsra32 t6 t6 0) - (dsra32 t8 t8 0) - (add.w s5 t5 vertex-base) - (l.q t5 t9) - (pcpyud t9 s5 r0) - (l.q t6 t6) - (add.w gp t7 vertex-base) - (l.q t7 t8) - (pcpyud ra gp r0) - (l.q t8 s5) - (dsra32 s5 s5 0) - (dsra32 s4 gp 0) - (nop!) - (l.q s5 s5) - (nop!) - (l.q t9 t9) - (nop!) - (l.q gp gp) - (nop!) - (l.q s4 s4) - (nop!) - (l.q ra ra) - (nop!) - (m.ni tri-a-0 t2) - (nop!) - (m.ni tri-a-1 t6) - (nop!) - (m.ni tri-a-2 indices) - (nop!) - (m.ni tri-b-0 t4) - (nop!) - (m.ni tri-b-1 t7) - (nop!) - (m.ni tri-b-2 t5) - (nop!) - (m.ni tri-c-0 t8) - (nop!) - (m.ni tri-c-1 s5) - (nop!) - (m.ni tri-c-2 t9) - (nop!) - (m.ni tri-d-0 gp) - (nop!) - (m.ni tri-d-1 s4) - (nop!) - (m.ni tri-d-2 ra) - (nop!) - (m.ni indices facing-a) - (nop!) - (m.ni t4 facing-b) - (nop!) - (m.ni t2 facing-c) - (b.ge indices r0 checked-tri-0 :delay (m.ni indices facing-d)) - (s.b facing-mark (-> tri faces)) - (nop!) - (s.w tri list) - (add.i list list 4) - (label checked-tri-0) - (b.ge t4 r0 checked-tri-1 :delay (add.i tri tri (type-size shadow-tri))) - (s.b facing-mark (-> tri faces)) - (nop!) - (s.w tri list) - (add.i list list 4) - (label checked-tri-1) - (b.ge t2 r0 checked-tri-2 :delay (add.i tri tri (type-size shadow-tri))) - (s.b facing-mark (-> tri faces)) - (nop!) - (s.w tri list) - (add.i list list 4) - (label checked-tri-2) - (b.ge indices r0 checked-tri-3 :delay (add.i tri tri (type-size shadow-tri))) - (s.b facing-mark (-> tri faces)) - (nop!) - (s.w tri list) - (add.i list list 4) - (label checked-tri-3) - (b.gt count r0 classify-four-tris :delay (add.i tri tri (type-size shadow-tri)))) - ;; The last one to three triangles, one at a time and without VU0: the cross product and dot fit - ;; in the inline VU0 macro instructions and the batch would cost more than it saves. - (asm-block classify-remaining-triangles - (label classify-remainder) - (b.le count r0 single-tris-done :delay (nop!)) - (label classify-one-tri) - (l.bu t2 (-> tri ind-0)) - (l.bu indices (-> tri ind-1)) - (l.bu facing-mark (-> tri ind-2)) - (sll t2 t2 4) - (sll indices indices 4) - (sll facing-mark facing-mark 4) - (add t2 t2 vertex-base) - (add indices indices vertex-base) - (add facing-mark facing-mark vertex-base) - (l.vf tri-a-0 t2) - (l.vf tri-a-1 indices) - (l.vf tri-a-2 facing-mark) - (vsub.xyzw edge-0 tri-a-1 tri-a-0) - (vsub.xyzw edge-1 tri-a-2 tri-a-0) - (vopmula.xyz acc edge-0 edge-1) - (vopmsub.xyz edge-0 edge-1 edge-0) - (vmul.xyz edge-0 edge-0 light-dir) - (vaddx.y edge-0 edge-0 edge-0) - (vaddz.y edge-0 edge-0 edge-0) - (m facing-mark edge-0) - (nop!) - (b.ge facing-mark r0 next-remainder-tri :delay (add.i facing-mark r0 1)) - (s.w tri list) - (add.i list list 4) - (s.b facing-mark (-> tri faces)) - (label next-remainder-tri) - (add.i count count -1) - (b.nz count classify-one-tri :delay (add.i tri tri (type-size shadow-tri)))) - ;; Record the list for shadow-add-facing-single-tris and hand the arena back grown by it. - (asm-block publish-single-tri-list - (label single-tris-done) - (sub vertex-base list list-base) - (sra vertex-base vertex-base 2) - (s.w vertex-base (-> work num-facing-single-tris)) - (s.w list-base (-> work single-tri-list)) - (s.w list (-> work dcache-top)) - (m result r0) - (l.d ra sp) - (l.q gp sp 48) - (l.q s5 sp 32) - (l.q s4 sp 16) - (jr ra :delay (add.i sp sp 64)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-find-single-edges () - "Collect the silhouette of a single-sided mesh from its edge adjacency table." - (declare (asm-func none) (allow-saved-regs)) - ;; An edge is on the silhouette when the light can see exactly one of the two triangles that share - ;; it. tri-1 of 255 means there is no second triangle at all -- an open boundary -- and such an edge - ;; is on the silhouette whenever its one face is lit. - ;; - ;; Each collected edge is recorded as its byte offset in the source table plus a flag saying which - ;; endpoint to emit first, so that the quad VU1 builds from it always winds outward. The flag is - ;; "tri-0 was the unlit one", which is exactly when the pair has to be swapped; a boundary edge - ;; needs no swap and gets zero, dropped in the branch delay slot before the test even runs. - (rlet ((geo :reg a0 :type shadow-header) - (work :reg a1 :type shadow-dcache) - (single-tris :reg a0 :type shadow-tri) - (list-base :reg a2) - (list :reg v1) - (count :reg a3) - (edge-table :reg t0) - (edge :reg t1 :type shadow-edge) - (no-neighbor :reg t2) - (flip :reg t3) - ;; t3, t4 and t5 each hold a triangle's address and then the faces byte read out of it, so - ;; each has both a pointer name and a value name. - (tri-a :reg t3 :type shadow-tri) - (neighbor :reg t4 :type shadow-tri) - (tri-b :reg t5 :type shadow-tri) - (faces-0 :reg t4) - (faces-1 :reg t5) - (result :reg v0)) - (asm-block find-tables - (l.w list-base (-> work dcache-top)) - (l.h count (-> geo num-single-edges)) - (m list list-base) - (l.w edge-table (-> geo ofs-single-edges)) - (b.z count single-edges-done :delay (l.w edge (-> geo ofs-single-tris))) - (add edge-table edge-table geo) - (s.w list-base (-> work single-edge-list)) - (add single-tris edge single-tris) - (s.w edge-table (-> work single-edge-table)) - (m edge edge-table) - (add.i no-neighbor r0 255) - (nop!)) - (asm-block scan-single-edges - (label single-edge-loop) - (add.i count count -1) - (l.bu faces-0 (-> edge tri-1)) - (nop!) - (l.bu faces-1 (-> edge tri-0)) - (b.eq faces-0 no-neighbor single-edge-boundary :delay (m flip r0)) - (sll flip faces-1 2) - (sll faces-0 faces-0 2) - (add flip flip single-tris) - (add faces-1 faces-0 single-tris) - (nop!) - (l.bu faces-0 (-> tri-a faces)) - (nop!) - (l.bu faces-1 (-> tri-b faces)) - (sltiu flip faces-0 1) - (nop!) - (b.eq faces-0 faces-1 single-edge-next :delay (nop!)) - (b single-edge-keep :delay (nop!))) - ;; No second triangle. Keep the edge only if its one face is lit. - (asm-block boundary-edge - (label single-edge-boundary) - (sll faces-0 faces-1 2) - (nop!) - (add faces-0 faces-0 single-tris) - (nop!) - (nop!) - (l.bu faces-0 (-> neighbor faces)) - (b.z faces-0 single-edge-next :delay (nop!))) - (asm-block keep-edge - (label single-edge-keep) - (sub faces-0 edge edge-table) - (s.h flip list 2) - (s.h faces-0 list) - (add.i list list 4)) - (asm-block next-edge - (label single-edge-next) - (b.nz count single-edge-loop :delay (add.i edge edge (type-size shadow-edge)))) - (asm-block publish-single-edge-list - (label single-edges-done) - (sub single-tris list list-base) - (sra single-tris single-tris 2) - (s.w single-tris (-> work num-single-edges)) - (s.w list (-> work dcache-top)) - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-find-facing-double-tris () - "Choose the winding of every double-sided triangle that faces the light and record it in the - triangle's own record." - (declare (asm-func none) (allow-saved-regs)) - ;; Double-sided geometry -- a flag, a cape, a leaf -- always casts, whichever way it is turned, so - ;; there is nothing to reject here. What matters is which of the two windings faces the light, and - ;; that answer is stored in the faces byte for the edge pass and for VU1, which reverses two of the - ;; three indices when it draws the cap. No list is built: every double-sided triangle is emitted. - ;; - ;; The four-at-a-time addressing is the same trick as the single-sided classifier; see the comment - ;; there for what the multimedia sequence computes. - (rlet ((geo :reg a0 :type shadow-header) - ;; a0 becomes the vertex table base, broadcast into all four packed words. - (vertex-base :reg a0) - (work :reg a1 :type shadow-dcache) - (count :reg a1) - (tri :reg v1 :type shadow-tri) - (facing-mark :reg a2) - (indices :reg t0) - (light-dir :reg vf1) - (tri-a-0 :reg vf2) - (tri-a-1 :reg vf3) - (tri-a-2 :reg vf4) - (edge-0 :reg vf5) - (edge-1 :reg vf6) - (tri-b-0 :reg vf7) - (tri-b-1 :reg vf8) - (tri-b-2 :reg vf9) - (tri-c-0 :reg vf12) - (tri-c-1 :reg vf13) - (tri-c-2 :reg vf14) - (tri-d-0 :reg vf17) - (tri-d-1 :reg vf18) - (tri-d-2 :reg vf19) - (facing-a :reg vf22) - (facing-b :reg vf23) - (facing-c :reg vf24) - (facing-d :reg vf25) - (result :reg v0)) - ;; a3 and t1 through t9 plus ra are the address shuttle and keep physical names. - (asm-block enter-classifier - (add.i sp sp -16) - (s.d ra sp) - (l.h facing-mark (-> geo num-double-tris)) - (l.w tri (-> geo ofs-double-tris)) - (add vertex-base tri geo) - (m tri vertex-base) - (s.w vertex-base (-> work double-tri-table)) - (l.vf light-dir (-> work light-dir)) - ;; VU0 entry 0 wants only the light direction; these two are overwritten before they are read. - (l.vf tri-a-0 (-> work center)) - (l.vf vf11 (-> work plane)) - (l.w vertex-base (-> work vtx-table)) - (pextlw vertex-base vertex-base vertex-base) - (pextlw vertex-base vertex-base vertex-base) - (add.i count facing-mark -4) - (add.i facing-mark r0 1) - (b.lt count r0 double-classify-remainder :delay (add.i count count 4)) - (nop!) - (l.q indices tri) - (nop!) - (nop!)) - (asm-block address-four-triangles - (pextub a3 r0 indices) - (mmi-nop!) - (pextlb indices r0 indices) - (mmi-nop!) - (sll.h a3 a3 4) - (mmi-nop!) - (sll.h t1 indices 4) - (mmi-nop!) - (pextuh indices r0 t1) - (mmi-nop!) - (pextlh t1 r0 t1) - (mmi-nop!) - (pextuh t4 r0 a3) - (mmi-nop!) - (pextlh t2 r0 a3) - (mmi-nop!) - (add.w t3 t1 vertex-base) - (mmi-nop!) - (pcpyud t1 t3 r0) - (l.q a3 t3) - (add.w t5 indices vertex-base) - (l.q indices t1) - (pcpyud t6 t5 r0) - (l.q t1 t5) - (dsra32 t3 t3 0) - (dsra32 t5 t5 0) - (add.w t9 t2 vertex-base) - (l.q t2 t6) - (pcpyud t6 t9 r0) - (l.q t3 t3) - (add.w t8 t4 vertex-base) - (l.q t4 t5) - (pcpyud t7 t8 r0) - (l.q t5 t9) - (dsra32 t9 t9 0) - (dsra32 ra t8 0) - (nop!) - (l.q t9 t9) - (nop!) - (l.q t6 t6) - (nop!) - (l.q t8 t8) - (nop!) - (l.q ra ra) - (nop!) - (l.q t7 t7) - (nop!) - (m.ni tri-a-0 a3) - (nop!) - (m.ni tri-a-1 t3) - (nop!) - (m.ni tri-a-2 indices) - (nop!) - (m.ni tri-b-0 t1) - (nop!) - (m.ni tri-b-1 t4) - (nop!) - (m.ni tri-b-2 t2) - (nop!) - (m.ni tri-c-0 t5) - (nop!) - (m.ni tri-c-1 t9) - (nop!) - (m.ni tri-c-2 t6) - (nop!) - (m.ni tri-d-0 t8) - (nop!) - (m.ni tri-d-1 ra) - (nop!) - (m.ni tri-d-2 t7)) - ;; A negative dot means the authored winding already faces the light, and the mark goes in; a - ;; nonnegative one leaves the byte at the zero the packed data shipped with, and VU1 reverses two - ;; of the three indices when it draws that cap. - (asm-block classify-triangle-groups - (label double-classify-four-tris) - (nop!) - (l.q indices tri 16) - (add.i count count -4) - (callms SHADOW-VU0-CLASSIFY-TRIS) - (pextub a3 r0 indices) - (mmi-nop!) - (pextlb indices r0 indices) - (mmi-nop!) - (sll.h a3 a3 4) - (mmi-nop!) - (sll.h t1 indices 4) - (mmi-nop!) - (pextuh indices r0 t1) - (mmi-nop!) - (pextlh t1 r0 t1) - (mmi-nop!) - (pextuh t4 r0 a3) - (mmi-nop!) - (pextlh t2 r0 a3) - (mmi-nop!) - (add.w t3 t1 vertex-base) - (mmi-nop!) - (pcpyud t1 t3 r0) - (l.q a3 t3) - (add.w t5 indices vertex-base) - (l.q indices t1) - (pcpyud t6 t5 r0) - (l.q t1 t5) - (dsra32 t3 t3 0) - (dsra32 t5 t5 0) - (add.w t9 t2 vertex-base) - (l.q t2 t6) - (pcpyud t6 t9 r0) - (l.q t3 t3) - (add.w t8 t4 vertex-base) - (l.q t4 t5) - (pcpyud t7 t8 r0) - (l.q t5 t9) - (dsra32 t9 t9 0) - (dsra32 ra t8 0) - (nop!) - (l.q t9 t9) - (nop!) - (l.q t6 t6) - (nop!) - (l.q t8 t8) - (nop!) - (l.q ra ra) - (nop!) - (l.q t7 t7) - (nop!) - (m.ni tri-a-0 a3) - (nop!) - (m.ni tri-a-1 t3) - (nop!) - (m.ni tri-a-2 indices) - (nop!) - (m.ni tri-b-0 t1) - (nop!) - (m.ni tri-b-1 t4) - (nop!) - (m.ni tri-b-2 t2) - (nop!) - (m.ni tri-c-0 t5) - (nop!) - (m.ni tri-c-1 t9) - (nop!) - (m.ni tri-c-2 t6) - (nop!) - (m.ni tri-d-0 t8) - (nop!) - (m.ni tri-d-1 ra) - (nop!) - (m.ni tri-d-2 t7) - (nop!) - (m.ni indices facing-a) - (nop!) - (m.ni t1 facing-b) - (nop!) - (m.ni a3 facing-c) - (b.ge indices r0 double-checked-tri-0 :delay (m.ni indices facing-d)) - (s.b facing-mark (-> tri faces)) - (nop!) - (label double-checked-tri-0) - (b.ge t1 r0 double-checked-tri-1 :delay (add.i tri tri (type-size shadow-tri))) - (s.b facing-mark (-> tri faces)) - (nop!) - (label double-checked-tri-1) - (b.ge a3 r0 double-checked-tri-2 :delay (add.i tri tri (type-size shadow-tri))) - (s.b facing-mark (-> tri faces)) - (nop!) - (label double-checked-tri-2) - (b.ge indices r0 double-checked-tri-3 :delay (add.i tri tri (type-size shadow-tri))) - (s.b facing-mark (-> tri faces)) - (nop!) - (label double-checked-tri-3) - (b.gt count r0 double-classify-four-tris :delay (add.i tri tri (type-size shadow-tri)))) - (asm-block classify-remaining-triangles - (label double-classify-remainder) - (b.le count r0 double-tris-done :delay (nop!)) - (label double-classify-one-tri) - (l.bu a3 (-> tri ind-0)) - (l.bu indices (-> tri ind-1)) - (l.bu facing-mark (-> tri ind-2)) - (sll a3 a3 4) - (sll indices indices 4) - (sll facing-mark facing-mark 4) - (add a3 a3 vertex-base) - (add indices indices vertex-base) - (add facing-mark facing-mark vertex-base) - (l.vf tri-a-0 a3) - (l.vf tri-a-1 indices) - (l.vf tri-a-2 facing-mark) - (vsub.xyzw edge-0 tri-a-1 tri-a-0) - (vsub.xyzw edge-1 tri-a-2 tri-a-0) - (vopmula.xyz acc edge-0 edge-1) - (vopmsub.xyz edge-0 edge-1 edge-0) - (vmul.xyz edge-0 edge-0 light-dir) - (vaddx.y edge-0 edge-0 edge-0) - (vaddz.y edge-0 edge-0 edge-0) - (m facing-mark edge-0) - (nop!) - (b.ge facing-mark r0 double-next-remainder-tri :delay (add.i facing-mark r0 1)) - (s.b facing-mark (-> tri faces)) - (label double-next-remainder-tri) - (add.i count count -1) - (b.nz count double-classify-one-tri :delay (add.i tri tri (type-size shadow-tri)))) - (asm-block return - (label double-tris-done) - (m result r0) - (l.d ra sp) - (jr ra :delay (add.i sp sp 16)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-find-double-edges () - "Collect the silhouette of a double-sided mesh from its edge adjacency table." - (declare (asm-func none) (allow-saved-regs)) - ;; Double-sided faces always cast, so an interior edge is on the silhouette when its two triangles - ;; chose *different* windings -- the surface folds away from the light there. Such an edge needs two - ;; quads, one for each of the two sheets meeting at it, and gets two records with opposite - ;; orientation flags; an open boundary edge needs only one. - ;; - ;; This is the one place a silhouette record is emitted more than once per edge, and it is why the - ;; double-sided edge list can be twice as long as the source table. - (rlet ((geo :reg a0 :type shadow-header) - (work :reg a1 :type shadow-dcache) - (edge-table :reg a0 :type shadow-edge) - (list-base :reg a2) - (list :reg v1) - (count :reg a3) - (double-tris :reg t0 :type shadow-tri) - (edge :reg t1 :type shadow-edge) - (no-neighbor :reg t2) - ;; t3 and t4 each hold a triangle's address and then the faces byte read out of it. - (tri-b :reg t3 :type shadow-tri) - (tri-a :reg t4 :type shadow-tri) - (faces-1 :reg t3) - (faces-0 :reg t4) - (flip :reg t5) - (result :reg v0)) - (asm-block find-tables - (l.w list-base (-> work dcache-top)) - (l.h count (-> geo num-double-edges)) - (m list list-base) - (l.w edge (-> geo ofs-double-edges)) - (b.z count double-edges-done :delay (l.w double-tris (-> work double-tri-table))) - (add edge-table edge geo) - (s.w list-base (-> work double-edge-list)) - (s.w edge-table (-> work double-edge-table)) - (m edge edge-table) - (add.i no-neighbor r0 255)) - (asm-block scan-double-edges - (label double-edge-loop) - (add.i count count -1) - (l.bu faces-1 (-> edge tri-1)) - (nop!) - (l.bu faces-0 (-> edge tri-0)) - (b.eq faces-1 no-neighbor double-edge-boundary :delay (m flip r0)) - (sll faces-0 faces-0 2) - (sll faces-1 faces-1 2) - (add faces-0 faces-0 double-tris) - (add faces-1 faces-1 double-tris) - (nop!) - (l.bu faces-0 (-> tri-a faces)) - (nop!) - (l.bu faces-1 (-> tri-b faces)) - (b.eq faces-0 faces-1 double-edge-next :delay (nop!)) - ;; The two records get complementary flags, so the two sheets wind away from each other. - (sltiu faces-0 faces-0 1) - (nop!) - (sltu faces-1 r0 faces-1) - (nop!) - (nop!) - (s.h faces-0 list 2) - (sub faces-0 edge edge-table) - (s.h faces-1 list 6) - (nop!) - (s.h faces-0 list) - (nop!) - (s.h faces-0 list 4) - (b double-edge-next :delay (add.i list list 8))) - ;; One face only: keep the edge unconditionally, oriented by whether that face was reversed. - (asm-block boundary-edge - (label double-edge-boundary) - (sll faces-1 faces-0 2) - (nop!) - (add faces-1 faces-1 double-tris) - (nop!) - (nop!) - (l.bu faces-1 (-> tri-b faces)) - (nop!) - (nop!) - (nop!) - (sltiu faces-1 faces-1 1) - (sub faces-0 edge edge-table) - (s.h faces-1 list 2) - (s.h faces-0 list) - (add.i list list 4)) - (asm-block next-edge - (label double-edge-next) - (b.nz count double-edge-loop :delay (add.i edge edge (type-size shadow-edge)))) - (asm-block publish-double-edge-list - (label double-edges-done) - (sub edge-table list list-base) - (sra edge-table edge-table 2) - (s.w edge-table (-> work num-double-edges)) - (s.w list (-> work dcache-top)) - (m result r0) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-add-verts () - "Append the two vertex uploads -- the skinned mesh and its duals -- to the output packet." - (declare (asm-func none) (allow-saved-regs)) - ;; Both uploads are plain V4-32 unpacks, one landing at SHADOW-VU1-VERTS and one at - ;; SHADOW-VU1-DUAL-VERTS. The two areas are the same size and adjacent, which is what lets the - ;; silhouette-quad path reach a vertex and its dual from one index register. - ;; - ;; The first upload carries the template's FLUSHA, so VU1 is finished with the previous caster's - ;; vertices before this one's overwrite them. The second clears vif0, since by then the wait has - ;; already happened. - ;; - ;; Each copy moves four quadwords per iteration and finishes with four separately branched single - ;; quadwords instead of a loop: a shadow mesh is small enough that the remainder is often most of - ;; it, and a taken branch per quadword beats a loop's bookkeeping at these lengths. - (rlet ((geo :reg a0 :type shadow-header) - (vertex-count :reg a0) - (work :reg a1 :type shadow-dcache) - ;; a1 becomes the base of the second unpack once the workspace has been read. - (dual-packet :reg a1) - (packet :reg a2) - ;; The same cursor, while the DMA and VIF tag quadword is being written through it. - (tag :reg a2 :type dma-packet) - (dual-dest :reg a2) - (shadow-state :reg v1 :type shadow-data) - (vertex-bytes :reg v1) - (template :reg a3) - (dual-source :reg a3) - (result :reg v0)) - ;; t0 through t6 are the copy cursors and quadword shuttles. Their roles differ between the two - ;; copies, so they keep physical names and each block says what they hold. - (asm-block upload-skinned-vertices - ;; t0 is the VU destination and then the countdown, t1 the output cursor, t2 the source, and - ;; t3 through t6 the quadwords in flight. - (m! shadow-state *shadow-data*) - (m shadow-state shadow-state) - (l.h vertex-count (-> geo num-verts)) - (add.i t0 r0 SHADOW-VU1-VERTS) - (l.q template (-> shadow-state dma-unpack-template)) - (nop!) - (s.q template packet) - (s.h vertex-count packet) - (s.b vertex-count packet 14) - (s.h t0 packet 12) - (add.i packet packet 16) - (l.w t2 (-> work vtx-table)) - (sll vertex-bytes vertex-count 4) - (m t1 packet) - (m t0 vertex-count) - (add.i t3 t0 -4) - (m t1 t1) - (b.lt t3 r0 copy-vertex-tail :delay (m t2 t2)) - (label copy-four-vertices) - (nop!) - (l.q t6 t2) - (nop!) - (l.q t3 t2 16) - (add.i t0 t0 -4) - (l.q t4 t2 32) - (add.i t1 t1 64) - (l.q t5 t2 48) - (add.i t2 t2 64) - (s.q t6 t1 -64) - (add.i t6 t0 -4) - (s.q t3 t1 -48) - (nop!) - (s.q t4 t1 -32) - (b.ge t6 r0 copy-four-vertices :delay (s.q t5 t1 -16)) - (label copy-vertex-tail) - (b.z t0 vertices-copied :delay (l.q t3 t2)) - (add.i t2 t2 16) - (add.i t1 t1 16) - (add.i t0 t0 -1) - (s.q t3 t1 -16) - (b.z t0 vertices-copied :delay (l.q t3 t2)) - (add.i t2 t2 16) - (add.i t1 t1 16) - (add.i t0 t0 -1) - (s.q t3 t1 -16) - (b.z t0 vertices-copied :delay (l.q t3 t2)) - (add.i t2 t2 16) - (add.i t1 t1 16) - (add.i t0 t0 -1) - (s.q t3 t1 -16) - (b.z t0 vertices-copied :delay (l.q t3 t2)) - (add.i t2 t2 16) - (add.i t1 t1 16) - (add.i t0 t0 -1) - (s.q t3 t1 -16)) - (asm-block upload-dual-vertices - ;; Same shape, one register to the left: t0 through t3 are the quadwords, the cursors moved - ;; into a2 and a3. - (label vertices-copied) - (m t0 r0) - (add packet packet vertex-bytes) - (add.i t0 r0 SHADOW-VU1-DUAL-VERTS) - (s.q template packet) - (s.h vertex-count packet) - (s.b vertex-count packet 14) - (s.w r0 (-> tag vif0)) - (s.h t0 packet 12) - (l.w dual-source (-> work ptr-dual-verts)) - (add.i dual-packet packet 16) - (m dual-dest dual-packet) - (nop!) - (add.i t0 vertex-count -4) - (m dual-dest dual-dest) - (b.lt t0 r0 copy-dual-tail :delay (m dual-source dual-source)) - (label copy-four-duals) - (nop!) - (l.q t3 dual-source) - (nop!) - (l.q t0 dual-source 16) - (add.i vertex-count vertex-count -4) - (l.q t1 dual-source 32) - (add.i dual-dest dual-dest 64) - (l.q t2 dual-source 48) - (add.i dual-source dual-source 64) - (s.q t3 dual-dest -64) - (add.i t3 vertex-count -4) - (s.q t0 dual-dest -48) - (nop!) - (s.q t1 dual-dest -32) - (b.ge t3 r0 copy-four-duals :delay (s.q t2 dual-dest -16)) - (label copy-dual-tail) - (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) - (add.i dual-source dual-source 16) - (add.i dual-dest dual-dest 16) - (add.i vertex-count vertex-count -1) - (s.q t0 dual-dest -16) - (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) - (add.i dual-source dual-source 16) - (add.i dual-dest dual-dest 16) - (add.i vertex-count vertex-count -1) - (s.q t0 dual-dest -16) - (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) - (add.i dual-source dual-source 16) - (add.i dual-dest dual-dest 16) - (add.i vertex-count vertex-count -1) - (s.q t0 dual-dest -16) - (b.z vertex-count duals-copied :delay (l.q t0 dual-source)) - (add.i dual-source dual-source 16) - (add.i dual-dest dual-dest 16) - (add.i vertex-count vertex-count -1) - (s.q t0 dual-dest -16)) - (asm-block return - (label duals-copied) - (m vertex-count r0) - (add result dual-packet vertex-bytes) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!))) - ) - - (defun shadow-add-facing-single-tris () - "Append the single-sided cap list and the MSCALF that draws it." - (declare (asm-func none) (allow-saved-regs)) - ;; The list VU1 reads is a count quadword followed by one four-byte record per primitive, unpacked - ;; as unsigned V4-8 so that each record arrives as four separate integer lanes. A cap record is the - ;; triangle's own word: three vertex indices and the faces byte the classifier wrote. - ;; - ;; The record area is rounded up to a whole quadword and the last quadword is zeroed before the - ;; records go in, so a partially filled tail cannot present VU1 with stale indices. - (rlet ((shadow-state :reg v1 :type shadow-data) - (count :reg v1) - (work :reg a1 :type shadow-dcache) - (record-count :reg a1) - (list :reg a0) - (packet :reg a2) - (tag :reg a2 :type dma-packet) - (state :reg a3 :type shadow-data) - (tri :reg a3 :type shadow-tri) - (records :reg t0) - (qwc :reg t0) - (unpack-imm :reg t0) - (dma-tag :reg t1) - (list-end :reg v1) - (result :reg v0)) - (asm-block size-the-upload - (m! shadow-state *shadow-data*) - (m state shadow-state) - (l.w count (-> work num-facing-single-tris)) - (l.w list (-> work single-tri-list)) - ;; One record for the count itself, then round up to a quadword. - (b.z count no-single-tris :delay (add.i record-count count 1)) - (add.i record-count record-count 3) - (sra qwc record-count 2) - (sll record-count qwc 2) - (add.i qwc qwc 1) - (l.d dma-tag (-> state dma-cnt)) - (add qwc dma-tag qwc) - (l.w state (-> state vif-unpack-v4-8)) - (s.d qwc packet) - (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-TRI-LIST)) - (s.w r0 (-> tag vif0)) - (s.w state (-> tag vif1)) - (s.b record-count packet 14) - (sll record-count record-count 2) - (s.h unpack-imm packet 12) - (add.i packet packet 16) - (add record-count packet record-count) - ;; Clear the tail quadword before anything real is written into it. - (s.q r0 record-count -16) - (s.w count packet) - (add.i packet packet 4)) - (asm-block copy-cap-records - (label copy-single-tri-loop) - (l.w tri list) - (add.i list list 4) - (l.w tri tri) - (add.i count count -1) - (s.w tri packet) - (add.i packet packet 4) - (b.gt count r0 copy-single-tri-loop :delay (nop!))) - (asm-block start-vu1 - (m list-end record-count) - (lui list VIF-MSCALF-WORD) - (ori list list SHADOW-VU1-ENTRY-SINGLE-TRIS) - (s.q r0 list-end) - (s.w list list-end 12) - (add.i packet list-end 16)) - (asm-block return - (label no-single-tris) - (m result packet) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-add-single-edges () - "Append the single-sided silhouette list and the MSCALF that draws its quads." - (declare (asm-func none) (allow-saved-regs)) - ;; A silhouette record is two vertex indices and an orientation flag. The two indices are lifted - ;; straight out of the source edge as one halfword -- shadow-edge keeps ind-0 and ind-1 adjacent - ;; for exactly this -- and the flag follows in the third byte. VU1 turns each record into the quad - ;; running from those two vertices to their two duals. - (rlet ((shadow-state :reg v1 :type shadow-data) - (count :reg v1) - (work :reg a1 :type shadow-dcache) - (edge-table :reg a1) - (list :reg a0) - (packet :reg a2) - (tag :reg a2 :type dma-packet) - (state :reg a3 :type shadow-data) - (record-bytes :reg a3) - (record :reg a3) - (record-count :reg t0) - (records :reg t0) - (qwc :reg t1) - (dma-tag :reg t2) - (unpack-imm :reg t1) - (indices :reg t1) - (flip :reg t0) - (list-end :reg v1) - (result :reg v0)) - (asm-block size-the-upload - (m! shadow-state *shadow-data*) - (m state shadow-state) - (l.w count (-> work num-single-edges)) - (l.w list (-> work single-edge-list)) - (l.w edge-table (-> work single-edge-table)) - (b.z count no-single-edges :delay (add.i record-count count 1)) - (add.i record-count record-count 3) - (sra qwc record-count 2) - (sll record-count qwc 2) - (add.i qwc qwc 1) - (l.d dma-tag (-> state dma-cnt)) - (add qwc dma-tag qwc) - (l.w state (-> state vif-unpack-v4-8)) - (s.d qwc packet) - (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-EDGE-LIST)) - (s.w r0 (-> tag vif0)) - (s.w state (-> tag vif1)) - (s.b record-count packet 14) - (sll record-bytes record-count 2) - (s.h unpack-imm packet 12) - (add.i records packet 16) - (add packet records record-bytes) - (s.q r0 packet -16) - (s.w count records) - (add.i record records 4)) - (asm-block copy-silhouette-records - (label copy-single-edge-loop) - (l.h indices list) - (l.h flip list 2) - (add.i list list 4) - (add indices indices edge-table) - ;; ind-0 and ind-1 travel together as one halfword. - (l.hu indices indices) - (s.h flip record 2) - (s.h indices record) - (add.i count count -1) - (add.i record record 4) - (b.gt count r0 copy-single-edge-loop :delay (nop!))) - (asm-block start-vu1 - (m list-end packet) - (lui list VIF-MSCALF-WORD) - (ori list list SHADOW-VU1-ENTRY-EDGES) - (s.q r0 list-end) - (s.w list list-end 12) - (add.i packet list-end 16)) - (asm-block return - (label no-single-edges) - (m result packet) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-add-double-tris () - "Append the double-sided cap list and the MSCALF that draws it." - (declare (asm-func none) (allow-saved-regs)) - ;; Every double-sided triangle is emitted, so the count comes straight from the header rather than - ;; from a collected list, and the records are read out of the classifier's table in order. The - ;; faces byte in each record tells VU1 which way to wind the cap. - (rlet ((shadow-state :reg v1 :type shadow-data) - (count :reg v1) - (geo :reg a0 :type shadow-header) - (work :reg a1 :type shadow-dcache) - (record-count :reg a1) - (tris :reg a0) - (packet :reg a2) - (tag :reg a2 :type dma-packet) - (state :reg a3 :type shadow-data) - (tri :reg a3) - (qwc :reg t0) - (unpack-imm :reg t0) - (dma-tag :reg t1) - (list-end :reg v1) - (result :reg v0)) - (asm-block size-the-upload - (m! shadow-state *shadow-data*) - (m state shadow-state) - (l.h count (-> geo num-double-tris)) - (l.w tris (-> work double-tri-table)) - (b.z count no-double-tris :delay (add.i record-count count 1)) - (add.i record-count record-count 3) - (sra qwc record-count 2) - (sll record-count qwc 2) - (add.i qwc qwc 1) - (l.d dma-tag (-> state dma-cnt)) - (add qwc dma-tag qwc) - (l.w state (-> state vif-unpack-v4-8)) - (s.d qwc packet) - (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-TRI-LIST)) - (s.w r0 (-> tag vif0)) - (s.w state (-> tag vif1)) - (s.b record-count packet 14) - (sll record-count record-count 2) - (s.h unpack-imm packet 12) - (add.i packet packet 16) - (add record-count packet record-count) - (s.q r0 record-count -16) - (s.w count packet) - (add.i packet packet 4)) - (asm-block copy-cap-records - (label copy-double-tri-loop) - (l.w tri tris) - (add.i tris tris 4) - (add.i count count -1) - (s.w tri packet) - (add.i packet packet 4) - (b.gt count r0 copy-double-tri-loop :delay (nop!))) - (asm-block start-vu1 - (m list-end record-count) - (lui tris VIF-MSCALF-WORD) - (ori tris tris SHADOW-VU1-ENTRY-DOUBLE-TRIS) - (s.q r0 list-end) - (s.w tris list-end 12) - (add.i packet list-end 16)) - (asm-block return - (label no-double-tris) - (m result packet) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!))) - ) - - (defun shadow-add-double-edges () - "Append the double-sided silhouette list and the MSCALF that draws its quads." - (declare (asm-func none) (allow-saved-regs)) - ;; Identical in form to the single-sided silhouette list, and it starts the same VU1 entry: by this - ;; point a silhouette record has forgotten which kind of mesh it came from. The only difference is - ;; the table it indexes and the fact that an interior edge contributed two records. - (rlet ((shadow-state :reg v1 :type shadow-data) - (count :reg v1) - (work :reg a1 :type shadow-dcache) - (edge-table :reg a1) - (list :reg a0) - (packet :reg a2) - (tag :reg a2 :type dma-packet) - (state :reg a3 :type shadow-data) - (record-bytes :reg a3) - (record :reg a3) - (record-count :reg t0) - (records :reg t0) - (qwc :reg t1) - (dma-tag :reg t2) - (unpack-imm :reg t1) - (indices :reg t1) - (flip :reg t0) - (list-end :reg v1) - (result :reg v0)) - (asm-block size-the-upload - (m! shadow-state *shadow-data*) - (m state shadow-state) - (l.w count (-> work num-double-edges)) - (l.w list (-> work double-edge-list)) - (l.w edge-table (-> work double-edge-table)) - (b.z count no-double-edges :delay (add.i record-count count 1)) - (add.i record-count record-count 3) - (sra qwc record-count 2) - (sll record-count qwc 2) - (add.i qwc qwc 1) - (l.d dma-tag (-> state dma-cnt)) - (add qwc dma-tag qwc) - (l.w state (-> state vif-unpack-v4-8)) - (s.d qwc packet) - (add.i unpack-imm r0 (logior VIF-UNPACK-USN SHADOW-VU1-EDGE-LIST)) - (s.w r0 (-> tag vif0)) - (s.w state (-> tag vif1)) - (s.b record-count packet 14) - (sll record-bytes record-count 2) - (s.h unpack-imm packet 12) - (add.i records packet 16) - (add packet records record-bytes) - (s.q r0 packet -16) - (s.w count records) - (add.i record records 4)) - (asm-block copy-silhouette-records - (label copy-double-edge-loop) - (l.h indices list) - (l.h flip list 2) - (add.i list list 4) - (add indices indices edge-table) - (l.hu indices indices) - (s.h flip record 2) - (s.h indices record) - (add.i count count -1) - (add.i record record 4) - (b.gt count r0 copy-double-edge-loop :delay (nop!))) - (asm-block start-vu1 - (m list-end packet) - (lui list VIF-MSCALF-WORD) - (ori list list SHADOW-VU1-ENTRY-EDGES) - (s.q r0 list-end) - (s.w list list-end 12) - (add.i packet list-end 16)) - (asm-block return - (label no-double-edges) - (m result packet) - (jr ra :delay (add sp sp r0)) - (nop!) - (nop!) - (nop!))) - ) - - (defun shadow-execute () - "Draw every caster in one shadow-queue run, appending their VIF packets to the output pointer and - returning where it ended up." - (declare (asm-func none) (allow-saved-regs)) - ;; One pass over the run's chain. Each caster's settings, geometry, and bone matrices come into the - ;; scratchpad in a single toSPR source chain; the whole per-caster pipeline then runs out of the - ;; scratchpad and the shared workspace, and the four VIF packets go straight into the frame's DMA - ;; buffer. The chain's next link is read out of the tag quadword that toSPR itself deposited, so - ;; walking the list costs nothing extra. - ;; - ;; The cache is written back once up front, before the first transfer, because the packets that were - ;; just built are still sitting in the data cache and the DMA controller does not see it. - (rlet ((caster :reg s4 :type shadow-dma-packet) - ;; The same register addresses the copy that landed in the scratchpad. - (geo :reg s4 :type shadow-header) - (work :reg s3 :type shadow-dcache) - (settings :reg s2 :type shadow-settings) - (stall-count :reg s5) - (output :reg gp) - (culled? :reg s1) - (scratch-base :reg at) - ;; The function's own address, kept so the two float constants below it can be reached. - (code-base :reg fp) - (to-spr :reg v1 :type dma-bank-spr) - (to-spr-poll :reg a1 :type dma-bank-spr) - (camera :reg v1 :type math-camera) - (cam-pos :reg v1 :type vector) - ;; The scratchpad destination, which then doubles as the value written to chcr. - (spr-dest :reg a0) - (result :reg v0)) - (asm-block enter-shadow-pass - (add.i sp sp -112) - (s.d ra sp) - (s.d fp sp 8) - (m code-base t9) - (s.q s1 sp 16) - (s.q s2 sp 32) - (s.q s3 sp 48) - (s.q s4 sp 64) - (s.q s5 sp 80) - (s.q gp sp 96) - (m output a1) - (m caster a0) - (add.i v1 r0 0) - (lui v1 #x7000) - (add.i stall-count v1 (shadow-spr-offset shadow-data waits)) - (m! t9 flush-cache) - (add.i a0 r0 0) - (jalr ra t9 :delay (sll v0 ra 0)) - (b test-next-caster :delay (nop!))) - ;; Pull one caster into the scratchpad. The first wait keeps the channel from being reprogrammed - ;; while it is still running and is not counted; the second and third are. - (asm-block upload-caster - (label next-caster) - (lui v1 #x1000) - (ori v1 v1 #xd400) - (add.i spr-dest r0 SHADOW-SPR-INPUT) - (m to-spr-poll to-spr) - (label wait-for-idle) - (l.w a2 (-> to-spr-poll chcr)) - (nop! :count 3) - (and.i a2 a2 DMA-CHCR-STR) - (nop!) - (b.z a2 to-spr-idle :delay (nop!)) - (nop! :count 10) - (b wait-for-idle :delay (nop!)) - (label to-spr-idle) - (m a1 r0) - (m to-spr-poll to-spr) - (m a2 stall-count) - (shadow-wait-counting-stalls! a3 t0 to-spr-poll a2 count-idle-stall input-idle) - (m a1 r0) - (s.w spr-dest (-> to-spr sadr)) - (s.w caster (-> to-spr tadr)) - (s.w r0 (-> to-spr qwc)) - (sync.l) - (add.i spr-dest r0 DMA-CHCR-START-SPR-CHAIN) - (s.w spr-dest (-> to-spr chcr)) - (sync.l) - (m spr-dest stall-count) - (shadow-wait-counting-stalls! a1 a2 to-spr spr-dest count-upload-stall input-ready) - (m v1 r0)) - ;; The settings and the geometry are now at fixed scratchpad addresses. - (asm-block bind-caster - (lui at #x7000) - (add.i v1 r0 SHADOW-SPR-GEO) - (add geo v1 scratch-base) - (m! work *gsf-buffer*) - (add.i settings scratch-base SHADOW-SPR-SETTINGS) - (nop!) - (l.w v1 (-> settings flags)) - (and.i v1 v1 (shadow-flags disable-draw)) - (b.nz v1 advance-chain :delay (m v1 s7))) - ;; locus = center + shadow-dir * dist-to-locus. Everything projects away from this point. - (asm-block place-light-locus - (l.s f0 (-> settings center x)) - (l.s f1 (-> settings shadow-dir x)) - (l.s f2 (-> settings dist-to-locus)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (s.s f0 (-> work center x)) - (l.s f0 (-> settings center y)) - (l.s f1 (-> settings shadow-dir y)) - (l.s f2 (-> settings dist-to-locus)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (s.s f0 (-> work center y)) - (l.s f0 (-> settings center z)) - (l.s f1 (-> settings shadow-dir z)) - (l.s f2 (-> settings dist-to-locus)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (s.s f0 (-> work center z)) - (l.s f0 code-base shadow-one) - (s.s f0 (-> work center w))) - ;; A caster that authored its planes in world space gets them verbatim; otherwise they are - ;; written relative to the shadow joint and have to be lowered by its height here. - (asm-block place-clipping-planes - (l.w v1 (-> settings flags)) - (and.i v1 v1 (shadow-flags world-space-planes)) - (b.z v1 offset-planes-by-center :delay (nop!)) - (l.q v1 (-> settings bot-plane)) - (s.q v1 (-> work plane)) - (l.q v1 (-> settings top-plane)) - (s.q v1 (-> work top-plane)) - (b planes-ready :delay (nop!)) - (label offset-planes-by-center) - (l.s f0 (-> settings bot-plane x)) - (s.s f0 (-> work plane x)) - (l.s f0 (-> settings bot-plane y)) - (s.s f0 (-> work plane y)) - (l.s f0 (-> settings bot-plane z)) - (s.s f0 (-> work plane z)) - (l.s f0 (-> settings bot-plane w)) - (l.s f1 (-> settings center y)) - (sub.s f0 f0 f1) - (s.s f0 (-> work plane w)) - (l.s f0 (-> settings top-plane x)) - (s.s f0 (-> work top-plane x)) - (l.s f0 (-> settings top-plane y)) - (s.s f0 (-> work top-plane y)) - (l.s f0 (-> settings top-plane z)) - (s.s f0 (-> work top-plane z)) - (l.s f0 (-> settings top-plane w)) - (l.s f1 (-> settings center y)) - (sub.s f0 f0 f1) - (s.s f0 (-> work top-plane w)) - (m v1 f0)) - ;; With camera-cull set, a camera below the ground plane means the volume would be seen from - ;; inside and the counting would come out inverted, so the whole caster is dropped. - (asm-block cull-against-ground-plane - (label planes-ready) - (m culled? s7) - (l.w v1 (-> settings flags)) - (and.i v1 v1 (shadow-flags camera-cull)) - (b.z v1 culling-done :delay (m v1 s7)) - (m! t9 camera-pos) - (jalr ra t9 :delay (sll v0 ra 0)) - (m v1 v0) - (l.s f0 (-> cam-pos x)) - (l.s f1 (-> work plane x)) - (mul.s f0 f0 f1) - (l.s f1 (-> cam-pos y)) - (l.s f2 (-> work plane y)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (l.s f1 (-> cam-pos z)) - (l.s f2 (-> work plane z)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (l.s f1 (-> work plane w)) - (add.s f0 f0 f1) - (m f1 r0) - (c.lt.s f0 f1) - (b.fpf culling-done :delay (m v1 s7)) - (add.i culled? s7 #t) - (m v1 culled?) - (label culling-done) - (b.ne s7 culled? advance-chain :delay (m v1 s7))) - ;; If the light itself has ended up at or below the ground plane the projection would run the - ;; wrong way, so the plane is moved to pass exactly through the locus. The volume collapses to - ;; nothing rather than turning inside out. - (asm-block keep-locus-above-plane - (l.s f0 (-> work center x)) - (l.s f1 (-> work plane x)) - (mul.s f0 f0 f1) - (l.s f1 (-> work center y)) - (l.s f2 (-> work plane y)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (l.s f1 (-> work center z)) - (l.s f2 (-> work plane z)) - (mul.s f1 f1 f2) - (add.s f0 f0 f1) - (m f1 r0) - (l.s f2 (-> work plane w)) - (add.s f2 f0 f2) - (c.lt.s f1 f2) - (b.fpf locus-checked :delay (m v1 s7)) - (neg.s f0 f0) - (s.s f0 (-> work plane w)) - (m v1 f0)) - ;; near-plane is (0, 0, 1, -2d): the camera-space plane at twice the near-plane distance. Nothing - ;; here needs a rotation, so it is built directly rather than transformed with the others. - (asm-block set-up-workspace - (label locus-checked) - (l.q v1 (-> settings shadow-dir)) - (s.q v1 (-> work light-dir)) - (m f0 r0) - (s.s f0 (-> work near-plane x)) - (m f0 r0) - (s.s f0 (-> work near-plane y)) - (l.s f0 code-base shadow-one) - (s.s f0 (-> work near-plane z)) - (l.s f0 code-base shadow-negative-two) - (m! v1 *math-camera*) - (l.s f1 (-> camera d)) - (mul.s f0 f0 f1) - (s.s f0 (-> work near-plane w)) - (add.i v1 work (offset-of shadow-dcache data)) - (s.w v1 (-> work dcache-top))) - ;; Skin, move the projection into camera space, and build the duals. - (asm-block build-shadow-volume - (m! t9 shadow-xform-verts) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 shadow-init-vars) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 shadow-calc-dual-verts) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (l.w v1 (-> settings flags)) - (and.i v1 v1 (shadow-flags scissor-top)) - (b.z v1 volume-clipped :delay (m v1 s7)) - (m! t9 shadow-scissor-top) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m v1 v0) - (label volume-clipped) - (m! t9 shadow-scissor-edges) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0))) - ;; Classify both kinds of face and collect both silhouettes. - (asm-block find-silhouettes - (m! t9 shadow-find-facing-single-tris) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 shadow-find-single-edges) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 shadow-find-facing-double-tris) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m! t9 shadow-find-double-edges) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0))) - ;; The vertices have to be uploaded before any list that indexes them, and each builder returns - ;; the advanced output pointer for the next. - (asm-block append-vif-packets - (m! t9 shadow-add-verts) - (m a0 geo) - (m a1 work) - (m a2 output) - (jalr ra t9 :delay (sll v0 ra 0)) - (m a2 v0) - (m! t9 shadow-add-facing-single-tris) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m a2 v0) - (m! t9 shadow-add-single-edges) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m a2 v0) - (m! t9 shadow-add-double-tris) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m a2 v0) - (m! t9 shadow-add-double-edges) - (m a0 geo) - (m a1 work) - (jalr ra t9 :delay (sll v0 ra 0)) - (m output v0) - (m v1 output)) - ;; toSPR deposited the packet's own tag quadword ahead of the settings, so the next caster's - ;; address is already in the scratchpad. A culled caster lands here too and the chain goes on. - (asm-block advance-chain - (label advance-chain) - (l.w caster scratch-base (+ SHADOW-SPR-INPUT (offset-of generic-merc-tag next-ptr))) - (label test-next-caster) - (b.nz caster next-caster :delay (nop!)) - (m v1 s7)) - (asm-block return - (m result output) - (l.d ra sp) - (l.d fp sp 8) - (l.q gp sp 96) - (l.q s5 sp 80) - (l.q s4 sp 64) - (l.q s3 sp 48) - (l.q s2 sp 32) - (l.q s1 sp 16) - (jr ra :delay (add.i sp sp 112)) - (nop!) - (nop!))) - ) - - ) - (defun shadow-vu0-upload () "Upload the shadow face-classification and dual-vertex VU0 program." (#unless PC_PORT @@ -2910,35 +590,37 @@ (defun shadow-execute-all ((dma-buf dma-buffer) (queue shadow-queue)) "Build the frame shadow pass for every nonempty queued run, bracket it with alpha initialization and resolve packets, and insert the completed chain into the shadow bucket." - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) + (add-profile-frame! #x40 0 #x40 #x80 'draw) (when (logtest? (vu1-renderer-mask shadow) *vu1-enable-user*) (when (> (-> queue cur-run) 0) (let ((has-shadow? #f)) (dotimes (i (the-as int (-> queue cur-run))) - (if (nonzero? (-> queue run i first)) (set! has-shadow? #t))) + (if (nonzero? (-> queue run i first)) + (set! has-shadow? #t))) (when has-shadow? (shadow-vu0-upload) - (with-dma-buffer-add-bucket ((global-buf (-> *display* frames (-> *display* on-screen) frame global-buf)) (bucket-id shadow)) :bucket-group (-> *display* frames (-> *display* on-screen) frame bucket-group) (shadow-vu1-init-buffer global-buf) (flush-cache 0) (let ((workspace (the-as shadow-dcache *gsf-buffer*))) - (set! (-> workspace stats num-single-tris) (the-as uint 0)) - (set! (-> workspace stats num-double-tris) (the-as uint 0)) - (set! (-> workspace stats num-single-edges) (the-as uint 0)) - (set! (-> workspace stats num-double-edges) (the-as uint 0))) 0 (shadow-dma-init global-buf) (dotimes (i (the-as int (-> queue cur-run))) - (let ((run (-> queue run i))) - (if (nonzero? (-> run first)) - (set! (-> global-buf base) (shadow-execute (the-as shadow-dma-packet (-> run first)) (-> global-buf base)))))) (shadow-dma-end global-buf)))) + (with-dma-buffer-add-bucket ((global-buf (-> (current-frame) global-buf)) (bucket-id shadow)) + (shadow-vu1-init-buffer global-buf) + (flush-cache 0) + (let ((workspace (the-as shadow-dcache *gsf-buffer*))) + (set! (-> workspace stats num-single-tris) (the-as uint 0)) + (set! (-> workspace stats num-double-tris) (the-as uint 0)) + (set! (-> workspace stats num-single-edges) (the-as uint 0)) + (set! (-> workspace stats num-double-edges) (the-as uint 0))) + 0 + (shadow-dma-init global-buf) + (dotimes (i (the-as int (-> queue cur-run))) + (let ((run (-> queue run i))) + (if (nonzero? (-> run first)) + (set! (-> global-buf base) (shadow-execute (the-as shadow-dma-packet (-> run first)) (-> global-buf base)))))) + (shadow-dma-end global-buf)))) (when #f - (let ((workspace (the-as structure *gsf-buffer*))) - (format *stdcon* "#single tris : ~4d~%" (-> (the-as shadow-dcache workspace) stats num-single-tris)) - (format *stdcon* "#double tris : ~4d~%" (-> (the-as shadow-dcache workspace) stats num-double-tris)) - (format *stdcon* "#single edges: ~4d~%" (-> (the-as shadow-dcache workspace) stats num-single-edges)) - (format *stdcon* "#double edges: ~4d~%" (-> (the-as shadow-dcache workspace) stats num-double-edges)))))) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :g #x42 :b #xe0 :a #x80))) + (let ((workspace (the-as shadow-dcache *gsf-buffer*))) + (format *stdcon* "#single tris : ~4d~%" (-> workspace stats num-single-tris)) + (format *stdcon* "#double tris : ~4d~%" (-> workspace stats num-double-tris)) + (format *stdcon* "#single edges: ~4d~%" (-> workspace stats num-single-edges)) + (format *stdcon* "#double edges: ~4d~%" (-> workspace stats num-double-edges)))))) + (add-profile-frame! #x40 #x42 #xe0 #x80 'draw) 0 (none)) diff --git a/goal_src/jak1/engine/gfx/shadow/shadow-vu1.gc b/goal_src/jak1/engine/gfx/shadow/shadow-vu1.gc index 321cbaed1b..594aa26dac 100644 --- a/goal_src/jak1/engine/gfx/shadow/shadow-vu1.gc +++ b/goal_src/jak1/engine/gfx/shadow/shadow-vu1.gc @@ -3,6 +3,10 @@ (bundles "ENGINE.CGO" "GAME.CGO") (require "engine/gfx/shadow/shadow-cpu.gc") +;; NOTE: this file is a bit of a mess now. +;; I'm planning to remove most of it and replace with PC shadow +;; renderer very soon. + ;; The perspective matrix goes in at the bottom of VU1 data memory, one row per quadword. (defconstant SHADOW-VU1-MATRIX 0) @@ -1084,49 +1088,48 @@ ) (defun shadow-vu1-add-constants ((dma-buf dma-buffer)) - "Upload the shadow projection, texture, fog, and color constants, then the four-quadword GIF tag block - the clipper writes its polygons into." + "Upload the shadow projection, texture, fog, color, and GIF constants plus the initial VU1 GIF + buffer template." (let* ((qwc 13) (packet-buffer dma-buf) - (upload-packet (the-as object (-> packet-buffer base)))) - (set! (-> (the-as dma-packet upload-packet) dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc qwc)) - (set! (-> (the-as dma-packet upload-packet) vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl))) - (set! (-> (the-as dma-packet upload-packet) vif1) (new 'static 'vif-tag :imm SHADOW-VU-CONSTS :cmd (vif-cmd unpack-v4-32) :num qwc)) + (upload-packet (the-as dma-packet (-> packet-buffer base)))) + (set! (-> upload-packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc qwc)) + (set! (-> upload-packet vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl))) + (set! (-> upload-packet vif1) (new 'static 'vif-tag :imm #x370 :cmd (vif-cmd unpack-v4-32) :num qwc)) (set! (-> packet-buffer base) (&+ (the-as pointer upload-packet) 16))) - (let ((constants (the-as object (-> dma-buf base))) + (let ((constants (the-as shadow-vu1-constants (-> dma-buf base))) (camera *math-camera*) (shadow-state *shadow-data*) (tri-template *shadow-vu1-tri-template*)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) hmgescale) (-> camera hmge-scale)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) invhscale) (-> camera inv-hmge-scale)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) texoffset) (-> shadow-state texoffset)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) texscale) (-> shadow-state texscale)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) hvdfoff) (-> camera hvdf-off)) - (set! (-> (the-as shadow-vu1-constants constants) fog x) (-> camera pfog0)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) clrs 0) (-> shadow-state clrs 0)) - (vector-copy! (-> (the-as shadow-vu1-constants constants) clrs 1) (-> shadow-state clrs 1)) - (set! (-> (the-as shadow-vu1-constants constants) adgif qword) (-> tri-template adgif qword)) - (set! (-> (the-as shadow-vu1-constants constants) texflush quad) (-> tri-template ad quad)) - (set! (-> (the-as shadow-vu1-constants constants) flush quad) (-> tri-template flush quad)) - (set! (-> (the-as shadow-vu1-constants constants) trigif qword) (-> tri-template trigif qword)) - (set! (-> (the-as shadow-vu1-constants constants) quadgif qword) (-> tri-template quadgif qword))) + (vector-copy! (-> constants hmgescale) (-> camera hmge-scale)) + (vector-copy! (-> constants invhscale) (-> camera inv-hmge-scale)) + (vector-copy! (-> constants texoffset) (-> shadow-state texoffset)) + (vector-copy! (-> constants texscale) (-> shadow-state texscale)) + (vector-copy! (-> constants hvdfoff) (-> camera hvdf-off)) + (set! (-> constants fog x) (-> camera pfog0)) + (vector-copy! (-> constants clrs 0) (-> shadow-state clrs 0)) + (vector-copy! (-> constants clrs 1) (-> shadow-state clrs 1)) + (set! (-> constants adgif qword) (-> tri-template adgif qword)) + (set! (-> constants texflush quad) (-> tri-template ad quad)) + (set! (-> constants flush quad) (-> tri-template flush quad)) + (set! (-> constants trigif qword) (-> tri-template trigif qword)) + (set! (-> constants quadgif qword) (-> tri-template quadgif qword))) (&+! (-> dma-buf base) 208) (let* ((gif-upload-buffer dma-buf) - (gif-upload (the-as object (-> gif-upload-buffer base)))) - (set! (-> (the-as dma-packet gif-upload) dma) (new 'static 'dma-tag :qwc #x4 :id (dma-tag-id cnt))) - (set! (-> (the-as dma-packet gif-upload) vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl))) - (set! (-> (the-as dma-packet gif-upload) vif1) (new 'static 'vif-tag :imm SHADOW-VU1-CLIP-GIF :num #x4 :cmd (vif-cmd unpack-v4-32))) + (gif-upload (the-as dma-packet (-> gif-upload-buffer base)))) + (set! (-> gif-upload dma) (new 'static 'dma-tag :qwc #x4 :id (dma-tag-id cnt))) + (set! (-> gif-upload vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl))) + (set! (-> gif-upload vif1) (new 'static 'vif-tag :imm #x3ac :num #x4 :cmd (vif-cmd unpack-v4-32))) (set! (-> gif-upload-buffer base) (&+ (the-as pointer gif-upload) 16))) (let* ((gif-template-buffer dma-buf) - (gif-template (the-as object (-> gif-template-buffer base)))) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) adgif tag) (new 'static 'gif-tag64 :nloop #x1 :nreg #x2)) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) adgif regs) - (new 'static 'gif-tag-regs :regs0 (gif-reg-id a+d) :regs1 (gif-reg-id rgbaq))) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) ad data) (the-as uint 0)) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) ad cmds) (the-as uint 63)) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) flush data) (the-as uint 0)) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) flush cmds) (the-as uint 0)) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) trigif tag) + (gif-template (the-as shadow-vu1-gifbuf-template (-> gif-template-buffer base)))) + (set! (-> gif-template adgif tag) (new 'static 'gif-tag64 :nloop #x1 :nreg #x2)) + (set! (-> gif-template adgif regs) (gs-reg-list a+d rgbaq)) + (set! (-> gif-template ad data) (the-as uint 0)) + (set! (-> gif-template ad cmds) (the-as uint 63)) + (set! (-> gif-template flush data) (the-as uint 0)) + (set! (-> gif-template flush cmds) (the-as uint 0)) + (set! (-> gif-template trigif tag) (new 'static 'gif-tag64 :nloop #x3 @@ -1135,8 +1138,7 @@ :prim (new 'static 'gs-prim :prim (gs-prim-type tri-fan) :tme #x1) :nreg #x2)) - (set! (-> (the-as shadow-vu1-gifbuf-template gif-template) trigif regs) - (new 'static 'gif-tag-regs :regs0 (gif-reg-id st) :regs1 (gif-reg-id xyzf2))) + (set! (-> gif-template trigif regs) (gs-reg-list st xyzf2)) (set! (-> gif-template-buffer base) (&+ (the-as pointer gif-template) 64))) (none)) @@ -1160,10 +1162,8 @@ (dma-buffer-add-vu-function dma-buf shadow-vu1-block 1) (shadow-vu1-add-constants dma-buf) (shadow-vu1-add-matrix dma-buf *math-camera*) - (let* ((packet-buffer dma-buf) - (start-packet (the-as object (-> packet-buffer base)))) - (set! (-> (the-as dma-packet start-packet) dma) (new 'static 'dma-tag :id (dma-tag-id cnt))) - (set! (-> (the-as dma-packet start-packet) vif0) (new 'static 'vif-tag :cmd (vif-cmd mscalf) :msk #x1 :imm SHADOW-VU1-ENTRY-INIT)) - (set! (-> (the-as dma-packet start-packet) vif1) (new 'static 'vif-tag :cmd (vif-cmd flushe) :msk #x1)) - (set! (-> packet-buffer base) (&+ (the-as pointer start-packet) 16))) + (dma-buffer-add-cnt-vif2 dma-buf + 0 + (new 'static 'vif-tag :cmd (vif-cmd mscalf) :msk #x1 :imm #xa) + (new 'static 'vif-tag :cmd (vif-cmd flushe) :msk #x1)) (none)) diff --git a/goal_src/jak1/engine/load/decomp.gc b/goal_src/jak1/engine/load/decomp.gc index c682f910e8..33a1556433 100644 --- a/goal_src/jak1/engine/load/decomp.gc +++ b/goal_src/jak1/engine/load/decomp.gc @@ -26,26 +26,30 @@ ;; The signed control byte selects a repeated or literal section. (set! control (-> src 0)) (set! src (&-> src 1)) - (b! (<= control 0) cfg-5 :delay (nop!)) + (b! (<= control 0) zero-or-neg :delay (nop!)) ;; A positive control stores the repeated run length minus one. (let ((repeated-value (-> src 0))) (set! src (&-> src 1)) - (label cfg-3) + (label write-repeated) (nop!) (nop!) (nop!) (nop!) (set! (-> dst 0) repeated-value)) (set! dst (&-> dst 1)) - (b! (> control 0) cfg-3 :delay (set! control (+ control -1)))) - (label cfg-5) + (b! (> control 0) write-repeated :delay (set! control (+ control -1)))) + (label zero-or-neg) ;; Zero ends the stream; a negative control introduces that many literal bytes. - (b! (zero? control) cfg-8 :delay (set! copy-length (- control))) - (label cfg-6) - (let ((literal-value (-> src 0))) (set! src (&-> src 1)) (nop!) (nop!) (set! (-> dst 0) literal-value)) + (b! (zero? control) end :delay (set! copy-length (- control))) + (label copy) + (let ((literal-value (-> src 0))) + (set! src (&-> src 1)) + (nop!) + (nop!) + (set! (-> dst 0) literal-value)) (+! copy-length -1) - (b! (> copy-length 0) cfg-6 :delay (set! dst (&-> dst 1)))) - (label cfg-8) + (b! (> copy-length 0) copy :delay (set! dst (&-> dst 1)))) + (label end) 0 (none)) diff --git a/test/common/test_demacro.cpp b/test/common/test_demacro.cpp index 2b29412abb..efda47c7e8 100644 --- a/test/common/test_demacro.cpp +++ b/test/common/test_demacro.cpp @@ -110,15 +110,14 @@ TEST(Demacro, ExpandsPatternTables) { ] } )RULES"); - const auto result = - demacro::rewrite("(begin (expanded-kind 0) (expanded-kind 1))", rules); + const auto result = demacro::rewrite("(begin (expanded-kind 0) (expanded-kind 1))", rules); EXPECT_EQ(result.rewrite_count(), 2); EXPECT_EQ(result.source, "(begin (kind first) (kind second))"); } TEST(Demacro, Jak1PreservesMemUsageNameSemantics) { - const auto rules = demacro::load_rules( - file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); + const auto rules = + demacro::load_rules(file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); const std::string source = R"((begin (set! (-> usage length) (max 1 (-> usage length))) (set! (-> usage data 0 name) "drawable-group") @@ -141,9 +140,65 @@ TEST(Demacro, Jak1PreservesMemUsageNameSemantics) { )"); } +TEST(Demacro, Jak1RecognizesPerfStatAndDmaMemUsageMacros) { + const auto rules = + demacro::load_rules(file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); + const std::string source = R"((begin + (let* ((stat (-> *perf-stats* data 1)) + (ctrl (-> stat ctrl))) + (+! (-> stat count) 1) + (b! (zero? ctrl) reset-done :delay (nop!)) + (.mtc0 Perf 0) + (.sync.l) + (.sync.p) + (.mtpc pcr0 0) + (.mtpc pcr1 0) + (.sync.l) + (.sync.p) + (.mtc0 Perf ctrl)) + (.sync.l) + (.sync.p) + (label reset-done) + 0 + (let ((stat (-> *perf-stats* data 1))) + (b! (zero? (-> stat ctrl)) read-done :delay (nop!)) + (.mtc0 Perf 0) + (.sync.l) + (.sync.p) + (.mfpc counter0 pcr0) + (+! (-> stat accum0) counter0) + (.mfpc counter1 pcr1) + (+! (-> stat accum1) counter1)) + (label read-done) + 0 + (let ((usage *dma-mem-usage*)) + (when (nonzero? usage) + (set! (-> usage length) (max 87 (-> usage length))) + (set! (-> usage data 86 name) "pris-generic") + (+! (-> usage data 86 count) 1) + (+! (-> usage data 86 used) bytes) + (set! (-> usage data 86 total) (-> usage data 86 used)))) + (let ((line (-> dma-buf base))) + (.sync.l) + (.cache dxwbin line 0) + (.sync.l) + (.cache dxwbin line 1)) + (.sync.l) + 0) +)"; + const auto result = demacro::rewrite(source, rules); + EXPECT_EQ(result.rewrite_count(), 4); + EXPECT_EQ(result.source, R"((begin + (reset! (-> *perf-stats* data 1)) + (read! (-> *perf-stats* data 1)) + (dma-mem-usage-add! pris-generic 1 bytes) + (invalidate-cache-line (-> dma-buf base))) +)"); +} + TEST(Demacro, Jak1RecognizesCachedEngineIteration) { - const auto rules = demacro::load_rules( - file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); + const auto rules = + demacro::load_rules(file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); const std::string source = R"((let ((node (-> *collide-player-list* alive-list next0))) *collide-player-list* (let ((next-node (-> node next0))) @@ -162,8 +217,8 @@ TEST(Demacro, Jak1RecognizesCachedEngineIteration) { } TEST(Demacro, Jak1RecognizesMergedCachedEngineIterations) { - const auto rules = demacro::load_rules( - file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); + const auto rules = + demacro::load_rules(file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); const std::string source = R"((begin (let ((node (-> first-engine alive-list next0))) first-engine @@ -210,8 +265,8 @@ TEST(Demacro, Jak1RecognizesMergedCachedEngineIterations) { } TEST(Demacro, Jak1RecognizesDmaBucketConstruction) { - const auto rules = demacro::load_rules( - file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); + const auto rules = + demacro::load_rules(file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); const std::string source = R"((let* ((buf (-> (current-frame) debug-buf)) (start (-> buf base))) ;; Keep the packet-building body. @@ -232,8 +287,8 @@ TEST(Demacro, Jak1RecognizesDmaBucketConstruction) { } TEST(Demacro, Jak1RecognizesInlinedFontEnumSetters) { - const auto rules = demacro::load_rules( - file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); + const auto rules = + demacro::load_rules(file_util::get_file_path({"decompiler/config/jak1/demacro.jsonc"})); const std::string source = R"((begin (set! (-> font flags) (font-flags shadow kerning large)) ;; Keep the color choice with its reconstructed call.