[jak3] Decompile `gcommon` (#3321)

Decompile `gcommon`. I adjusted the spacing of docstring comments, and
removed some spammy decompiler warning prints.

I also added some random notes I had on VU programs from jak1/jak2. They
are not polished, but I think it's still worth including since we'll
have to go through them again for jak 3.
pull/3323/head
water111 2024-01-20 12:33:39 -05:00 committed by GitHub
parent 9a4929ac0c
commit 1c0038294f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 4173 additions and 722 deletions

View File

@ -1428,7 +1428,9 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
f.ir2.env.disable_def(b2_delay_op.dst(), f.warnings);
f.ir2.env.disable_use(shift_left->expr().get_arg(0).var());
f.warnings.warning("Using new Jak 2 rtype-of");
if (f.ir2.env.version != GameVersion::Jak3) {
f.warnings.warning("Using new Jak 2 rtype-of");
}
return b0_ptr;
}

View File

@ -23,10 +23,27 @@ goos::Object get_arg_list_for_function(const Function& func, const Env& env) {
}
namespace {
std::string fix_docstring_indent(const std::string& input) {
std::string result;
for (auto c : input) {
if (c == '\n') {
result += '\n';
for (int i = 0; i < 3; i++) {
result += ' ';
}
} else {
result += c;
}
}
return result;
}
void append_body_to_function_definition(goos::Object* top_form,
const std::vector<goos::Object>& inline_body,
const FunctionVariableDefinitions& var_dec,
const TypeSpec& ts) {
const TypeSpec& ts,
GameVersion version) {
// Some forms like docstrings and local-vars we _always_ want to be at the top level and first (in
// the order added)
std::vector<goos::Object> initial_top_level_forms;
@ -35,7 +52,12 @@ void append_body_to_function_definition(goos::Object* top_form,
body_elements.insert(body_elements.end(), inline_body.begin(), inline_body.end());
// If the first element in the body is a docstring, add it first
if (body_elements.size() > 0 && body_elements.at(0).is_string()) {
initial_top_level_forms.push_back(inline_body.at(0));
if (version > GameVersion::Jak2) {
initial_top_level_forms.push_back(
goos::StringObject::make_new(fix_docstring_indent(inline_body.at(0).as_string()->data)));
} else {
initial_top_level_forms.push_back(inline_body.at(0));
}
body_elements.erase(body_elements.begin());
}
@ -65,7 +87,7 @@ void append_body_to_function_definition(goos::Object* top_form,
}
} // namespace
goos::Object final_output_lambda(const Function& func) {
goos::Object final_output_lambda(const Function& func, GameVersion version) {
std::vector<goos::Object> inline_body;
func.ir2.top_form->inline_forms(inline_body, func.ir2.env);
auto var_dec = func.ir2.env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1);
@ -74,11 +96,11 @@ goos::Object final_output_lambda(const Function& func) {
if (behavior) {
auto result = pretty_print::build_list(fmt::format("lambda :behavior {}", *behavior),
get_arg_list_for_function(func, func.ir2.env));
append_body_to_function_definition(&result, inline_body, var_dec, func.type);
append_body_to_function_definition(&result, inline_body, var_dec, func.type, version);
return result;
} else {
auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env));
append_body_to_function_definition(&result, inline_body, var_dec, func.type);
append_body_to_function_definition(&result, inline_body, var_dec, func.type, version);
return result;
}
}
@ -115,7 +137,7 @@ goos::Object final_output_defstate_anonymous_behavior(const Function& func,
auto var_dec = func.ir2.env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1);
auto result = pretty_print::build_list("behavior", get_arg_list_for_function(func, func.ir2.env));
append_body_to_function_definition(&result, inline_body, var_dec, func.type);
append_body_to_function_definition(&result, inline_body, var_dec, func.type, dts.version());
return result;
}
@ -167,7 +189,7 @@ std::string final_defun_out(const Function& func,
}
}
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version());
return pretty_print::to_string(top_form);
}
@ -188,7 +210,8 @@ std::string final_defun_out(const Function& func,
inline_body.insert(inline_body.begin(),
pretty_print::new_string(method_info.docstring.value()));
}
append_body_to_function_definition(&top_form, inline_body, var_dec, method_info.type);
append_body_to_function_definition(&top_form, inline_body, var_dec, method_info.type,
dts.version());
return pretty_print::to_string(top_form);
}
@ -199,7 +222,7 @@ std::string final_defun_out(const Function& func,
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version());
return pretty_print::to_string(top_form);
}
@ -212,7 +235,7 @@ std::string final_defun_out(const Function& func,
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version());
return pretty_print::to_string(top_form);
}
@ -226,7 +249,7 @@ std::string final_defun_out(const Function& func,
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type);
append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version());
return pretty_print::to_string(top_form);
}

View File

@ -18,7 +18,7 @@ std::string write_from_top_level(const Function& top_level,
const std::unordered_set<std::string>& skip_functions);
goos::Object get_arg_list_for_function(const Function& func, const Env& env);
goos::Object final_output_lambda(const Function& function);
goos::Object final_output_lambda(const Function& function, GameVersion version);
goos::Object final_output_defstate_anonymous_behavior(const Function& func,
const DecompilerTypeSystem& dts);
} // namespace decompiler

View File

@ -46,7 +46,7 @@ bool try_convert_lambda(const Function& parent_function,
if (defstate_behavior) {
result = final_output_defstate_anonymous_behavior(*other_func, dts);
} else {
result = final_output_lambda(*other_func);
result = final_output_lambda(*other_func, dts.version());
}
f->clear();

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,323 @@
{
"(method 0 inline-array-class)": {
"args": [
"allocation",
"type-to-make",
"count"
]
},
"(method 9 inline-array-class)": {
"args": [
"object-to-insert"
]
},
"(method 3 inline-array-class)": {
"args": [
"idx-to-remove"
]
},
"identity": {
"args": [
"obj"
]
},
"1/": {
"args": [
"x"
]
},
"+": {
"args": [
"a",
"b"
]
},
"-": {
"args": [
"a",
"b"
]
},
"*": {
"args": [
"a",
"b"
]
},
"/": {
"args": [
"a",
"b"
]
},
"ash": {
"args": [
"x",
"shift-amount"
]
},
"mod": {
"args": [
"a",
"b"
]
},
"rem": {
"args": [
"a",
"b"
]
},
"abs": {
"args": [
"x"
]
},
"min": {
"args": [
"a",
"b"
]
},
"max": {
"args": [
"a",
"b"
]
},
"logior": {
"args": [
"a",
"b"
]
},
"logand": {
"args": [
"a",
"b"
]
},
"lognor": {
"args": [
"a",
"b"
]
},
"logxor": {
"args": [
"a",
"b"
]
},
"lognot": {
"args": [
"x"
]
},
"basic-type?": {
"args": [
"obj",
"typ"
]
},
"type-type?": {
"args": [
"check-type",
"parent-type"
]
},
"type?": {
"args": [
"obj",
"desired-type"
]
},
"find-parent-method": {
"args": [
"typ",
"method-id"
]
},
"ref&": {
"args": [
"list",
"idx"
]
},
"ref": {
"args": [
"list",
"idx"
]
},
"last": {
"args": [
"list"
]
},
"member": {
"args": [
"obj-to-find",
"list"
]
},
"nmember": {
"args": [
"obj-to-find",
"list"
]
},
"assoc": {
"args": [
"key",
"assoc-list"
]
},
"assoce": {
"args": [
"key",
"assoc-list"
]
},
"nassoc": {
"args": [
"key",
"assoc-list"
]
},
"nassoce": {
"args": [
"key",
"assoc-list"
]
},
"append!": {
"args": [
"list",
"new-obj"
]
},
"delete!": {
"args": [
"obj",
"list"
]
},
"delete-car!": {
"args": [
"car-to-match",
"list"
]
},
"insert-cons!": {
"args": [
"new-obj",
"list"
]
},
"sort": {
"args": [
"list",
"compare-func"
]
},
"string->symbol-debug": {
"args": [
"str"
]
},
"symbol->string-debug": {
"args": [
"sym"
]
},
"symbol->hash": {
"args": [
"sym"
]
},
"mem-copy!": {
"args": [
"dst",
"src",
"bytes"
]
},
"qmem-copy<-!": {
"args": [
"dst",
"src",
"qwc"
]
},
"qmem-copy->!": {
"args": [
"dst",
"src",
"qwc"
]
},
"qmem-clear!": {
"args": [
"dst",
"qwc"
]
},
"mem-set32!": {
"args": [
"dst",
"word-count",
"value"
]
},
"mem-or!": {
"args": [
"dst",
"src",
"bytes"
]
},
"fact": {
"args": [
"x"
]
},
"print": {
"args": [
"obj"
]
},
"printl": {
"args": [
"obj"
]
},
"inspect": {
"args": [
"obj"
]
},
"mem-print": {
"args": [
"ptr",
"word-count"
]
},
"print-tree-bitmask": {
"args": [
"mask",
"count"
]
},
"valid?": {
"args": [
"obj",
"expected-type",
"err-msg-str",
"allow-false",
"err-msg-dest"
]
},
"matrix-transpose!": {
"args": [
"dst",

View File

@ -11,7 +11,7 @@
#include "decompiler/Disasm/Register.h"
namespace decompiler {
DecompilerTypeSystem::DecompilerTypeSystem(GameVersion version) {
DecompilerTypeSystem::DecompilerTypeSystem(GameVersion version) : m_version(version) {
ts.add_builtin_types(version);
}

View File

@ -95,7 +95,10 @@ class DecompilerTypeSystem {
void reset() { current_method_type.clear(); }
} type_prop_settings;
GameVersion version() const { return m_version; }
private:
GameVersion m_version;
mutable goos::Reader m_reader;
};
} // namespace decompiler

View File

@ -179,7 +179,7 @@ goos::Object decompile_function_at_label(const DecompilerLabel& label,
auto other_func = file->try_get_function_at_label(label);
if (other_func && other_func->ir2.env.has_local_vars() && other_func->ir2.top_form &&
other_func->ir2.expressions_succeeded) {
auto out = final_output_lambda(*other_func);
auto out = final_output_lambda(*other_func, file->version);
if (in_static_pair) {
return pretty_print::build_list("unquote", out);
} else {

View File

@ -0,0 +1,44 @@
# BLERC
"blerc" = "merc blend shape" is the face animation program.
It works by updating the vertices used by merc.
It begine with a call to `(blerc-init)`, which resets the list of things to blerc.
Then, each process-drawable calls `merc-blend-shape` (if needed), which figures out the blend shape coefficients for the current frame, and passes it to `setup-blerc-chains`. This builds a DMA chain of all blercs.
However, this DMA chain doesn't get put in a VU1 bucket. Instead, after the full chain is build, `(blerc-execute)` will do a bunch of math and update the merc vertex data.
This was checked by running `(-> (the process-drawable (process-by-name "sidekick" *active-pool*)) draw mgeo effect 0 frag-geo)` and seeing that the `spad_from_dma_no_sadr_off` in `merc_blend_shape.cpp` was writing to this data.
There is a system to avoid doing blerc computations when not needed. Once blerc is disabled, it does a single final run with all 0's, which puts everything "back to normal", and then blerc no longer runs.
# Determining which fragments can be blerc'd
In the PC port, we will need to update the vertices. We want to update as few vertices as possible at runtime because this will likely be slow. Additionally, we need metadata to figure out how to go from modified merc data back to PC-port format. If we know which vertices can possibly be modified by blerc, we can skip including the metadata for the rest. Only a small number of merc vertices are faces, so this could be a big win for data size.
As far as I can tell, there's a `merc-blend-ctrl` per each fragment. It's size is `2 + merc_ctrl.header.blend_target_count`. If the `blend-vtx-count` field of a merc-blend-ctrl is 0, then this fragment has no blerc.
Checking some common models:
(showing number of effects, frags, lump4 bytes that have possible blerc)
```
BLERC: eichar-lod0, 3/4 e, 15/85 f, 1737/9984 v
BLERC: sidekick-lod0, 3/4 e, 9/35 f, 957/3933 v
```
# PC draw lists:
The plan is to make 3 sets of draws:
- Normal draw list
- Draw list that touches no blerc vertices
- Alternate draw list for blerc vertices
The first list is what is normally used. This may contain draws that mix together blerc and non-blerc vertices.
The second draw list can be used when blerc is active for this character for all the non-blerc vertices.
The third draw list will contain the blerc vertices, but will do indexing slightly differently. Instead of indexing into the giant array of all merc vertices, it will index into the yet-to-be-filled blerc index buffer for this character.
This approach gives us:
- the same performance as before if blerc is off
- easy way to do per-character blerc'd vertex uploads
- possibility to do per-frame blerc'd vertex uploads with some clever indexing.
- per-fragment granularity for blerc on/off

View File

@ -0,0 +1,137 @@
## Finding the Normals
We're going to assume that the generic tie math in Jak 1 is the same as ETIE as Jak 2. This could be wrong, but it should be easy to verify.
The tricky part is finding the normals. These are needed for ETIE, but not plain TIE.
For looking through the types, it seems like generic TIE uses the same normals as ETIE:
```lisp
(deftype generic-tie-normal (structure)
((x int8 :offset-assert 0)
(y int8 :offset-assert 1)
(z int8 :offset-assert 2)
(dummy int8 :offset-assert 3) ;; was 0 in ETIE normals.
)
:method-count-assert 9
:size-assert #x4
:flag-assert #x900000004
)
```
Searching around for normal
```lisp
(deftype generic-tie-header (structure)
((effect uint8 :offset-assert 0)
(interp-table-size uint8 :offset-assert 1)
(num-bps uint8 :offset-assert 2)
(num-ips uint8 :offset-assert 3)
(tint-color uint32 :offset-assert 4)
(index-table-offset uint16 :offset-assert 8)
(kick-table-offset uint16 :offset-assert 10)
(normal-table-offset uint16 :offset-assert 12) ;; here it is!
(interp-table-offset uint16 :offset-assert 14)
(gsf-header gsf-header :inline :offset-assert 16)
)
:method-count-assert 9
:size-assert #x20
:flag-assert #x900000020
)
```
My first guess is that the generic data for a TIE fragment is just a header, and these `uint16`s are byte-offsets for the normal data. I'd guess that there's a header per-fragment - they used `uint8`'s for `num-bps`/`num-ips` (base points, interpolated points). A single proto might have more than 255 points, but a fragment won't.
My guess is that `generic-ref` in a `tie-fragment` points to some data that starts with `generic-tie-header`.
```lisp
(deftype tie-fragment (drawable)
((gif-ref (inline-array adgif-shader) :offset 4)
(point-ref uint32 :offset 8)
(color-index uint16 :offset 12)
(base-colors uint8 :offset 14)
(tex-count uint16 :offset-assert 32)
(gif-count uint16 :offset-assert 34)
(vertex-count uint16 :offset-assert 36)
(color-count uint16 :offset-assert 38)
(num-tris uint16 :offset-assert 40)
(num-dverts uint16 :offset-assert 42)
(dp-ref uint32 :offset-assert 44)
(dp-qwc uint32 :offset-assert 48)
(generic-ref uint32 :offset-assert 52) ;; the data we want
(generic-count uint32 :offset-assert 56)
(debug-lines (array vector-array) :offset-assert 60)
)
:method-count-assert 18
:size-assert #x40
:flag-assert #x1200000040
)
```
Extract the data from the file
```cpp
u16 generic_qwc = read_plain_data_field<u32>(ref, "generic-count", dts);
if (generic_qwc) {
generic_data.resize(16 * generic_qwc);
auto generic_data_ref = deref_label(get_field_ref(ref, "generic-ref", dts));
memcpy_plain_data((u8*)generic_data.data(), generic_data_ref, generic_qwc * 16);
```
The data made sense. There were some cases where the `interp-table` and the `normal-table` appeared to be on top of each other, but this only occured when `num-ips` was 0 and `interp-table-size` was 0 too.
Treating `normal-table-offset` as a byte-offset from the start of the header seemed to work.
One fear I had is that the mesh for generic/non-generic is somehow different, so I sanity checked that `num-ips` + `num-bps` from the generic header matched the total unique vertex count from the TIE unpacker.
## Finding the Envmap shader
We need an additional shader for the environment map draw. Fortunately, this seems the same as Jak 2 - there's a field in `prototype-bucket-tie` for it. I turned on that code for Jak 1, and it found envmap shaders on reasonable things.
## Finding the tint color
This is a little trickier. In jak 2, the tint color was specified per bucket, but in Jak 1, it's specified per fragment. Luckily my data format for ETIE can support this, but `extract_tie.cpp` needs some refactoring to actually generate it.
## Normal Bugs
Some very small number of fragments appear totally wrong.
The most likely explanation is the normals are in the wrong order. The normals seems like valid normals, but looking at the mesh, they are clearly wrong.
I made the assumption that the order of normals would match the order of points, but could this be wrong?
There's this table called `index_table`:
```
0 2 32 33
1 3 16 17
34 35 4 5
6 8 36 37
7 9 18 20
38 39 19 21
10 11 40 41
22 23 12 13
42 43 24 25
14 15 44 45
26 27 28 29
46 47 30 31
```
but going through extremely carefully showed nothing interesting... It all matches up perfectly
Next step: maybe the normal matrix is wrong... let's try to actually find the mystery scaling factor:
```
vmulx.xyz vf16, vf10, vf14
```
the `vf14.x` here.
```
lui t6, 16256
mtc1 f1, t6 ;; 1.0
qmfc2.i s1, vf10
mtc1 f12, s1
dsra32 s2, s1, 0
mtc1 f13, s2
pextuw s2, r0, s2
mtc1 f14, s2
mula.s f12, f12
madda.s f13, f13
madd.s f15, f14, f14
rsqrt.s f15, f1, f15
mfc1 s1, f15
qmtc2.i vf14, s1
vmulx.xyz vf16, vf10, vf14
```

View File

@ -0,0 +1,513 @@
```
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function setup-blerc-chains-for-one-fragment
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setup-blerc-chains-for-one-fragment
a0: num-targets
a1: blend-shape-coeffs (s16 array)
a2: dma-mem-ptr
a3: blend-data
t0: blend-ctrl
t1: geo-data)
B0:
L9:
daddiu sp, sp, -128
sd ra, 0(sp)
sq s0, 16(sp)
sq s1, 32(sp)
sq s2, 48(sp)
sq s3, 64(sp)
sq s4, 80(sp)
sq s5, 96(sp)
sq gp, 112(sp)
lb v1, 0(t0) ;; v1 = blend-vtx-count
addiu t2, r0, 0 ;; t2 = 0
por t7, r0, r0 ;; t7 = 0
lw t3, *blerc-globals*(s7) ;; t3 = *blerc-globals*
lw t3, 4(t3) ;; t3 = blec-globals.next
or t4, v1, r0 ;; t4 = blend-vtx-count.
;; link
B1:
L10:
lui t7, 4096
sll r0, r0, 0
daddiu t7, t7, 1
sll r0, r0, 0
beq t3, r0, L11
sq t7, 0(a2)
B2:
sw a2, 12(t3)
sll r0, r0, 0
B3:
L11:
or t3, a2, r0
daddu t6, t4, t4
daddu t5, v1, v1
daddu t6, t6, t4
daddu t7, t5, v1
daddu t5, t6, t6
daddu t6, t7, t7
daddu t7, t5, t5
daddiu t6, t6, 15
daddiu t5, t5, 15
andi t6, t6, 65520
dsrl t5, t5, 4
daddu t8, t2, t2
daddiu t7, t7, 15
daddu t9, t8, t2
dsrl t8, t7, 4
daddu ra, t9, t9
addiu t9, r0, 0
daddu t7, ra, ra
daddiu s3, a2, 32
daddu s2, ra, a3
daddu ra, t7, t1
lui t7, 12288
daddiu gp, a0, -1
daddu t7, t7, t5
or s5, a1, r0
sq t7, 0(s3)
daddiu s4, t0, 2
sw s2, 4(s3)
daddu s2, s2, t6
daddiu s3, s3, 16
sll r0, r0, 0
B4:
L12:
lb s1, 0(s4)
daddiu s4, s4, 1
lh s0, 0(s5)
daddiu s5, s5, 2
beq s1, r0, L13
sq t7, 0(s3)
B5:
sw s2, 4(s3)
daddu s2, s2, t6
beq s0, r0, L13
sw s0, 12(s3)
B6:
daddiu s3, s3, 16
daddiu t9, t9, 1
B7:
L13:
bne gp, r0, L12
daddiu gp, gp, -1
B8:
sq t7, 0(s3)
por t6, r0, r0
sw ra, 4(s3)
lui t6, 28672
sb t8, 0(s3)
sll r0, r0, 0
sq t6, 16(s3)
daddiu t6, s3, 32
sw t9, 20(a2)
sll r0, r0, 0
sw t4, 16(a2)
sll r0, r0, 0
sw ra, 24(a2)
sll r0, r0, 0
sw t8, 28(a2)
sll r0, r0, 0
bne t4, v1, L14
daddiu t5, t5, 1
B9:
daddiu t7, t9, 3
multu3 t5, t5, t7
daddiu t5, t5, -457
sll r0, r0, 0
blez t5, L14
sll r0, r0, 0
B10:
beq r0, r0, L10
addiu t4, r0, 24
B11:
L14:
or a2, t6, r0
daddu t2, t2, t4
beq t2, v1, L15
daddu t5, t2, t4
B12:
dsubu t5, t5, v1
sll r0, r0, 0
blez t5, L10
sll r0, r0, 0
B13:
beq r0, r0, L10
dsubu t4, v1, t2
B14:
L15:
lw v1, *blerc-globals*(s7)
sw t3, 4(v1)
or v0, a2, r0
ld ra, 0(sp)
lq gp, 112(sp)
lq s5, 96(sp)
lq s4, 80(sp)
lq s3, 64(sp)
lq s2, 48(sp)
lq s1, 32(sp)
lq s0, 16(sp)
jr ra
daddiu sp, sp, 128
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; .function blerc-execute
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;BAD PROLOGUE
;; Warnings:
;; INFO: Flagged as mips2c by config
;; INFO: Assembly Function
B0:
L37:
daddiu sp, sp, -96
sd ra, 0(sp)
sq s2, 16(sp)
sq s3, 32(sp)
sq s4, 48(sp)
sq s5, 64(sp)
sq gp, 80(sp)
lw v1, *blerc-globals*(s7)
lwu s5, 0(v1)
beq s5, r0, L56
or v1, s7, r0
B1:
addiu v1, r0, 0
addiu gp, r0, 0
addiu v1, r0, 0
lw v1, *gsf-buffer*(s7)
lw t9, flush-cache(s7)
addiu a0, r0, 0
jalr ra, t9
sll v0, ra, 0
addiu v1, r0, 848
lui a0, 28672
daddu a1, v1, a0
lw v1, *blerc-globals*(s7)
daddu v1, r0, v1
sll r0, r0, 0
lui a0, 4096
sll r0, r0, 0
ori a0, a0, 54272
andi a1, a1, 16383
B2:
L38:
lw a2, 0(a0)
sll r0, r0, 0
sll r0, r0, 0
sll r0, r0, 0
andi a2, a2, 256
sll r0, r0, 0
bne a2, r0, L38
lw a2, 0(v1)
B3:
beq a2, r0, L39
sw a1, 128(a0)
B4:
addiu v1, r0, 324
sw a2, 48(a0)
sw r0, 32(a0)
sync.l
sw v1, 0(a0)
sync.l
B5:
L39:
or v1, r0, r0
beq r0, r0, L55
sll r0, r0, 0
B6:
L40:
bne gp, r0, L41
sll r0, r0, 0
B7:
lui v1, 28672
daddu a0, r0, v1
beq r0, r0, L42
sll r0, r0, 0
B8:
L41:
addiu v1, r0, 8192
lui a0, 28672
daddu a0, v1, a0
B9:
L42:
bne gp, r0, L43
sll r0, r0, 0
B10:
addiu v1, r0, 8192
lui a1, 28672
daddu a1, v1, a1
beq r0, r0, L44
sll r0, r0, 0
B11:
L43:
lui v1, 28672
daddu a1, r0, v1
B12:
L44:
daddiu v1, a0, 848
daddu a2, r0, a0
daddiu a3, a1, 848
daddiu a1, v1, 12
sll r0, r0, 0
lui a2, 4096
sll r0, r0, 0
ori a2, a2, 54272
andi a3, a3, 16383
B13:
L45:
lw t0, 0(a2)
sll r0, r0, 0
sll r0, r0, 0
sll r0, r0, 0
andi t0, t0, 256
sll r0, r0, 0
bne t0, r0, L45
lw t0, 0(a1)
B14:
beq t0, r0, L46
sw a3, 128(a2)
B15:
addiu a1, r0, 324
sw t0, 48(a2)
sw r0, 32(a2)
sync.l
sw a1, 0(a2)
sync.l
B16:
L46:
;; blend ctrl
;; blend data
;; coeffs.
;;
or a1, r0, r0
or a2, a0, r0 ;; likely the blerc-block.
lw a3, *gsf-buffer*(s7)
daddiu t2, a2, 880 ;; t2 is whatever's after blerc-block
lb t1, 0(t2) ;; size of whatever's after
sll r0, r0, 0
or t0, a2, r0 ;; t0 is the blerc block
lw a1, 868(a2) ;; a1 = block.header.overlap
daddiu t3, t1, 1 ;; some counts??
or t1, a3, r0 ;; t1 = ee buffer.
sll t8, t3, 4 ;; t8 = some count (stride)
sll t3, a1, 4 ;; t3 = overlap * 16
daddu t9, t3, a3 ;; t9 = &buffer[overlap] (qws)
daddu t3, t2, t8 ;; t3 = &dummy[unk_count + 1] (qws)
beq a1, r0, L49
daddiu ra, t2, 16 ;; ra = after block ptr (+1 qw to skip a header I guess)
;; this block expands "overlap"s to
B17:
lh t5, 12(t3)
daddu t2, t3, t8
beq r0, r0, L48
sll r0, r0, 0
B18:
L47:
lh t5, 12(t2)
daddu t2, t2, t8
sq t6, 0(t1)
daddiu t1, t1, 16
B19:
L48:
pcpyh t5, t5
mfc1 r0, f31
bne t1, t9, L47
pcpyld t6, t5, t5
B20:
dsubu t3, t2, t8
sll r0, r0, 0
B21:
L49:
addiu t1, r0, 255 ;; cs
addiu t2, r0, 8192 ;; cs
lb s5, 0(t3)
daddiu s4, t3, 16
pcpyh t1, t1
mfc1 r0, f31
pcpyld t1, t1, t1
mfc1 r0, f31
pcpyh t2, t2
mfc1 r0, f31
pcpyld t2, t2, t2 ;; t2 = 8192's
mfc1 r0, f31
por t3, t1, r0 ;; t3 = 255's
mfc1 r0, f31
por t4, r0, r0
mfc1 r0, f31
B22:
L50:
ld t6, 0(ra)
daddu s2, ra, t8
daddiu ra, ra, 8
or s3, a3, r0
pextlb t6, r0, t6
mfc1 r0, f31
pmulth t7, t6, t2
ld t5, 0(s2)
daddiu s5, s5, -1
sll r0, r0, 0
beq r0, r0, L52
daddu s2, s2, t8
B23:
L51:
pmaddh t7, t5, t6
ld t5, 0(s2)
daddu s2, s2, t8
daddiu s3, s3, 16
B24:
L52:
lq t6, 0(s3)
pextlb t5, t5, r0
bne s3, t9, L51
psrah t5, t5, 8
B25:
pmfhl.uw t5
mfc1 r0, f31
psraw t7, t7, 13
mfc1 r0, f31
psraw t5, t5, 13
mfc1 r0, f31
pinteh t5, t5, t7
mfc1 r0, f31
pminh t3, t3, t5
mfc1 r0, f31
pmaxh t4, t4, t5
mfc1 r0, f31
pminh t5, t5, t1
mfc1 r0, f31
pmaxh t5, t5, r0
lq t7, 0(s4)
ppacb t5, r0, t5
mfc1 r0, f31
ppach t7, r0, t7
mfc1 r0, f31
pextlh t5, t5, t7
mfc1 r0, f31
sq t5, 0(t0)
daddiu t0, t0, 16
bne s5, r0, L50
daddiu s4, s4, 16
B26:
lw a3, *stats-blerc*(s7)
beq a3, s7, L53
lw a3, *blerc-globals*(s7)
B27:
lw t2, 12(a3)
lw t1, 16(a3)
lw t0, 20(a3)
lw a2, 864(a2)
multu3 a1, a1, a2
daddiu t2, t2, 1
daddu a2, t1, a2
daddu a1, t0, a1
sw t2, 12(a3)
sw a2, 16(a3)
sw a1, 20(a3)
pcpyud a1, t3, r0
pminh t3, t3, a1
dsrl32 a1, t3, 0
pminh t3, t3, a1
dsrl t3, t3, 16
lh a1, 8(a3)
pminh t3, t3, a1
sh t3, 8(a3)
pcpyud a1, t4, r0
pmaxh t4, t4, a1
dsrl32 a1, t4, 0
pmaxh t4, t4, a1
dsrl t4, t4, 16
lh a1, 10(a3)
pmaxh t4, t4, a1
sh t4, 10(a3)
B28:
L53:
or a1, r0, r0
lwu a1, 872(a0)
or a3, a0, r0
lwu a0, 876(a0)
lui a2, 4096
sll r0, r0, 0
ori a2, a2, 53248
andi a3, a3, 16383
B29:
L54:
lw t0, 0(a2)
sll r0, r0, 0
sll r0, r0, 0
sll r0, r0, 0
andi t0, t0, 256
sll r0, r0, 0
bne t0, r0, L54
sll r0, r0, 0
B30:
sw a3, 128(a2)
addiu a3, r0, 256
sw a1, 16(a2)
sll r0, r0, 0
sw a0, 32(a2)
sync.l
sw a3, 0(a2)
sync.l
or a0, r0, r0
addiu a0, r0, 1
dsubu gp, a0, gp
lwu s5, 12(v1)
or v1, s5, r0
B31:
L55:
bne s5, r0, L40
sll r0, r0, 0
B32:
or v1, s7, r0
B33:
L56:
or v0, r0, r0
ld ra, 0(sp)
lq gp, 80(sp)
lq s5, 64(sp)
lq s4, 48(sp)
lq s3, 32(sp)
lq s2, 16(sp)
jr ra
daddiu sp, sp, 96
sll r0, r0, 0
sll r0, r0, 0
sll r0, r0, 0
```

View File

@ -0,0 +1,261 @@
# Generic
What do we expect to fix by adding generic?
- "warp" effect (distortion effect that samples the framebuffer)
- "death" effect (get transformed vertices in world for spawning death effect particles)
- "ripple query" feature (get transformed vertices in world, kinda like depth)
- "hud drawing" for the orb/metal head gem
# Part 1: redirecting stuff to generic
Right now, we send everything to PC merc all the time, completely ignoring the original decision. I added code to redirect stuff back to generic when any of the above features are used. I noticed that warp stuff often contained two merc "effects" (really just chunks of frags drawn with the same renderer). One has `warp`, and the other has `force-mercneric`. So I redirected both of these to mercneric for now, though it's possible that the `force-mercneric` one can work with PC merc. (doesn't seem like a big deal either way...).
This seemed to work - lots of broken merc stuff is correclty redirected.
I also checked for the same sort of issue that we had with jak 1's "transparent sculptor visor". They have a trick to draw transparent stuff in a texture bucket that normally doesn't have transparency - they set `force-mercneric` and set bit `0b10` in the effect bits, which changes the blending mode for this effect. This same thing is possible in jak 2, and I added support for it as well. I couldn't find any cases where this actually mattered, but it doesn't hurt.
# Part 2: Generic Buffer Setup
Like with many renderers, the bucket setup is only done if the renderer is actually used.
This path is:
- `swap-display` is called in `main.gc`
- this calls `display-frame-finish`
- this calls `generic-vu1-init-buffers`
- this calls a function to initialize each generic bucket. For warp, it does
```
;; s5-0 is the usual gs-zbuf with depth writes enabled
(generic-vu1-init-buf-special (bucket-id gmerc-warp) s5-0)
```
```
(defun generic-vu1-init-buf-special ((arg0 bucket-id) (arg1 gs-zbuf))
"At the beginning of this bucket's chain, insert DMA data for fx-copy-buf and initializing generic."
(let ((s5-0 (-> *display* frames (-> *display* on-screen) bucket-group arg0)))
(when (!= s5-0 (-> s5-0 last))
(let* ((s4-0 (-> *display* frames (-> *display* on-screen) global-buf))
(s3-1 (-> s4-0 base))
)
;; generate DMA data for copying the framebuffer
(fx-copy-buf s4-0)
;; generate DMA data for initializing generic VU1.
(generic-init-buf s4-0 arg1)
(let ((v1-12 (the-as dma-packet (-> s4-0 base))))
(set! (-> v1-12 dma) (new 'static 'dma-tag :id (dma-tag-id next) :addr (-> s5-0 next)))
(set! (-> v1-12 vif0) (new 'static 'vif-tag))
(set! (-> v1-12 vif1) (new 'static 'vif-tag))
(set! (-> s4-0 base) (the-as pointer (&+ v1-12 16)))
)
(set! (-> s5-0 next) (the-as uint s3-1))
)
)
)
0
(none)
)
```
The import part is
```
;; generate DMA data for copying the framebuffer
(fx-copy-buf s4-0)
;; generate DMA data for initializing generic VU1.
(generic-init-buf s4-0 arg1)
```
The `fx-copy-buf` function needs to be decompiled. There's nothing too interesting, other than it copies the screen in chunks, rather than one giant sprite. The offsets and stuff are a bit confusing, but we might be able to guess our way through it.
For now, I'm going to leave out this function, and we'll just remember to do the copy on the C++ at the start of this bucket.
# Part 3: Submitting merc models to generic
In part 1, we set things up so the main `foreground-draw` functions sends models to `foreground-generic-merc`, which isn't decompiled yet. If this works like Jak 1, this doesn't actually do the EE side of generic yet - it just adds them to a list.
The next step was to mips2c/decompile `foreground-generic-merc` and the functions it calls. Unfortunately, the main function can't easily be decompiled. I suspect it's very similar to `draw-bones-generic-merc` where it fills out generic parameters based on settings. The death vertex and fragment adding loop are split into separate functions that we can decompile this time:
```
(defun foreground-generic-merc-death ((arg0 draw-control) (arg1 generic-merc-ctrl))
"Modify a generic-merc-ctrl to set up merc-death effect."
;; possibly disable drawing if requested
(when (and (>= (the-as int (- (-> arg0 death-timer-org) (-> arg0 death-timer)))
(the-as int (-> arg0 death-draw-overlap))
)
(!= (-> arg0 death-draw-overlap) 255)
)
(set! (-> arg1 header display-triangles) (the-as uint 0))
0
)
;; update the vertices that we're querying.
(when (not (paused?))
(let ((v1-6 (+ (-> arg0 death-vertex-skip) (rand-vu-int-count (the-as int (-> arg0 death-vertex-skip))))))
(set! (-> arg1 header death-vertex-skip) v1-6)
(set! (-> arg1 header death-effect) (-> arg0 death-effect))
(set! (-> arg1 header death-start-vertex)
(/ (* v1-6 (- (-> arg0 death-timer-org) (-> arg0 death-timer))) (-> arg0 death-timer-org))
)
)
)
(none)
)
(defun foreground-generic-merc-add-fragments ((arg0 merc-effect) (arg1 pointer) (arg2 mercneric-chain))
"Add fragments from a merc-effect to the generic chain."
(let ((v1-0 (-> arg0 frag-geo))
(a3-0 (the-as structure (-> arg0 frag-ctrl)))
(a0-1 (-> arg0 frag-count))
)
;; loop over fragments, adding matrix refs.
(dotimes (t0-0 (the-as int a0-1))
(let ((t1-2 (+ (* (-> (the-as merc-fragment-control a3-0) mat-xfer-count) 2) 4))
(t2-0 (-> v1-0 header mm-quadword-size))
)
(set! (-> (the-as dma-packet arg1) dma)
(new 'static 'dma-tag :id (dma-tag-id ref) :addr (the-as int v1-0) :qwc t2-0)
)
(set! (-> (the-as dma-packet arg1) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet arg1) vif1) (new 'static 'vif-tag))
(when (nonzero? t0-0)
(set! (-> (the-as (pointer int32) (-> arg2 next)) 0) (the-as int arg1))
(set! (-> arg2 next) (the-as uint (&+ arg1 12)))
)
(let ((a1-1 (the-as object (&+ arg1 16))))
(dotimes (t3-6 (the-as int (-> (the-as merc-fragment-control a3-0) mat-xfer-count)))
(let ((t5-4
(-> (scratchpad-object foreground-work)
regs
mtxs
(-> (the-as merc-fragment-control a3-0) mat-dest-data t3-6 matrix-number)
)
)
)
(set! (-> (the-as dma-packet a1-1) dma)
(new 'static 'dma-tag :qwc #x7 :id (dma-tag-id ref) :addr (the-as int t5-4))
)
)
(set! (-> (the-as dma-packet a1-1) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet a1-1) vif1) (new 'static 'vif-tag))
(set! a1-1 (&+ (the-as pointer a1-1) 16))
)
(set! (-> (the-as dma-packet a1-1) dma) (new 'static 'dma-tag :id (dma-tag-id end)))
(set! (-> (the-as dma-packet a1-1) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet a1-1) vif1) (new 'static 'vif-tag))
(set! arg1 (&+ (the-as pointer a1-1) 16))
)
(set! a3-0 (&+ a3-0 t1-2))
(set! v1-0 (the-as merc-fragment (+ (the-as uint v1-0) (* t2-0 16))))
)
)
)
arg1
)
```
It's overall very similar, but the way of finding the matrices is little different, but it all makes sense. The generic chain will be processed later, and it will dma these matrices to the scratchpad for transforming generic merc data.
# Part 4: Running Generic Merc EE processing
Part 3 just adds fragments to a list. We actually have to process these fragments on the EE before drawing them with VU1. Assuming it's like Jak 1, the EE step transforms vertices to world frame and computes stuff like envmap coordinates. The VU1 part does the perspective tranformation and clipping.
This is run from `generic-merc-execute-all`, called from `drawable.gc`.
```
(defun generic-merc-execute-all ((arg0 dma-buffer))
"Run EE processing on all generic merc chains."
(let ((s4-0 (-> *foreground* foreground-grid))
(gp-0 (-> *display* frames (-> *display* on-screen) global-buf base))
)
(with-profiler 'generic-merc *profile-generic-merc-color*
;; reset profiling
(reset! (-> *perf-stats* data (perf-stat-bucket mercneric)))
(set! (-> (scratchpad-object generic-work) saves to-vu0-waits) (the-as uint 0))
(set! (-> (scratchpad-object generic-work) saves to-spr-waits) (the-as uint 0))
(set! (-> (scratchpad-object generic-work) saves from-spr-waits) (the-as uint 0))
(flush-cache 0)
;; init generic
(generic-initialize-without-sync (-> *math-camera* perspective) *default-lights*)
(generic-merc-init-asm)
(set! (-> (scratchpad-object generic-work) in-buf merc shadow write-limit) (the-as int (&+ (-> arg0 end) -262144)))
;; loop over grid of chains (levels x textures) and do the chain.
(dotimes (s3-1 7)
(dotimes (s2-1 7)
(generic-merc-do-chain (-> s4-0 level-buckets s3-1 data s2-1 mercneric) arg0)
)
)
;; do separate warp chain
(generic-merc-do-chain (-> s4-0 warp-chain) arg0)
;; finish profiling
(read! (-> *perf-stats* data (perf-stat-bucket mercneric)))
(update-wait-stats
(-> *perf-stats* data 38)
(-> (scratchpad-object generic-work) saves to-vu0-waits)
(-> (scratchpad-object generic-work) saves to-spr-waits)
(-> (scratchpad-object generic-work) saves from-spr-waits)
)
)
;; update dma memory usage stats.
(let ((v1-66 *dma-mem-usage*))
(when (nonzero? v1-66)
(set! (-> v1-66 length) (max 90 (-> v1-66 length)))
(set! (-> v1-66 data 89 name) "pris-generic")
(+! (-> v1-66 data 89 count) 1)
(+! (-> v1-66 data 89 used)
(&- (-> *display* frames (-> *display* on-screen) global-buf base) (the-as uint gp-0))
)
(set! (-> v1-66 data 89 total) (-> v1-66 data 89 used))
)
)
)
(none)
)
```
This function doesn't look too bad...
# Part 5: Decompiling `generic-merc` stuff
- `generic-merc-init-asm`
Nothing interesting, dma sync patched out.
- `mercneric-convert`
The worst function. Copied all the vu0 stuff from jak 1.
- `high-speed-reject`
Copied vu0 stuff from jak1
- `generic-translucent`
New for jak 2, easy.
- `generic-merc-query`
New for jak 2, easy. (maybe no mips2c?)
- `generic-merc-death`
New for jak 2, easy. (maybe no mips2c?)
- `generic-merc-execute-asm`
Did the same direct-call patch for the `mercneric-convert` call. Not sure why I did this originally, but it can only help.
- `generic-merc-do-chain`
New for jak 2, easy (patched out dma sync).
# Part 6: Decompiling `generic-effect` stuff
- `generic-work-init`
Not mips2c. Just copies stuff to generic-work.
- `generic-light-proc`
- `generic-warp-source`
- `generic-warp-envmap-dest`
- `generic-warp-dest`
- `generic-prepare-dma-single` (`generic-effect`)
- `generic-prepare-dma-double` (`generic-effect`)
- `generic-envmap-proc`

319
docs/scratch/shrub.md Normal file
View File

@ -0,0 +1,319 @@
# Shrub EE code notes
## Looking through village1-vis.asm
The "prototype" main class is `prototype-bucket-shrub`. These are stored in a `prototype-inline-array-shrub`.
Example of a `prototype-bucket-shrub`:
```
.type prototype-bucket-shrub
.word L22404
.word 0x0
.word 0x20025
.word L4630
.word L4604
.word L4617
.word L4641
.word 0x48a00000
.word 0x48200000
```
The `.word L22404` is a string `"palmplant-top.mb"`.
The four `L4630`, `L4604`, `L4617`, `L4641` are the 4 `geometry`. In this case, it looks like each is a different kind, but I don't think we can make any assumptions about this.
### Highest Detail `prototype-generic-shrub`
For now, let's ignore this because we don't have a generic renderer. It also really looks like no shrubs ever draw with generic in-game, but I could be wrong. It looks like every single shrub has this as their first geomery.
### 2nd Highest `prototype-shrubbery`
I think this is the "normal" shrub. Each prototype is made up of one or more `shrubbery`. Again, every shrub has this.
### 3rd Highest `prototype-trans-shrubbery`
Based on PCSX2, it looks like these are usually lower-res models that fade out as you move away. Like the 2nd, they are made of `shrubbery`, and every shrub has one.
### 4th Highest `billboard`
These are the lowest res, always face toward the camera. Each `billboard` is just a single thing - no sub-parts. I suspect that each prototype can become a single rectangle "billboard", no matter how complicated the original shrub is.
I think we should work on `prototype-shrubbery` (and possibly the transparent one) first. Maybe we can leave out the billboard and claim that we're "increasing the level of detail" :).
## Looking through `shrubbery.gc` decomp
Doing reverse order in the `ir2.asm` file.
### `login billboard`
No surprise here, billboard is a single rectangle, it can have only one texture, and there's exactly one `adgif-shader-login`.
### `mem-usage-shrub-walk`
This function recursively iterates over a `draw-node` tree, computing memory usage, and is a good example of how these `draw-node` trees work. Each `draw-node` has a sphere. Inside this sphere are between 1 and 8 "children", which are in an `inline-array`. The children are usually `draw-node`, except for the leaf nodes, which are some other drawable type. In this case, they are `instance-shrubbery`. This is used for frustum culling. If a `draw-node` sphere is outside of the view of the camera, you can just skip all instances in that `draw-node`.
Unlike a normal tree, they rarely refer to a single `draw-node`, but instead a group of between 1 and 8 draw-nodes. These are passed around as a `draw-node` (the first in the group) and an integer (the number in the group). This might seem weird, but simplifies the iteration logic. (if you don't know the type/size of the leaf node, you can't iterate over an inline-array of them, but you could call some virtual function that expects the object to be the first one in an `inline-array` with a size argument.)
This is how to recursively iterate over `draw-node` tree:
```lisp
(dotimes (s1-0 arg1)
(let ((a1-2 (-> s2-0 child-count)))
(cond
((logtest? (-> s2-0 flags) 1) ;; flag means that we aren't a leaf.
(mem-usage-shrub-walk (the-as draw-node (-> s2-0 child)) (the-as int a1-2) arg2 arg3)
)
(else
;; you are at an inline-array of leaves.
;; (-> s2-0 child) is an inline-array of a1-2 leaves
;; a1-2 is between 1 and 8.
)
)
)
;; move on to the next child draw node.
(&+! s2-0 32)
)
```
NOTE: `arg1` should be an int here.
We learned here that `shrub` does use the `draw-node` BVH system. (this was unclear because `shrub` does not appear to use the compressed visibility string based vis system, which is often used simultaneously with `draw-node`).
### `login generic-shrub-fragment`
`generic-shrub-fragment`'s have a variable number of textures. The `cnt-qwc` field refers to the total size of the `textures` array, as each `adgif-shader` is 5 qw.
### `login prototype-shrubbery`
From village1-vis, we know this is just logging in `shrubbery`s.
### `asize-of prototype-shrubbery`
The size calculation makes sense for an inline-array of `shrubbery`, with 1 `shrubbery` being inside the type, and the rest running off the end of the array.
### `login prototype-generic-shrub`
No surprises. Confirms that these aren't inline arrays (and we could tell from village1-vis anyway)
### `login shrubbery`
Gives us some idea of the layout of the "header". The first `u32` is the number of textures / 2. Each `shrubbery` may have multiple textures.
### `login drawable-tree-instance-shrub`
Nothing interesting
### `shrub-num-tris`
This tells us the number of triangles in a `shrubbery` is `header[2] - 2*header[1]`. One of the most popular GS formats is "triangle strip". The number of triangles in a triangle strip is `num_verts - 2`. So, very likely `header[2]` is the total number of vertices, and `header[1]` is the number of strips
### `shrub-make-perspective-matrix`
This probably makes the matrix used to transform shrubbery. I'm not sure why yet, but they take the normal transformation (`camera-temp`) and modify it a bit. We may be able to get away with ignoring this. Ideally we just get the shrub positions (in the world coordinate system, just like with TIE/TFRAG), and then use the same logic we used for TIE/TFRAG.
### `shrub-init-view-data`.
Note: change `texture-giftag` in `shrub-view-data` to a `gs-gif-tag`.
This populates fields of a `shrub-view-data`, which will probably end up in VU memory.
There's two things here that are suspicious. First:
```lisp
(set! (-> arg0 texture-giftag tag) (new 'static 'gif-tag64 :nloop #x1 :nreg #x4))
(set! (-> arg0 texture-giftag regs) (new 'static 'gif-tag-regs
:regs0 (gif-reg-id a+d)
:regs1 (gif-reg-id a+d)
:regs2 (gif-reg-id a+d)
:regs3 (gif-reg-id a+d)
)
)
```
usually an `adgif-shader` is 5 qw's of a+d data. This sets up 4. The fifth one is usually `gs-alpha`, and it seems reasonable that they could set this once, then not bother setting it again.
Second, they put `#x40a00000` in the 3rd word of the giftag:
```
(set! (-> arg0 texture-giftag word 3) (the-as uint #x40a00000))
```
as far as I can tell the GIF will ignore this. They might use it as a constant `5.0`. But it seems weird to put it here.
If you change the `score` so it uses the named fields, the rest of the constants are:
```
(set! (-> arg0 tex-start-ptr) (the-as int 25167696.0))
(set! (-> arg0 mtx-buf-ptr) (the-as int 8388608.0))
(set! (-> arg0 fog-0) (-> *math-camera* pfog0))
(set! (-> arg0 fog-1) (-> *math-camera* pfog1))
(set! (-> arg0 fog-clamp x) (-> *math-camera* fog-min))
(set! (-> arg0 fog-clamp y) (-> *math-camera* fog-max))
```
the fog stuff seems reasonable to me (it's involved in computing the perspective division and the GS fog coefficient, don't worry for now).
The `buf-ptr` stuff is very likely this awful trick they do. The VU doesn't have good instructions for common integer operations (like you'd use to mainpulate memory addresses), so they find floating point values, then when added/subtracted in certain ways, have the lower 16 bits of the float equal to the VU memory address. Gross. Maybe the `5.0` is also part of this.
### `shrub-upload-view-data`
Note: I changed the type casts:
```
"shrub-upload-view-data": [
[[3, 16], "a0", "dma-packet"]
],
```
and got a very typical upload.
```
(defun shrub-upload-view-data ((arg0 dma-buffer))
(let ((s5-0 3))
(let* ((v1-0 arg0)
(a0-1 (the-as object (-> v1-0 base)))
)
(set! (-> (the-as dma-packet a0-1) dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc s5-0))
(set! (-> (the-as dma-packet a0-1) vif0) (new 'static 'vif-tag :imm #x404 :cmd (vif-cmd stcycl)))
(set! (-> (the-as dma-packet a0-1) vif1) (new 'static 'vif-tag :cmd (vif-cmd unpack-v4-32) :num s5-0))
(set! (-> v1-0 base) (&+ (the-as pointer a0-1) 16))
)
(shrub-init-view-data (the-as shrub-view-data (-> arg0 base)))
(&+! (-> arg0 base) (* s5-0 16))
)
#f
)
```
- `s5-0` this is the number of quadwords they will upload.
- `dma-tag`: `cnt`, means the data will come after the tag (and the next thing comes after that). `qwc` makes sense
- As part of a DMA tag, you get 2 VIF tags "for free". These are sent to the VIF.
- `vif0`: the `stcycl` sets the `cl`/`wl` register of VIF to `0x404` which is the mode for just copying stuff like normal (doesn't skip data)
- `vif1`: the `unpack` command means copy data to data memory. The `v4-32` is the mode for copying quadwords.
The `0x404 stcycl` and `v4-32` format is used if you just want to copy memory exactly, so it shows up a lot. The other formats can do weird unpacking stuff, but are less common.
### `shrub-time`
Not really sure what this is. I noticed in PCSX2 there's a `time:` that shows up in instance info. Maybe it computes the number of VU cycles.
### `shrub-do-init-frame`
This adds stuff to the DMA list to get the hardware ready for shrub.
Note:
```
"shrub-do-init-frame": [
[[10, 21], "a0", "dma-packet"],
[[24, 29], "a0", "dma-packet"],
[33, "v1", "(pointer vif-tag)"],
[[35, 41], "v1", "(pointer uint32)"],
[42, "v1", "(pointer vif-tag)"],
[[43, 51], "v1", "(pointer uint32)"],
[52, "v1", "(pointer vif-tag)"],
[54, "v1", "(pointer uint32)"]
],
```
All the data gets sent to the VIF. You can check the VIFcode reference part of the manual to see how long the data for each vifcode is.
upload the shrub program. In the PC port we usually set the VU program definitions to have a size of 0, then this function will do nothing.
```
(dma-buffer-add-vu-function arg0 shrub-vu1-block 1)
```
upload the shrub view data:
```
(shrub-upload-view-data arg0)
```
Add a DMA tag. Note that `qwc` is zero so this one just sends the two `vif` tags that are part of the dma tag:
```
(set! (-> (the-as dma-packet a0-3) dma) (new 'static 'dma-tag :id (dma-tag-id cnt)))
(set! (-> (the-as dma-packet a0-3) vif0) (new 'static 'vif-tag :cmd (vif-cmd mscalf) :msk #x1 :imm #x0))
(set! (-> (the-as dma-packet a0-3) vif1) (new 'static 'vif-tag :cmd (vif-cmd flushe) :msk #x1))
(set! (-> v1-0 base) (&+ (the-as pointer a0-3) 16))
```
The `mscalf` runs a VU1 program. The `imm` is 0, so it runs starting at the beginning of VU1 program memory. Let's ignore this for now. It's probably an initialization program that sets up the VU memory and registers. We'll look at it when we get to the actual drawing.
The `flushe` waits for that program to end.
Next it sets the `strow`/`stcol`/`stmask` registers of the VIF. These are used for unpacking settings.
### `shrub-init-frame`
Calls `shrub-do-init-frame` then sets the `gs-test` register.
### `shrub-upload-model`
Seems to upload a model
```
(function shrubbery dma-buffer int symbol)
and
"shrub-upload-model": [
[[17, 26], "a3", "dma-packet"],
[[33, 41], "a0", "dma-packet"],
[[47, 55], "a0", "dma-packet"]
],
```
The model data upload:
```
(set! (-> (the-as dma-packet a3-0) dma)
(new 'static 'dma-tag
:id (dma-tag-id ref)
:addr (-> arg0 obj)
:qwc (+ (-> arg0 obj-qwc) (-> arg0 vtx-qwc) (-> arg0 col-qwc) (-> arg0 stq-qwc))
)
)
(set! (-> (the-as dma-packet a3-0) vif0) (new 'static 'vif-tag :cmd (vif-cmd base) :imm *shrub-state*))
(set! (-> (the-as dma-packet a3-0) vif1) (new 'static 'vif-tag :cmd (vif-cmd offset)))
(set! (-> v1-0 base) (&+ (the-as pointer a3-0) 16))
)
```
this tells us that there is a static DMA chain, starting at the `obj` field of the `shrubbery`. This includes `obj`, `vtx`, `col`, `stq`. The fact that it's a `ref` likely means the DMA will do one big transfer of all that data, then come back to the DMA buffer we're filling.
The use of the `base` with `offset = 0` indicates they aren't using the normal VU double buffering (the weird float tricker earlier was also a clue). They have insane buffering schemes so you can have one model uploading, another model generating GIF tags, and a third model (no longer in VU1 memory) having its GIF tags being processed by the GS. Luckily we can mostly ignore this and just make non-buffered versions because there's no performance benefit to doing this on a PC.
Next, is:
```
(cond
((= arg2 1)
(let* ((v1-2 arg1)
(a0-9 (the-as object (-> v1-2 base)))
)
(set! (-> (the-as dma-packet a0-9) dma) (new 'static 'dma-tag :id (dma-tag-id cnt)))
(set! (-> (the-as dma-packet a0-9) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet a0-9) vif1) (new 'static 'vif-tag :cmd (vif-cmd mscal) :msk #x1 :imm #x11))
(set! (-> v1-2 base) (&+ (the-as pointer a0-9) 16))
)
)
(else
(let* ((v1-3 arg1)
(a0-11 (the-as object (-> v1-3 base)))
)
(set! (-> (the-as dma-packet a0-11) dma) (new 'static 'dma-tag :id (dma-tag-id cnt)))
(set! (-> (the-as dma-packet a0-11) vif0) (new 'static 'vif-tag))
(set! (-> (the-as dma-packet a0-11) vif1) (new 'static 'vif-tag :cmd (vif-cmd mscal) :msk #x1 :imm #x15))
(set! (-> v1-3 base) (&+ (the-as pointer a0-11) 16))
)
)
)
```
which adds a tag that runs a VU1 program. Either program starting at `0x11` or `0x15`, depending on the argument.
Then
```
(set! *shrub-state* (- 164 *shrub-state*))
```
Based on the use of `*shrub-state*`, I think it is a VU buffer management thing.
### `draw-inline-array-instance-shrub`
asm. I'll come back to this later. I assume this runs _before_ the prototype draw, and that it will build lists of instances in each bucket, to be processed by the next function.
### `draw-prototype-inline-array-shrub`
This one is tricky too, I'll come back later.
### `draw-drawable-tree-instance-shrub`
This is the main draw. It does:
- setup `instance-shrub-work` (likely used in the instance asm function)
- set the `next-clear` stuff to 0. This is just a shortcut fast way to reset lists of instances from the previous frame. Will make more sense once we see the previous two functions, I think.
- call `draw-inline-array-instance-shrub`
- call `draw-prototype-inline-array-shrub`
- a whole bunch of performance counters we can skip
### `draw drawable-tree-instance-shrub`
This connects the background system and the drawable/bsp system. When the draw engine executes, it calls this function, adding the drawable tree to `background-work`. When `finish-background` is called, it calls `draw-drawable-tree-instance-shrub` that does the real work
### `unpack-vis`
Having this do nothing makes the shrub system not part of the occlusion culling system.
### `collect-stats`
This shows that geometry 0 is `shrub-near`, geometry 1 is `shrub`, 2 is `trans-shrub`, and 3 is billboard.
It appears that near shrubs are drawn with the generic renderer (also looking at the prototype draw to confirm).
If this is the case, we can probably get away with ignoring geometry 0 and making a single shrub renderer that can handle the near case. From playing in PCSX2, "near" doesn't mean higher resolution, it just means the shrub is partially behind the camera, or close to that. If we port the rendering to opengl properly, we get this "for free".
## Shrubbery Header
- `[0] u32` - number of textures divided by 2.
- `[4] u32` - number of vertices to draw.
- `[8] u32` - number of triangle strips.

View File

@ -45,6 +45,7 @@
(define-extern basic type)
(define-extern string type)
(define-extern symbol type)
(define-extern boolean type)
(define-extern type type)
(define-extern object type)
(define-extern link-block type)

File diff suppressed because it is too large Load Diff

View File

@ -249,7 +249,7 @@
;; WARN: disable def twice: 23. This may happen when a cond (no else) is nested inside of another conditional, but it should be rare.
(defmethod intersects-line-segment? ((this bounding-box) (arg0 vector) (arg1 vector))
"Check intersection in xz plane, using liang-barsky. Not sure if this actually
a useful check or not..."
a useful check or not..."
(let ((f28-0 (- (-> arg1 x) (-> arg0 x)))
(f30-0 (- (-> arg1 z) (-> arg0 z)))
)
@ -275,7 +275,3 @@ a useful check or not..."
)
)
)

View File

@ -97,7 +97,7 @@
;; definition for function matrix->eul
(defun matrix->eul ((arg0 euler-angles) (arg1 matrix) (arg2 int))
"Convert matrix to euler angles with given order flag.
Not clear how this works if the matrix has more than just a rotation."
Not clear how this works if the matrix has more than just a rotation."
0
0
0
@ -213,7 +213,3 @@ Not clear how this works if the matrix has more than just a rotation."
)
arg0
)

View File

@ -4,7 +4,7 @@
;; definition for function truncate
(defun truncate ((arg0 float))
"Round (toward zero) to an integer.
@param arg0 float to truncate"
@param arg0 float to truncate"
(the float (the int arg0))
)
@ -340,8 +340,8 @@
;; definition for function seek-ease
(defun seek-ease ((arg0 float) (arg1 float) (arg2 float) (arg3 float) (arg4 float))
"Move arg0 toward arg1, and slow down before reaching the end.
When farther than arg3 away, move by at most arg2.
When closer than arg3, linearly ramp down the movement amount from arg2 to 0 but no lower than arg4."
When farther than arg3 away, move by at most arg2.
When closer than arg3, linearly ramp down the movement amount from arg2 to 0 but no lower than arg4."
(let ((f2-0 (- arg1 arg0)))
(when (>= arg3 (fabs f2-0))
(set! arg2 (* arg2 (- 1.0 (/ (- arg3 (fabs f2-0)) arg3))))
@ -366,9 +366,9 @@ When closer than arg3, linearly ramp down the movement amount from arg2 to 0 but
;; definition for function seek-ease-in-out
(defun seek-ease-in-out ((arg0 float) (arg1 float) (arg2 float) (arg3 float) (arg4 float) (arg5 float) (arg6 float))
"Move arg0 toward arg2, and slow down at the start and end.
When within arg4 of arg1 (at the beginning of movement), ramp up speed, with a minimum speed of arg6
When within arg5 of arg2 (at the end of movement), ramp down speed, with a minimum speed of arg5
Normally, move at most arg3"
When within arg4 of arg1 (at the beginning of movement), ramp up speed, with a minimum speed of arg6
When within arg5 of arg2 (at the end of movement), ramp down speed, with a minimum speed of arg5
Normally, move at most arg3"
(let ((f2-0 (- arg2 arg0)))
(let ((f4-1 (- arg0 arg1)))
(when (>= arg4 (fabs f4-1))
@ -407,7 +407,7 @@ Normally, move at most arg3"
;; definition (debug) for function lerp-scale-old
(defun-debug lerp-scale-old ((arg0 float) (arg1 float) (arg2 float) (arg3 float) (arg4 float))
"Linearly remap arg2 in [arg3, arg4] to [arg0, arg1].
This is the jak 1 implementation, which I claimed was a bad implementation..."
This is the jak 1 implementation, which I claimed was a bad implementation..."
(let ((f0-1 (fmax 0.0 (fmin 1.0 (/ (- arg2 arg3) (- arg4 arg3))))))
(+ (* (- 1.0 f0-1) arg0) (* f0-1 arg1))
)
@ -418,7 +418,7 @@ This is the jak 1 implementation, which I claimed was a bad implementation..."
;; ERROR: Unsupported inline assembly instruction kind - [madd.s f0, f1, f3]
(defun lerp-scale ((arg0 float) (arg1 float) (arg2 float) (arg3 float) (arg4 float))
"Linearly remap arg2 in [arg3, arg4] to [arg0, arg1].
More efficient than the -old version."
More efficient than the -old version."
(local-vars (f0-2 float))
(let* ((v1-0 1.0)
(f1-0 0.0)
@ -563,8 +563,8 @@ More efficient than the -old version."
;; ERROR: Inline assembly instruction marked with TODO - [TODO.VRGET]
(defun rand-vu-nostep ()
"Get the number currently in the random generator.
This will be equal to the last call of (rand-vu).
This will not update the random generator."
This will be equal to the last call of (rand-vu).
This will not update the random generator."
(local-vars (v0-0 float))
(rlet ((vf0 :class vf)
(vf1 :class vf)
@ -614,7 +614,7 @@ This will not update the random generator."
;; WARN: new jak 2 until loop case, check carefully
(defun rand-vu-int-count-excluding ((arg0 int) (arg1 int))
"Get an integer in the range [0, arg0).
If bit n is set in arg1, exclude this value from being returned."
If bit n is set in arg1, exclude this value from being returned."
(let ((s4-0 0)
(s5-0 0)
)
@ -657,7 +657,7 @@ If bit n is set in arg1, exclude this value from being returned."
;; WARN: new jak 2 until loop case, check carefully
(defun rand-vu-int-range-exclude ((arg0 int) (arg1 int) (arg2 int))
"Get an integer in the range [0, arg0), excluding arg2.
Note that this doesn't use bits like rand-vu-int-count-excluding."
Note that this doesn't use bits like rand-vu-int-count-excluding."
(until #f
(let ((v1-0 (rand-vu-int-range arg0 arg1)))
(if (!= v1-0 arg2)
@ -742,7 +742,7 @@ Note that this doesn't use bits like rand-vu-int-count-excluding."
;; definition for function smooth-step
(defun smooth-step ((arg0 float))
"Interpolate between 0, 1 with a cubic polynomial.
These are picked so f(0) = 0, f(1) = 1, f'(0) = f'(1) = 0."
These are picked so f(0) = 0, f(1) = 1, f'(0) = f'(1) = 0."
(cond
((>= 0.0 arg0)
0.0
@ -759,13 +759,9 @@ These are picked so f(0) = 0, f(1) = 1, f'(0) = f'(1) = 0."
;; definition for function smooth-interp
(defun smooth-interp ((arg0 float) (arg1 float) (arg2 float) (arg3 float) (arg4 float))
"Remap arg2 from (arg3, arg4) to (arg0, arg1), using cubic interpolation.
Satisfies:
- f(arg3) = arg0
- f(arg4) = arg1
- f'(arg3) = f'(arg4) = 0"
Satisfies:
- f(arg3) = arg0
- f(arg4) = arg1
- f'(arg3) = f'(arg4) = 0"
(+ arg0 (* (- arg1 arg0) (smooth-step (/ (- arg2 arg3) (- arg4 arg3)))))
)

View File

@ -52,7 +52,7 @@
;; definition for function matrix+!
(defun matrix+! ((arg0 matrix) (arg1 matrix) (arg2 matrix))
"Set dst = src1 + src2. It is okay for any arguments to be the same data.
This is not an efficient implementation."
This is not an efficient implementation."
(dotimes (v1-0 16)
(set! (-> arg0 rvec data v1-0) (+ (-> arg1 rvec data v1-0) (-> arg2 rvec data v1-0)))
)
@ -62,7 +62,7 @@ This is not an efficient implementation."
;; definition for function matrix-!
(defun matrix-! ((arg0 matrix) (arg1 matrix) (arg2 matrix))
"Set dst = src1 - src1. It is okay for any arugments to be the same data.
This is not an efficient implementation."
This is not an efficient implementation."
(dotimes (v1-0 16)
(set! (-> arg0 rvec data v1-0) (- (-> arg1 rvec data v1-0) (-> arg2 rvec data v1-0)))
)
@ -72,7 +72,7 @@ This is not an efficient implementation."
;; definition for function matrix*!
(defun matrix*! ((arg0 matrix) (arg1 matrix) (arg2 matrix))
"Set dst = src1 * src2. It is okay for any arguments to be the same data.
This is a moderately efficient implementation."
This is a moderately efficient implementation."
(rlet ((acc :class vf)
(vf10 :class vf)
(vf11 :class vf)
@ -123,8 +123,8 @@ This is a moderately efficient implementation."
;; INFO: Used lq/sq
(defun matrixp*! ((arg0 matrix) (arg1 matrix) (arg2 matrix))
"Set dst = src1 * src2. NOTE: this function is a wrapper around matrix*!
that adds no additional functionality. It seems to be a leftover from
a time when matrix*! wasn't safe to use in place. This is unused."
that adds no additional functionality. It seems to be a leftover from
a time when matrix*! wasn't safe to use in place. This is unused."
(let ((s5-0 (new 'stack-no-clear 'matrix)))
(set! (-> s5-0 rvec quad) (the-as uint128 0))
(set! (-> s5-0 uvec quad) (the-as uint128 0))
@ -198,8 +198,8 @@ a time when matrix*! wasn't safe to use in place. This is unused."
;; definition for function vector-rotate*!
(defun vector-rotate*! ((arg0 vector) (arg1 vector) (arg2 matrix))
"Set dst to be the input vector rotated by the rotation part of mat.
The input matrix should be a homogeneous transform with a rotation matrix as its upper-left 3x3.
dst may be equal to src."
The input matrix should be a homogeneous transform with a rotation matrix as its upper-left 3x3.
dst may be equal to src."
(rlet ((acc :class vf)
(vf1 :class vf)
(vf2 :class vf)
@ -230,7 +230,7 @@ dst may be equal to src."
;; INFO: Used lq/sq
(defun vector3s-matrix*! ((arg0 vector3s) (arg1 vector3s) (arg2 matrix))
"Set dst to be ([src 1.0] * mat).xyz. Doesn't touch the w of dst.
dst and vec can be the same memory"
dst and vec can be the same memory"
(let ((s5-0 (new-stack-vector0)))
(set-vector! s5-0 (-> arg1 x) (-> arg1 y) (-> arg1 z) 1.0)
(vector-matrix*! s5-0 s5-0 arg2)
@ -245,7 +245,7 @@ dst and vec can be the same memory"
;; INFO: Used lq/sq
(defun vector3s-rotate*! ((arg0 vector3s) (arg1 vector3s) (arg2 matrix))
"Set dst to vec rotated by the rotation in the homogeneous transform mat.
mat should not have a scale/shear (the upper 3x3 should be a pure rotation)."
mat should not have a scale/shear (the upper 3x3 should be a pure rotation)."
(let ((s5-0 (new-stack-vector0)))
(set-vector! s5-0 (-> arg1 x) (-> arg1 y) (-> arg1 z) 1.0)
(vector-rotate*! s5-0 s5-0 arg2)
@ -298,7 +298,7 @@ mat should not have a scale/shear (the upper 3x3 should be a pure rotation)."
;; definition for function matrix-inverse-of-rot-trans!
(defun matrix-inverse-of-rot-trans! ((arg0 matrix) (arg1 matrix))
"Set dst = src^-1, assuming src is a homogeneous tranform with only rotation/translation.
NOTE: THIS FUNCTION REQUIRES dst != src"
NOTE: THIS FUNCTION REQUIRES dst != src"
(rlet ((acc :class vf)
(vf0 :class vf)
(vf1 :class vf)
@ -335,7 +335,7 @@ NOTE: THIS FUNCTION REQUIRES dst != src"
;; ERROR: Bad vector register dependency: vf5
(defun matrix-4x4-inverse! ((arg0 matrix) (arg1 matrix))
"Invert a 4x4 matrix. This assumes that the input is a homogeneous transform.
Src and dst can be the same."
Src and dst can be the same."
(rlet ((acc :class vf)
(Q :class vf)
(vf0 :class vf)
@ -499,7 +499,7 @@ Src and dst can be the same."
;; INFO: Used lq/sq
(defun matrix-translate+! ((arg0 matrix) (arg1 matrix) (arg2 vector))
"Add the given translation to the translation of homogenous transform mat src
and store in dst. It is okay for dst = src."
and store in dst. It is okay for dst = src."
(set! (-> arg0 trans x) (+ (-> arg1 trans x) (-> arg2 x)))
(set! (-> arg0 trans y) (+ (-> arg1 trans y) (-> arg2 y)))
(set! (-> arg0 trans z) (+ (-> arg1 trans z) (-> arg2 z)))
@ -515,7 +515,7 @@ and store in dst. It is okay for dst = src."
;; INFO: Used lq/sq
(defun matrix-scale! ((arg0 matrix) (arg1 vector))
"Set dst to a homogenous transform with only a scale. The x,y,z components
of scale become the x,y,z scaling factors"
of scale become the x,y,z scaling factors"
(set! (-> arg0 rvec quad) (the-as uint128 0))
(set! (-> arg0 uvec quad) (the-as uint128 0))
(set! (-> arg0 fvec quad) (the-as uint128 0))
@ -530,8 +530,8 @@ of scale become the x,y,z scaling factors"
;; definition for function scale-matrix!
(defun scale-matrix! ((arg0 matrix) (arg1 vector) (arg2 matrix))
"Scale an existing matrix. Okay for dst = src. The scaling is applied per row.
This means the x component of scale is used to scale the first row of src.
The w component of scale is used."
This means the x component of scale is used to scale the first row of src.
The w component of scale is used."
(rlet ((vf4 :class vf)
(vf5 :class vf)
(vf6 :class vf)
@ -559,7 +559,7 @@ The w component of scale is used."
;; INFO: Used lq/sq
(defun matrix-inv-scale! ((arg0 matrix) (arg1 vector))
"Set dst to a homogeneous transform with only a scale.
The x,y,z components of scale are inverted and used as the x,y,z scaling factors"
The x,y,z components of scale are inverted and used as the x,y,z scaling factors"
(set! (-> arg0 rvec quad) (the-as uint128 0))
(set! (-> arg0 uvec quad) (the-as uint128 0))
(set! (-> arg0 fvec quad) (the-as uint128 0))
@ -574,7 +574,7 @@ The x,y,z components of scale are inverted and used as the x,y,z scaling factors
;; definition for function column-scale-matrix!
(defun column-scale-matrix! ((arg0 matrix) (arg1 vector) (arg2 matrix))
"Scale an existing matrix. Okay for dst = src. The scaling is applied column-wise.
Meaning the x component of scale will scale the first column of src."
Meaning the x component of scale will scale the first column of src."
(rlet ((vf4 :class vf)
(vf5 :class vf)
(vf6 :class vf)
@ -815,7 +815,7 @@ Meaning the x component of scale will scale the first column of src."
;; definition for function matrix-rotate-yxy!
(defun matrix-rotate-yxy! ((arg0 matrix) (arg1 vector))
"Rotate. I believe in yxy order? Compared to the other rotations, this one
is quite a bit more optimized and avoid repeated trig operations."
is quite a bit more optimized and avoid repeated trig operations."
(let ((a2-0 (new 'stack-no-clear 'vector))
(s5-0 (new 'stack-no-clear 'vector))
(s4-0 (new 'stack-no-clear 'vector))
@ -1194,7 +1194,7 @@ is quite a bit more optimized and avoid repeated trig operations."
;; definition for function matrix-3x3-inverse!
(defun matrix-3x3-inverse! ((arg0 matrix) (arg1 matrix))
"Compute the inverse of a 3x3 matrix. Not very efficient.
Requires src != dst."
Requires src != dst."
(let ((f0-0 (matrix-3x3-determinant arg1)))
(set! (-> arg0 rvec x)
(/ (- (* (-> arg1 uvec y) (-> arg1 fvec z)) (* (-> arg1 uvec z) (-> arg1 fvec y))) f0-0)
@ -1230,7 +1230,7 @@ Requires src != dst."
;; definition for function matrix-3x3-inverse-transpose!
(defun matrix-3x3-inverse-transpose! ((arg0 matrix) (arg1 matrix))
"Invert and transpose.
Requires dst != src."
Requires dst != src."
(let ((f0-0 (matrix-3x3-determinant arg1)))
(set! (-> arg0 rvec x)
(/ (- (* (-> arg1 uvec y) (-> arg1 fvec z)) (* (-> arg1 uvec z) (-> arg1 fvec y))) f0-0)
@ -1426,7 +1426,7 @@ Requires dst != src."
;; definition for function matrix-4x4-inverse-transpose!
(defun matrix-4x4-inverse-transpose! ((arg0 matrix) (arg1 matrix))
"Invert and transpose an entire 4x4. I think has no restrictions, other than dst != src. Unused.
The answer is wrong. The determinant function is wrong."
The answer is wrong. The determinant function is wrong."
(let ((f0-0 (matrix-4x4-determinant arg1)))
(let ((f9-0 (-> arg1 uvec y))
(f2-0 (-> arg1 uvec z))
@ -1771,7 +1771,7 @@ The answer is wrong. The determinant function is wrong."
;; INFO: Used lq/sq
(defun matrix->quat ((arg0 matrix) (arg1 quaternion))
"Convert matrix to quaternion, works for matrix with scale.
unlike matrix->quaternion."
unlike matrix->quaternion."
(let ((s5-0 (new 'stack-no-clear 'matrix)))
(let* ((a2-0 arg0)
(v1-0 (-> a2-0 rvec quad))
@ -1901,7 +1901,3 @@ unlike matrix->quaternion."
)
arg0
)

View File

@ -184,8 +184,8 @@
;; ERROR: Bad vector register dependency: vf2
(defun quaternion-conjugate! ((arg0 quaternion) (arg1 quaternion))
"Set arg0 to the conjugate of arg1 (negate only ijk).
If arg1 is normalized, this is equivalent to the inverse
NOTE: this gives you the inverse rotation."
If arg1 is normalized, this is equivalent to the inverse
NOTE: this gives you the inverse rotation."
(rlet ((vf1 :class vf)
(vf2 :class vf)
)
@ -290,7 +290,7 @@ NOTE: this gives you the inverse rotation."
;; ERROR: Bad vector register dependency: vf3
(defun quaternion-inverse! ((arg0 quaternion) (arg1 quaternion))
"Invert a quaternion. The inverse will satisfy q * q^-1 = identity, even if q is not normalized.
If your quaternion is normalized, it is faster/more accurate to do quaternion-conjugate!"
If your quaternion is normalized, it is faster/more accurate to do quaternion-conjugate!"
(rlet ((acc :class vf)
(Q :class vf)
(vf0 :class vf)
@ -371,9 +371,9 @@ If your quaternion is normalized, it is faster/more accurate to do quaternion-co
;; definition for function quaternion-right-mult-matrix!
(defun quaternion-right-mult-matrix! ((arg0 matrix) (arg1 quaternion))
"Place quaternion coefficients into a matrix.
You can convert a quaternion to a matrix by taking the product of this
right-mult and left-mult matrix, but this method is not used.
Instead, quaternion->matrix is a more efficient implementation."
You can convert a quaternion to a matrix by taking the product of this
right-mult and left-mult matrix, but this method is not used.
Instead, quaternion->matrix is a more efficient implementation."
(let ((f3-0 (-> arg1 x))
(f2-0 (-> arg1 y))
(f1-0 (-> arg1 z))
@ -663,7 +663,7 @@ Instead, quaternion->matrix is a more efficient implementation."
;; definition for function quaternion-slerp!
(defun quaternion-slerp! ((arg0 quaternion) (arg1 quaternion) (arg2 quaternion) (arg3 float))
"Real quaternion slerp. Spherical-linear interpolation is a nice way to interpolate
between quaternions."
between quaternions."
(local-vars (v1-15 float))
(rlet ((acc :class vf)
(vf1 :class vf)
@ -729,8 +729,8 @@ between quaternions."
;; definition for function quaternion-pseudo-slerp!
(defun quaternion-pseudo-slerp! ((arg0 quaternion) (arg1 quaternion) (arg2 quaternion) (arg3 float))
"This is a bad interpolation between quaternions. It lerps then normalizes.
It will behave extremely poorly for 180 rotations.
It is unused."
It will behave extremely poorly for 180 rotations.
It is unused."
(rlet ((acc :class vf)
(vf1 :class vf)
(vf2 :class vf)
@ -764,7 +764,7 @@ It is unused."
;; definition for function quaternion-pseudo-seek
(defun quaternion-pseudo-seek ((arg0 quaternion) (arg1 quaternion) (arg2 quaternion) (arg3 float))
"Seek one quaternion toward another. Not using real slerp, so this is only good if the quaternions
are pretty similar."
are pretty similar."
(let ((s3-0 (new 'stack-no-clear 'quaternion)))
(let ((s5-0 (new 'stack-no-clear 'quaternion)))
(quaternion-copy! s3-0 arg2)
@ -1086,7 +1086,3 @@ are pretty similar."
(vector-y-angle s5-0)
)
)

View File

@ -112,20 +112,16 @@ As a result, this type has a lot of weird methods and extra stuff hidden in it."
;; definition for method 23 of type trsqv
(defmethod global-y-angle-to-point ((this trsqv) (arg0 vector))
"Get the angle in the xz plane from the position of this trsqv to the point arg0
(ignores our current yaw)."
(ignores our current yaw)."
(vector-y-angle (vector-! (new 'stack-no-clear 'vector) arg0 (-> this trans)))
)
;; definition for method 24 of type trsqv
(defmethod relative-y-angle-to-point ((this trsqv) (arg0 vector))
"Get the y angle between the current orientation and arg0
(how much we'd have to yaw to point at arg0)."
(how much we'd have to yaw to point at arg0)."
(deg-diff (y-angle this) (vector-y-angle (vector-! (new 'stack-no-clear 'vector) arg0 (-> this trans))))
)
;; failed to figure out what this is:
0

View File

@ -15,7 +15,7 @@
;; definition for function deg-
(defun deg- ((arg0 float) (arg1 float))
"Compute arg0-arg1, unwrapped, using rotation units.
Result should be in the range (-180, 180)"
Result should be in the range (-180, 180)"
(the float (sar (- (shl (the int arg0) 48) (shl (the int arg1) 48)) 48))
)
@ -195,9 +195,9 @@ Result should be in the range (-180, 180)"
;; definition for function vector-sin-rad!
(defun vector-sin-rad! ((arg0 vector) (arg1 vector))
"Taylor series approximation of sine on all 4 elements in a vector.
Inputs should be in radians, in -pi to pi.
Somehow their coefficients are a little bit off.
Like the first coefficient, which should obviously be 1, is not quite 1."
Inputs should be in radians, in -pi to pi.
Somehow their coefficients are a little bit off.
Like the first coefficient, which should obviously be 1, is not quite 1."
(rlet ((acc :class vf)
(vf1 :class vf)
(vf10 :class vf)
@ -239,7 +239,7 @@ Like the first coefficient, which should obviously be 1, is not quite 1."
;; ERROR: Bad vector register dependency: vf2
(defun vector-cos-rad! ((arg0 vector) (arg1 vector))
"Compute the cosine of all 4 vector elements.
Radians, with no wrapping. Uses taylor series with 4 coefficients."
Radians, with no wrapping. Uses taylor series with 4 coefficients."
(rlet ((acc :class vf)
(vf0 :class vf)
(vf1 :class vf)
@ -272,8 +272,8 @@ Radians, with no wrapping. Uses taylor series with 4 coefficients."
;; ERROR: Bad vector register dependency: vf14
(defun vector-sincos-rad! ((arg0 vector) (arg1 vector) (arg2 vector))
"Compute the sine and cosine of each element of src, storing it in dst-sin and dst-cos.
This is more efficient than separate calls to sin and cos.
Inputs should be radians in -pi to pi."
This is more efficient than separate calls to sin and cos.
Inputs should be radians in -pi to pi."
(rlet ((acc :class vf)
(vf0 :class vf)
(vf1 :class vf)
@ -331,7 +331,7 @@ Inputs should be radians in -pi to pi."
;; WARN: Return type mismatch float vs none.
(defun vector-rad<-vector-deg! ((out vector) (in vector))
"Convert a vector in rotation units to radians, and unwrap.
Input can be anything, output will be -2pi to pi."
Input can be anything, output will be -2pi to pi."
(local-vars (v0-0 float) (v1-1 uint128) (v1-2 uint128) (v1-3 uint128))
(rlet ((vf1 :class vf)
(vf2 :class vf)
@ -357,7 +357,7 @@ Input can be anything, output will be -2pi to pi."
;; WARN: Return type mismatch float vs int.
(defun vector-rad<-vector-deg/2! ((out vector) (in vector))
"Divide the input by two, and then convert from rotation units to radians, unwrapping.
Not sure why this really needs to be separate the from previous function..."
Not sure why this really needs to be separate the from previous function..."
(local-vars (v0-0 float) (v1-1 uint128) (v1-2 uint128) (v1-3 uint128))
(rlet ((vf1 :class vf)
(vf2 :class vf)
@ -438,7 +438,7 @@ Not sure why this really needs to be separate the from previous function..."
;; definition for function sign
(defun sign ((arg0 float))
"Similar to above, but returns 0 if input is 0.
But is more complicated."
But is more complicated."
(cond
((< 0.0 arg0)
1.0
@ -658,7 +658,3 @@ But is more complicated."
)
)
)

View File

@ -923,8 +923,8 @@
;; definition for function vector-dot
(defun vector-dot ((arg0 vector) (arg1 vector))
"Take the dot product of two vectors.
Only does the x, y, z compoments.
Originally handwritten assembly to space out loads and use FPU accumulator"
Only does the x, y, z compoments.
Originally handwritten assembly to space out loads and use FPU accumulator"
(vector-dot arg0 arg1)
)
@ -948,7 +948,7 @@ Originally handwritten assembly to space out loads and use FPU accumulator"
;; definition for function vector4-dot
(defun vector4-dot ((arg0 vector) (arg1 vector))
"Take the dot product of two vectors.
Does the x, y, z, and w compoments"
Does the x, y, z, and w compoments"
(vector4-dot arg0 arg1)
)
@ -1033,7 +1033,3 @@ Does the x, y, z, and w compoments"
;; failed to figure out what this is:
0

1551
test/decompiler/reference/jak3/kernel/gcommon_REF.gc generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,13 @@
"skip_compile_files": [],
"skip_compile_functions": [
// gcommon
"qmem-clear!",
"(method 9 inline-array-class-uint32)",
"(method 9 inline-array-class-uint64)",
"breakpoint-range-set!",
"valid?",
// math
"logf",
"log2f",

6
test_no_filter.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Directory of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
$DIR/build/goalc-test --gtest_color=yes "$@"