diff --git a/common/type_system/TypeFieldLookup.cpp b/common/type_system/TypeFieldLookup.cpp index 50e0ef6557..a75253987f 100644 --- a/common/type_system/TypeFieldLookup.cpp +++ b/common/type_system/TypeFieldLookup.cpp @@ -272,6 +272,7 @@ void try_reverse_lookup_inline_array(const FieldReverseLookupInput& input, next_input.stride = 0; next_input.offset = input.offset; // includes the offset. next_input.base_type = di.result_type; + next_input.field_score_overrides = input.field_score_overrides; try_reverse_lookup(next_input, ts, &var_idx_node, output, max_count); return; } @@ -318,6 +319,7 @@ void try_reverse_lookup_inline_array(const FieldReverseLookupInput& input, next_input.stride = input.stride; next_input.offset = offset_into_elt; next_input.base_type = di.result_type; + next_input.field_score_overrides = input.field_score_overrides; try_reverse_lookup(next_input, ts, &const_idx_node, output, max_count); } @@ -368,6 +370,12 @@ void try_reverse_lookup_other(const FieldReverseLookupInput& input, token.kind = FieldReverseLookupOutput::Token::Kind::FIELD; token.name = field.name(); token.field_score = field.field_score(); + for (const auto& score_override : input.field_score_overrides) { + if (score_override.type_name == input.base_type.base_type() && + score_override.field_name == field.name()) { + token.field_score += score_override.score; + } + } if (field_deref.needs_deref) { if (offset_into_field == 0) { @@ -440,6 +448,7 @@ void try_reverse_lookup_other(const FieldReverseLookupInput& input, next_input.offset = offset_into_field - expected_offset_into_field; next_input.stride = input.stride; next_input.base_type = field_deref.type; + next_input.field_score_overrides = input.field_score_overrides; ReverseLookupNode node; node.prev = parent; node.token = token; diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index dbd61bd825..71ece9447c 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -64,11 +64,20 @@ struct DerefKind { RegClass reg_kind = RegClass::INVALID; }; +struct FieldReverseLookupScoreOverride { + std::string type_name; + std::string field_name; + double score = 0; +}; + struct FieldReverseLookupInput { std::optional deref = std::nullopt; // if we actually access memory int offset = 0; // if we apply a constant offset int stride = 0; // if we are doing a + (idx * stride) TypeSpec base_type; // the type of the thing we're accessing + // Per-call scoring adjustments for ambiguous overlay fields. Lookup still discovers and + // validates every candidate normally; these only affect which valid candidate is preferred. + std::vector field_score_overrides; }; constexpr double CONSTANT_INDEX_SCORE = -10.0; diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index 607c4cd3b0..fec2781538 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -76,7 +76,7 @@ FormElement* SetVarOp::get_as_form(FormPool& pool, const Env& env) const { rd_in.stride = 0; rd_in.offset = m_src.get_arg(1).get_int(); rd_in.base_type = arg0_type.typespec(); - auto rd = env.dts->ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { auto source = pool.alloc_single_element_form( @@ -235,7 +235,7 @@ FormElement* StoreOp::get_vf_store_as_form(FormPool& pool, const Env& env) const rd_in.base_type = input_type.typespec(); rd_in.stride = 0; rd_in.offset = ro.offset; - auto rd = env.dts->ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { auto source = pool.alloc_single_element_form( @@ -251,7 +251,7 @@ FormElement* StoreOp::get_vf_store_as_form(FormPool& pool, const Env& env) const } else { // try again with no deref. rd_in.deref = {}; - auto rd_no_deref = env.dts->ts.reverse_field_lookup(rd_in); + auto rd_no_deref = env.reverse_field_lookup(rd_in); if (rd_no_deref.success) { auto source = pool.alloc_single_element_form( nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx); @@ -378,7 +378,7 @@ FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const { rd_in.base_type = input_type.get_obj_plus_const_mult_typespec(); rd_in.stride = input_type.get_multiplier(); rd_in.offset = ro.offset; - auto rd = env.dts->ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { std::vector tokens; @@ -412,7 +412,7 @@ FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const { rd_in.base_type = input_type.typespec(); rd_in.stride = 0; rd_in.offset = ro.offset; - auto rd = env.dts->ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { auto source = pool.alloc_single_element_form( @@ -626,7 +626,7 @@ Form* LoadVarOp::get_load_src(FormPool& pool, const Env& env) const { rd_in.stride = 1; } rd_in.offset = ro.offset; - auto rd = env.dts->ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { std::vector tokens; @@ -673,7 +673,7 @@ Form* LoadVarOp::get_load_src(FormPool& pool, const Env& env) const { rd_in.base_type = input_type.typespec(); rd_in.stride = 0; rd_in.offset = ro.offset; - auto rd = env.dts->ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); // todo, error here? diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 110e66f2ec..d30bc3614b 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -318,7 +318,7 @@ TP_Type get_stack_type_at_constant_offset(int offset, rd_in.stride = 0; // not a strided access rd_in.offset = offset - var.hint.stack_offset; // offset into this var rd_in.base_type = var.ref_type; // use ref type for ptr. - auto rd = dts.ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { auto result = TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type)); lg::print("Matched a stack variable! {}\n", result.print()); @@ -476,7 +476,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.offset = arg1_type.get_integer_constant(); rd_in.stride = 0; rd_in.base_type = arg0_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(rd_in); + auto out = env.reverse_field_lookup(rd_in); if (out.success) { sum_type = coerce_to_reg_type(out.result_type); } @@ -514,7 +514,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.offset = arg0_type.get_add_int_constant(); rd_in.stride = arg0_type.get_mult_int_constant(); rd_in.base_type = arg1_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(rd_in); + auto out = env.reverse_field_lookup(rd_in); if (out.success) { return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type)); } @@ -524,7 +524,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.offset = arg1_type.get_add_int_constant(); rd_in.stride = arg1_type.get_mult_int_constant(); rd_in.base_type = arg0_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(rd_in); + auto out = env.reverse_field_lookup(rd_in); if (out.success) { return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type)); } @@ -535,7 +535,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.offset = arg0_type.get_integer_constant(); rd_in.stride = 1; rd_in.base_type = arg1_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(rd_in); + auto out = env.reverse_field_lookup(rd_in); if (out.success) { return TP_Type::make_from_ts(coerce_to_reg_type(out.result_type)); } @@ -577,7 +577,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.stride = 0; rd_in.offset = m_args[1].get_int(); rd_in.base_type = arg0_type.typespec(); - auto rd = dts.ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type)); @@ -593,7 +593,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.stride = arg1_type.get_multiplier(); rd_in.offset = 0; rd_in.base_type = arg0_type.typespec(); - auto rd = dts.ts.reverse_field_multi_lookup(rd_in); + auto rd = env.reverse_field_multi_lookup(rd_in); for (int i = 0; i < (int)rd.results.size(); i++) { if (rd.results.at(i).has_variable_token()) { @@ -611,7 +611,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.stride = arg0_type.get_multiplier(); rd_in.offset = 0; rd_in.base_type = arg1_type.typespec(); - auto rd = dts.ts.reverse_field_multi_lookup(rd_in); + auto rd = env.reverse_field_multi_lookup(rd_in); for (int i = 0; i < (int)rd.results.size(); i++) { if (rd.results.at(i).has_variable_token()) { @@ -698,7 +698,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, rd_in.offset = arg0_type.get_integer_constant(); rd_in.stride = 0; rd_in.base_type = arg1_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(rd_in); + auto out = env.reverse_field_lookup(rd_in); if (out.success) { sum_type = coerce_to_reg_type(out.result_type); } @@ -1100,7 +1100,7 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input, rd_in.base_type = input_type.get_obj_plus_const_mult_typespec(); rd_in.stride = input_type.get_multiplier(); rd_in.offset = ro.offset; - auto rd = dts.ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { // load_path_set = true; @@ -1145,7 +1145,7 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input, rd_in.base_type = input_type.typespec(); rd_in.stride = 0; rd_in.offset = ro.offset; - auto rd = dts.ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { if (rd_in.base_type.base_type() == "state" && rd.tokens.size() == 1 && @@ -1210,7 +1210,7 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input, rd_in.deref = dk; rd_in.base_type = input_type.get_objects_typespec(); rd_in.offset = ro.offset; - auto rd = dts.ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type)); } @@ -1227,7 +1227,7 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input, rd_in.base_type = input_type.get_objects_typespec(); rd_in.stride = 0; rd_in.offset = input_type.get_integer_constant(); - auto rd = dts.ts.reverse_field_lookup(rd_in); + auto rd = env.reverse_field_lookup(rd_in); if (rd.success) { return TP_Type::make_from_ts(coerce_to_reg_type(rd.result_type)); } diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index 9773a76301..389c430640 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -17,6 +17,17 @@ namespace decompiler { +FieldReverseLookupOutput Env::reverse_field_lookup(FieldReverseLookupInput input) const { + input.field_score_overrides = m_reverse_lookup_field_score_overrides; + return dts->ts.reverse_field_lookup(input); +} + +FieldReverseMultiLookupOutput Env::reverse_field_multi_lookup(FieldReverseLookupInput input, + int max_count) const { + input.field_score_overrides = m_reverse_lookup_field_score_overrides; + return dts->ts.reverse_field_multi_lookup(input, max_count); +} + constexpr const char* reg_names[] = {"a0-0", "a1-0", "a2-0", "a3-0", "t0-0", "t1-0", "t2-0", "t3-0"}; diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index 40c0b08d3f..c4b6506825 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -4,6 +4,7 @@ #include #include "common/goos/Object.h" +#include "common/type_system/TypeSystem.h" #include "common/util/Assert.h" #include "decompiler/Disasm/Register.h" @@ -187,6 +188,13 @@ class Env { void set_scratchpad_type(const TypeSpec& type) { m_scratchpad_type = type; } const std::optional& scratchpad_type() const { return m_scratchpad_type; } + void set_reverse_lookup_field_score_overrides( + std::vector overrides) { + m_reverse_lookup_field_score_overrides = std::move(overrides); + } + FieldReverseLookupOutput reverse_field_lookup(FieldReverseLookupInput input) const; + FieldReverseMultiLookupOutput reverse_field_multi_lookup(FieldReverseLookupInput input, + int max_count = 100) const; void note_scratchpad_access() const { m_uses_scratchpad = true; } bool uses_scratchpad() const { return m_uses_scratchpad; } @@ -288,6 +296,7 @@ class Env { std::unordered_map> m_typecasts; std::unordered_map m_stack_typecasts; std::optional m_scratchpad_type; + std::vector m_reverse_lookup_field_score_overrides; mutable bool m_uses_scratchpad = false; std::vector m_stack_structures; std::unordered_map m_var_remap; diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index fc5a1330b1..933b096665 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -671,8 +671,6 @@ Form* cast_form_from(Form* in, FormPool& pool, const Env& env, bool tc_pass = false) { - auto& ts = env.dts->ts; - auto form_type = try_get_deref_form_type(in, env); if (form_type == new_type) { return in; @@ -694,7 +692,7 @@ Form* cast_form_from(Form* in, suffix_lookup.offset = 0; suffix_lookup.stride = 0; suffix_lookup.base_type = new_type; - auto suffix_results = ts.reverse_field_multi_lookup(suffix_lookup); + auto suffix_results = env.reverse_field_multi_lookup(suffix_lookup); for (const auto& result : suffix_results.results) { if (!result.addr_of && result.result_type == *form_type && deref_suffix_matches_lookup(deref->tokens(), keep, result)) { @@ -717,7 +715,7 @@ Form* cast_form_from(Form* in, lookup_input.offset = 0; lookup_input.stride = 0; lookup_input.base_type = form_type.value_or(old_type); - auto lookup_result = ts.reverse_field_multi_lookup(lookup_input); + auto lookup_result = env.reverse_field_multi_lookup(lookup_input); if (lookup_result.success) { for (auto& result : lookup_result.results) { if (result.result_type == new_type) { @@ -1337,7 +1335,7 @@ DerefElement* try_reassociate_inline_array_field_access(Form* field_access, lookup.stride = stride; lookup.offset = 0; lookup.base_type = *base_type; - auto reverse = env.dts->ts.reverse_field_multi_lookup(lookup); + auto reverse = env.reverse_field_multi_lookup(lookup); for (const auto& candidate : reverse.results) { if (candidate.has_variable_token()) { auto tokens = deref->tokens(); @@ -1360,7 +1358,7 @@ DerefElement* try_reassociate_inline_array_field_access(Form* field_access, lookup.stride = stride; lookup.offset = 0; lookup.base_type = *field_type; - auto reverse = env.dts->ts.reverse_field_multi_lookup(lookup); + auto reverse = env.reverse_field_multi_lookup(lookup); for (const auto& candidate : reverse.results) { if (!candidate.has_variable_token()) { continue; @@ -1472,7 +1470,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, input.offset = arg0_type.get_integer_constant(); input.stride = 1; input.base_type = arg1_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(input); + auto out = env.reverse_field_lookup(input); if (out.success && out.has_variable_token()) { // it is. now we have to modify things // first, look for the index @@ -1509,7 +1507,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, input.offset = arg0_type.get_add_int_constant(); input.stride = arg0_type.get_mult_int_constant(); input.base_type = arg1_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(input); + auto out = env.reverse_field_lookup(input); if (out.success && out.has_variable_token()) { // it is. now we have to modify things // first, look for the index @@ -1585,7 +1583,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, rd_in.stride = arg1_type.get_multiplier(); rd_in.offset = 0; rd_in.base_type = arg0_type.typespec(); - auto rd = env.dts->ts.reverse_field_multi_lookup(rd_in); + auto rd = env.reverse_field_multi_lookup(rd_in); int idx_of_success = -1; if (rd.success) { for (int i = 0; i < (int)rd.results.size(); i++) { @@ -1637,7 +1635,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, rd_in.stride = arg0_type.get_multiplier(); rd_in.offset = 0; rd_in.base_type = arg1_type.typespec(); - auto rd = env.dts->ts.reverse_field_multi_lookup(rd_in); + auto rd = env.reverse_field_multi_lookup(rd_in); int idx_of_success = -1; if (rd.success) { for (int i = 0; i < (int)rd.results.size(); i++) { @@ -1689,7 +1687,7 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, input.offset = arg0_type.get_integer_constant(); input.stride = 0; input.base_type = arg1_type.typespec(); - auto out = env.dts->ts.reverse_field_lookup(input); + auto out = env.reverse_field_lookup(input); if (out.success && !out.has_variable_token()) { // it is. now we have to modify things // first, look for the index diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index de08114818..765183c71c 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -614,7 +614,13 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config, ObjectF auto scratchpad_type = config.scratchpad_types_by_object.find(obj_name); if (scratchpad_type != config.scratchpad_types_by_object.end()) { - func.ir2.env.set_scratchpad_type(TypeSpec(scratchpad_type->second)); + func.ir2.env.set_scratchpad_type(TypeSpec(scratchpad_type->second.type_name)); + std::vector score_overrides; + for (const auto& override_config : scratchpad_type->second.field_score_overrides) { + score_overrides.push_back( + {override_config.type_name, override_config.field_name, override_config.score}); + } + func.ir2.env.set_reverse_lookup_field_score_overrides(std::move(score_overrides)); } if (config.hacks.pair_functions_by_name.find(func_name) != diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 316b9cb254..d7c0e154a4 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -196,7 +196,22 @@ Config make_config_via_json(nlohmann::json& json) { if (json.contains("scratchpad_types_file")) { auto scratchpad_types_json = read_json_file_from_config(json, "scratchpad_types_file"); for (auto& kv : scratchpad_types_json.items()) { - config.scratchpad_types_by_object[kv.key()] = kv.value().get(); + ScratchpadTypeConfig scratchpad_config; + if (kv.value().is_string()) { + scratchpad_config.type_name = kv.value().get(); + } else { + scratchpad_config.type_name = kv.value().at("type").get(); + if (kv.value().contains("field_score_overrides")) { + for (const auto& score_json : kv.value().at("field_score_overrides")) { + ScratchpadTypeConfig::FieldScoreOverride score_override; + score_override.type_name = score_json.at("type").get(); + score_override.field_name = score_json.at("field").get(); + score_override.score = score_json.at("score").get(); + scratchpad_config.field_score_overrides.push_back(std::move(score_override)); + } + } + } + config.scratchpad_types_by_object[kv.key()] = std::move(scratchpad_config); } } diff --git a/decompiler/config.h b/decompiler/config.h index be033a27fa..a33f1e325e 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -28,6 +28,16 @@ struct StackTypeCast { std::string type_name; }; +struct ScratchpadTypeConfig { + std::string type_name; + struct FieldScoreOverride { + std::string type_name; + std::string field_name; + double score = 0; + }; + std::vector field_score_overrides; +}; + struct LabelConfigInfo { // if the label is a "value" type, it will be loaded directly into a register. // in all cases, this is a constant, either a 64-bit integer or a float. @@ -167,7 +177,7 @@ struct Config { std::unordered_map> label_types; std::unordered_map> stack_structure_hints_by_function; - std::unordered_map scratchpad_types_by_object; + std::unordered_map scratchpad_types_by_object; std::unordered_map object_patches; std::unordered_map bad_format_strings; diff --git a/decompiler/config/jak1/all-types.gc b/decompiler/config/jak1/all-types.gc index f5adbf4ea5..635f1d9825 100644 --- a/decompiler/config/jak1/all-types.gc +++ b/decompiler/config/jak1/all-types.gc @@ -4319,7 +4319,7 @@ sound." (_type_ int) int) ;; 12 (define-extern dma-send-chain-no-tte "Wait for a channel, flush the cache, and start a source-chain DMA without transferring its tags. This function is unused." (function dma-bank-source uint none)) (define-extern dma-send-chain-no-flush "Wait for a channel and start a source-chain DMA with tag transfer enabled without first flushing the data cache." (function dma-bank-source uint none)) (define-extern dma-send-to-spr "Transfer quadwords from main memory to scratchpad, optionally waiting for completion." (function uint uint uint symbol none)) -(define-extern dma-send-to-spr-no-flush "Transfer quadwords from main memory to scratchpad without flushing the data cache, optionally waiting for completion." (function uint uint uint symbol none)) +(define-extern dma-send-to-spr-no-flush "Transfer quadwords from main memory to scratchpad without flushing the data cache, optionally waiting for completion." (function pointer pointer uint symbol none)) (define-extern dma-send-from-spr "Transfer quadwords from scratchpad to main memory, optionally waiting for completion." (function uint uint uint symbol none)) (define-extern dma-send-from-spr-no-flush "Transfer quadwords from scratchpad to main memory without flushing the data cache, optionally waiting for completion." (function uint uint uint symbol none)) (define-extern dma-initialize "Mask the VIF0 and VIF1 DMAtag mismatch error required by the EE hardware workaround." (function none)) diff --git a/decompiler/config/jak1/demacro.jsonc b/decompiler/config/jak1/demacro.jsonc index dde123cb20..4906e6a124 100644 --- a/decompiler/config/jak1/demacro.jsonc +++ b/decompiler/config/jak1/demacro.jsonc @@ -155,6 +155,26 @@ {"count": "14", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12) :regs13 (gif-reg-id $reg13)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12 $reg13"}, {"count": "15", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12) :regs13 (gif-reg-id $reg13) :regs14 (gif-reg-id $reg14)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12 $reg13 $reg14"}, {"count": "16", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12) :regs13 (gif-reg-id $reg13) :regs14 (gif-reg-id $reg14) :regs15 (gif-reg-id $reg15)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12 $reg13 $reg14 $reg15"} + ], + // A static rgba constructor omits fields whose component is zero. Recover the source macro + // for every possible subset while supplying the omitted zeroes explicitly. + "static-rgba": [ + {"mask": "0000", "fields": "", "r": "0", "g": "0", "b": "0", "a": "0"}, + {"mask": "1000", "fields": ":r $r", "r": "$r", "g": "0", "b": "0", "a": "0"}, + {"mask": "0100", "fields": ":g $g", "r": "0", "g": "$g", "b": "0", "a": "0"}, + {"mask": "0010", "fields": ":b $b", "r": "0", "g": "0", "b": "$b", "a": "0"}, + {"mask": "0001", "fields": ":a $a", "r": "0", "g": "0", "b": "0", "a": "$a"}, + {"mask": "1100", "fields": ":r $r :g $g", "r": "$r", "g": "$g", "b": "0", "a": "0"}, + {"mask": "1010", "fields": ":r $r :b $b", "r": "$r", "g": "0", "b": "$b", "a": "0"}, + {"mask": "1001", "fields": ":r $r :a $a", "r": "$r", "g": "0", "b": "0", "a": "$a"}, + {"mask": "0110", "fields": ":g $g :b $b", "r": "0", "g": "$g", "b": "$b", "a": "0"}, + {"mask": "0101", "fields": ":g $g :a $a", "r": "0", "g": "$g", "b": "0", "a": "$a"}, + {"mask": "0011", "fields": ":b $b :a $a", "r": "0", "g": "0", "b": "$b", "a": "$a"}, + {"mask": "1110", "fields": ":r $r :g $g :b $b", "r": "$r", "g": "$g", "b": "$b", "a": "0"}, + {"mask": "1101", "fields": ":r $r :g $g :a $a", "r": "$r", "g": "$g", "b": "0", "a": "$a"}, + {"mask": "1011", "fields": ":r $r :b $b :a $a", "r": "$r", "g": "0", "b": "$b", "a": "$a"}, + {"mask": "0111", "fields": ":g $g :b $b :a $a", "r": "0", "g": "$g", "b": "$b", "a": "$a"}, + {"mask": "1111", "fields": ":r $r :g $g :b $b :a $a", "r": "$r", "g": "$g", "b": "$b", "a": "$a"} ] }, "rules": [ @@ -496,6 +516,17 @@ "match": "(new 'static 'gif-tag-regs {{fields}})", "rewrite": "(gs-reg-list {{regs}})" }, + { + "name": "static-rgba-{{mask}}", + "for_each": "static-rgba", + "match": "(new 'static 'rgba {{fields}})", + "rewrite": "(static-rgba {{r}} {{g}} {{b}} {{a}})" + }, + { + "name": "add-profile-frame", + "match": "(if *debug-segment* (add-frame (-> (current-frame) profile-bar 0) $name (static-rgba $r $g $b $a)))", + "rewrite": "(add-profile-frame! $r $g $b $a $name)" + }, { "name": "scratchpad-object-direct-{{type}}", "for_each": "scratchpad-object-type", diff --git a/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc b/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc index 2ec46b66a3..08041ce897 100644 --- a/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc +++ b/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc @@ -1,3 +1,9 @@ { - "cam-debug": "cam-dbg-scratch" + "cam-debug": "cam-dbg-scratch", + "bsp": { + "type": "terrain-context", + "field_score_overrides": [ + {"type": "work-area", "field": "background", "score": 1000} + ] + } } diff --git a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc index 9423e6fc53..0623771bdd 100644 --- a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc +++ b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc @@ -1208,16 +1208,7 @@ ], "(method 10 bsp-header)": [ [[51, 61], "a0", "(pointer uint128)"], - [[51, 61], "a1", "(pointer uint128)"], - [133, "v1", "terrain-bsp"], - [141, "v1", "terrain-bsp"], - [148, "v1", "terrain-bsp"], - [5, "a0", "terrain-bsp"], - [8, "a0", "terrain-bsp"] - ], - "(method 15 bsp-header)": [ - [5, "a0", "terrain-bsp"], - [8, "a0", "terrain-bsp"] + [[51, 61], "a1", "(pointer uint128)"] ], "upload-vis-bits": [ [[4, 16], "a1", "(pointer uint128)"], diff --git a/goal_src/jak1/engine/dma/dma.gc b/goal_src/jak1/engine/dma/dma.gc index a506a81cad..30c11559fb 100644 --- a/goal_src/jak1/engine/dma/dma.gc +++ b/goal_src/jak1/engine/dma/dma.gc @@ -150,7 +150,7 @@ (if wait-for-completion? (dma-sync (the-as pointer bank) 0 0)) (none)) -(defun dma-send-to-spr-no-flush ((scratchpad-address uint) (memory-address uint) (quadword-count uint) (wait-for-completion? symbol)) +(defun dma-send-to-spr-no-flush ((scratchpad-address pointer) (memory-address pointer) (quadword-count uint) (wait-for-completion? symbol)) "Transfer quadwords from main memory to scratchpad without flushing the data cache, optionally waiting for completion." (local-vars (bank dma-bank-spr)) diff --git a/goal_src/jak1/engine/geometry/cylinder.gc b/goal_src/jak1/engine/geometry/cylinder.gc index c307cb3055..cdebb372ba 100644 --- a/goal_src/jak1/engine/geometry/cylinder.gc +++ b/goal_src/jak1/engine/geometry/cylinder.gc @@ -9,34 +9,23 @@ ;; Both shapes are stored the same way: origin is one end of the centerline, axis is a unit ;; direction, length is the centerline length, and radius is the distance from the centerline. So the -;; far end is origin + axis * length, and a capsule extends radius beyond each end while a flat -;; cylinder does not. +;; far end is origin + axis * length. ;; ;; The intersect methods take a finite probe: the segment from probe-origin to ;; probe-origin + probe-displacement. They return the fraction of that displacement at which the -;; earliest contact happens, always between zero and one, or COLLISION_MISS. Compare a returned -;; fraction against zero before using it, since COLLISION_MISS is a large negative number rather -;; than a distinguished type. -;; -;; The debug-draw methods build one longitudinal outline, rotate it a sixteenth of a revolution at a -;; time, and draw both the rotated copy and the segment joining it to the previous one. The rotation -;; matrix has its translation row patched so it turns about the shape's own axis rather than the -;; world origin, which is what lets the same matrix be reapplied sixteen times. +;; earliest contact happens, always between zero and one, or COLLISION_MISS. ;; DECOMP BEGINS (defmethod ray-capsule-intersect ((this cylinder) (probe-origin vector) (probe-displacement vector)) "Test the finite probe origin + fraction * displacement against the cylindrical wall and both - hemispherical ends. Return the earliest zero-to-one fraction, or COLLISION_MISS. - The wall test's axis-point output is computed into a stack vector and discarded, so this method - reports when the capsule is hit but not where." + hemispherical ends. Return the earliest zero-to-one fraction, or COLLISION_MISS." (let ((axis-point (new 'stack-no-clear 'vector)) (end-center (new 'stack-no-clear 'vector))) 0.0 0.0 - ;; Test the wall first, then keep either spherical-cap contact when it is earlier. - ;; A negative wall fraction means the wall was missed, so the first cap contact replaces it - ;; outright; after that the comparison is a genuine "is this one earlier". + + ;; test wall (let ((contact-fraction (ray-cylinder-intersect probe-origin probe-displacement (-> this origin) @@ -44,13 +33,18 @@ (-> this radius) (-> this length) axis-point))) + ;; test near cap (let ((origin-cap-fraction (ray-sphere-intersect probe-origin probe-displacement (-> this origin) (-> this radius)))) (if (and (>= origin-cap-fraction 0.0) (or (< contact-fraction 0.0) (< origin-cap-fraction contact-fraction))) - (set! contact-fraction origin-cap-fraction))) + (set! contact-fraction origin-cap-fraction))) + + ;; test far cap (vector+float*! end-center (-> this origin) (-> this axis) (-> this length)) (let ((end-cap-fraction (ray-sphere-intersect probe-origin probe-displacement end-center (-> this radius)))) (if (and (>= end-cap-fraction 0.0) (or (< contact-fraction 0.0) (< end-cap-fraction contact-fraction))) - (set! contact-fraction end-cap-fraction))) + (set! contact-fraction end-cap-fraction))) + + ;; return closest hit. contact-fraction))) ;; This is used to hold vertices for debug-drawing cylinders. @@ -124,14 +118,11 @@ (defun ray-arbitrary-circle-intersect ((probe-origin vector) (probe-displacement vector) (circle-origin vector) (circle-normal vector) (radius float)) "Intersect the finite probe origin + fraction * displacement with the filled disk centered at circle-origin in circle-normal's plane. Return a zero-to-one fraction only when the plane contact - lies strictly inside radius; return COLLISION_MISS otherwise. - circle-normal need not be unit, since it appears in both halves of the ratio, but it must not be - perpendicular to probe-displacement: a displacement parallel to the plane divides by zero and the - resulting fraction is compared without any special case. A point exactly on the rim counts as a - miss, and the disk is two-sided." + lies strictly inside radius; return COLLISION_MISS otherwise." ;; The membership test squares both sides rather than taking a square root of the radial offset. (let* ((center-offset (vector-! (new 'stack-no-clear 'vector) circle-origin probe-origin)) - (contact-fraction (/ (vector-dot center-offset circle-normal) (vector-dot probe-displacement circle-normal)))) + (contact-fraction (/ (vector-dot center-offset circle-normal) + (vector-dot probe-displacement circle-normal)))) (cond ((or (< 1.0 contact-fraction) (< contact-fraction 0.0)) COLLISION_MISS) ((let ((radial-offset (new 'stack-no-clear 'vector))) @@ -143,9 +134,7 @@ (defmethod ray-flat-cyl-intersect ((this cylinder-flat) (probe-origin vector) (probe-displacement vector)) "Test the finite probe origin + fraction * displacement against the cylindrical wall and both - filled end caps. Return the earliest zero-to-one fraction, or COLLISION_MISS. - Unlike ray-capsule-intersect this keeps a contact position in a stack vector, overwriting it with - the relevant cap center when a cap wins, but that vector is local and is discarded on return." + filled end caps. Return the earliest zero-to-one fraction, or COLLISION_MISS." (let ((axis-point (new 'stack-no-clear 'vector)) (end-center (new 'stack-no-clear 'vector))) 0.0 @@ -217,7 +206,9 @@ (dotimes (vertex-index 10) (vector-matrix*! (-> rotated-vertices vert vertex-index) (-> vertices vert vertex-index) rotation-matrix) (camera-line (-> vertices vert vertex-index) (-> rotated-vertices vert vertex-index) color) - (if (nonzero? vertex-index) (camera-line (-> vertices vert vertex-index) (-> vertices vert (+ vertex-index -1)) color))) - (let ((swap vertices)) (set! vertices rotated-vertices) (set! rotated-vertices swap))))) + (if (nonzero? vertex-index) + (camera-line (-> vertices vert vertex-index) (-> vertices vert (+ vertex-index -1)) color))) + (let ((swap vertices)) + (set! vertices rotated-vertices) (set! rotated-vertices swap))))) 0 (none))) diff --git a/goal_src/jak1/engine/gfx/hw/display-h.gc b/goal_src/jak1/engine/gfx/hw/display-h.gc index 1cb9b8a022..de11257fb6 100644 --- a/goal_src/jak1/engine/gfx/hw/display-h.gc +++ b/goal_src/jak1/engine/gfx/hw/display-h.gc @@ -202,6 +202,11 @@ (defmacro current-frame () `(-> *display* frames (-> *display* on-screen) frame)) +(defmacro add-profile-frame! (r g b a name) + "Add a colored marker to the current profile bar in debug builds." + `(if *debug-segment* + (add-frame (-> (current-frame) profile-bar 0) ,name (static-rgba ,r ,g ,b ,a)))) + (defmacro current-time () `(-> *display* base-frame-counter)) diff --git a/goal_src/jak1/engine/level/bsp.gc b/goal_src/jak1/engine/level/bsp.gc index 47c6bf80a7..9172883e7c 100644 --- a/goal_src/jak1/engine/level/bsp.gc +++ b/goal_src/jak1/engine/level/bsp.gc @@ -15,7 +15,7 @@ (defun-recursive mem-usage-bsp-tree none ((header bsp-header) (node bsp-node) (mem-use memory-usage-block) (flags mem-usage-flags)) "Recursively count the 32-byte internal nodes reachable from node in the BSP-node memory - category. Nonpositive terminal children are not followed; header and flags are unused." + category." (cond ((zero? node)) (else @@ -30,238 +30,173 @@ (defmethod mem-usage ((this bsp-header) (mem-use memory-usage-block) (flags mem-usage-flags)) "Account for this header's owned level data and delegate memory accounting to its drawable trees and cameras." - ;; start off by setting the current bsp + ;; seems unused? (set! (-> mem-use work-bsp) this) ;; this seems slightly wrong, we count the file-info toward array. (when (nonzero? (-> this info)) (mem-usage-add! mem-use array 1 (asize-of (-> this info)))) ;; measure the drawable trees. - (if (nonzero? (-> this drawable-trees)) (mem-usage (-> this drawable-trees) mem-use flags)) - ;; add stuff + (if (nonzero? (-> this drawable-trees)) + (mem-usage (-> this drawable-trees) mem-use flags)) + ;; set some names for categories that don't use the normal macros (set! (-> mem-use length) (max 63 (-> mem-use length))) (set! (-> mem-use data 43 name) "entity") (set! (-> mem-use data 44 name) "camera") (set! (-> mem-use data 62 name) "pat") (set! (-> mem-use data 58 name) "bsp-node") - ;; add the bsp-header itself (mem-usage-add! mem-use bsp-main 1 400) - ;; add the visible list (mem-usage-add! mem-use bsp-leaf-vis-self 1 (-> this visible-list-length)) - ;; add the unk-data-0 (mem-usage-add! mem-use bsp-misc 1 (* (-> this texture-remap-table-len) 8)) - ;; add the unk-data-1 (mem-usage-add! mem-use bsp-misc 1 (* (-> this texture-page-count) 4)) - ;; add unk-zero-0 (when (nonzero? (-> this unk-zero-0)) (mem-usage-add! mem-use bsp-misc 1 (asize-of (-> this unk-zero-0)))) - ;; add adgifs (when (nonzero? (-> this adgifs)) (mem-usage-add! mem-use bsp-misc 1 (asize-of (-> this adgifs)))) - ;; add boxes (when (nonzero? (-> this boxes)) (mem-usage-add! mem-use bsp-misc 1 (asize-of (-> this boxes))) - ;; add box indices (when (nonzero? (-> this split-box-indices)) (mem-usage-add! mem-use bsp-misc 1 (* (-> this boxes length) 2)))) - ;; add actor-birth-order (when (nonzero? (-> this actor-birth-order)) - ;; add actors (mem-usage-add! mem-use bsp-misc 1 (* (-> this actors length) 4))) - ;; add pat (+! (-> mem-use data 62 count) (-> this pat-length)) (let ((pat-bytes (* (-> this pat-length) 4))) (+! (-> mem-use data 62 used) pat-bytes) (+! (-> mem-use data 62 total) (logand -16 (+ pat-bytes 15)))) - ;; add cameras (let ((cameras (-> this cameras))) (when (nonzero? cameras) (dotimes (i (-> cameras length)) (mem-usage (-> cameras i) mem-use (logior flags (mem-usage-flags resource-camera)))))) - ;; add the tree itself - (mem-usage-bsp-tree this (the-as bsp-node (-> this nodes)) mem-use flags) + ;; recursively add bsp tree nodes + (mem-usage-bsp-tree this (-> this nodes 0) mem-use flags) this) (defmethod login ((this bsp-header)) - "Log in the level's drawable trees and every ADGIF shader." + "Log in the level's drawable trees and every ADGIF shader. + This method takes too long to run and level.gc's login state + machine splits the work over multiple frames." ;; login our drawables - (if (nonzero? (-> this drawable-trees)) (login (-> this drawable-trees))) + (if (nonzero? (-> this drawable-trees)) + (login (-> this drawable-trees))) ;; login our shaders (when (nonzero? (-> this adgifs)) - (let ((shaders (-> this adgifs))) (dotimes (i (-> shaders length)) (adgif-shader-login-no-remap (-> shaders data i))))) + (let ((shaders (-> this adgifs))) + (dotimes (i (-> shaders length)) + (adgif-shader-login-no-remap (-> shaders data i))))) this) (define *test-shrub* 0) ;; unused. +(defmacro setup-drawing-registers () + "Set VU0 registers to hold camera constants used in background rendering code." + `(let ((math-cam *math-camera*)) + (with-vf (vf16 vf17 vf18 vf19 vf20 vf21 vf22 vf23 vf24 vf25 vf26 vf27 vf28 vf29 vf30 vf31) + :rw 'write + (.lvf vf16 (&-> math-cam plane 0 quad)) + (.lvf vf17 (&-> math-cam plane 1 quad)) + (.lvf vf18 (&-> math-cam plane 2 quad)) + (.lvf vf19 (&-> math-cam plane 3 quad)) + (.lvf vf20 (&-> math-cam shrub-mat vector 0 quad)) + (.lvf vf21 (&-> math-cam shrub-mat vector 1 quad)) + (.lvf vf22 (&-> math-cam shrub-mat vector 2 quad)) + (.lvf vf23 (&-> math-cam shrub-mat vector 3 quad)) + (.lvf vf24 (&-> math-cam camera-rot vector 0 quad)) + (.lvf vf25 (&-> math-cam camera-rot vector 1 quad)) + (.lvf vf26 (&-> math-cam camera-rot vector 2 quad)) + (.lvf vf27 (&-> math-cam camera-rot vector 3 quad)) + (.lvf vf28 (&-> math-cam camera-temp vector 0 quad)) + (.lvf vf29 (&-> math-cam camera-temp vector 1 quad)) + (.lvf vf30 (&-> math-cam camera-temp vector 2 quad)) + (.lvf vf31 (&-> math-cam camera-temp vector 3 quad))))) + (defmethod draw ((this bsp-header) (other-draw bsp-header) (disp-frame display-frame)) "Prepare scratchpad visibility, subdivision state, and math-camera VU registers; draw the level's drawable trees and three foreground engines for the current display frame." - (local-vars (inverted-quad uint128) (flipped-quad uint128) (zero-quad uint128)) - (set! zero-quad (the-as uint128 0)) - (let ((lev (-> this level))) - ;; set up some stuff in the scratchpad - (set! (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) lev-index) (-> lev index)) - (set! (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) mood) (-> lev mood)) - ;; update the subdivision settings - (if *artist-use-menu-subdiv* - (update-subdivide-settings! *subdivide-settings* *math-camera* 3) - (update-subdivide-settings! *subdivide-settings* *math-camera* (-> lev index))) - ;; at this point we are no longer adding textures, so we can mark the end of the - ;; textures with interrupts to get rendering VIF interrupts for profiling. - (add-irq-to-tex-buckets! lev) - ;; upload the visible list to the scratchpad. - ;; the final result of all visibility calculations is stored in vis-bits. - ;; this goes at the end of the scratchpad. - (let ((vis-list-qwc (/ (+ (-> this visible-list-length) 15) 16))) - (dma-send-to-spr-no-flush (scratchpad-object uint :offset VISIBLE_LIST_SCRATCHPAD) - (the-as uint (-> lev vis-bits)) - (the-as uint vis-list-qwc) - #f))) - ;; this is a race condition with the previous DMA transfer. - ;; probably the DMA wins though. - ;; this messes with the visibility bits to invert what was just uploaded - (when *artist-flip-visible* - (let ((vis-list-qwc2 (/ (+ (-> this visible-list-length) 15) 16)) - (vis-list-spad (scratchpad-object (pointer uint128) :offset VISIBLE_LIST_SCRATCHPAD)) - (vis-list-lev (the-as (pointer uint128) (-> this all-visible-list)))) - ;; iterate through all qw's in the visible list. - (dotimes (current-qw vis-list-qwc2) - ;; invert the stuff in the spad list - (let ((scratch-quad (-> vis-list-spad current-qw))) - ;; note: this isn't great x86 code, but it's probably fine. - (.pnor inverted-quad scratch-quad zero-quad)) - ;; and with the all visible list so we don't - ;; accidentally turn on the vis bit for something that - ;; doesn't exist. - (let ((all-visible-quad (-> vis-list-lev current-qw))) (.pand flipped-quad inverted-quad all-visible-quad)) - (set! (-> vis-list-spad current-qw) flipped-quad)))) - ;; set up the math camera registers - (let ((math-cam *math-camera*)) - (with-vf (vf16 vf17 vf18 vf19 vf20 vf21 vf22 vf23 vf24 vf25 vf26 vf27 vf28 vf29 vf30 vf31) - :rw 'write - (.lvf vf16 (&-> math-cam plane 0 quad)) - (.lvf vf17 (&-> math-cam plane 1 quad)) - (.lvf vf18 (&-> math-cam plane 2 quad)) - (.lvf vf19 (&-> math-cam plane 3 quad)) - (.lvf vf20 (&-> math-cam shrub-mat vector 0 quad)) - (.lvf vf21 (&-> math-cam shrub-mat vector 1 quad)) - (.lvf vf22 (&-> math-cam shrub-mat vector 2 quad)) - (.lvf vf23 (&-> math-cam shrub-mat vector 3 quad)) - (.lvf vf24 (&-> math-cam camera-rot vector 0 quad)) - (.lvf vf25 (&-> math-cam camera-rot vector 1 quad)) - (.lvf vf26 (&-> math-cam camera-rot vector 2 quad)) - (.lvf vf27 (&-> math-cam camera-rot vector 3 quad)) - (.lvf vf28 (&-> math-cam camera-temp vector 0 quad)) - (.lvf vf29 (&-> math-cam camera-temp vector 1 quad)) - (.lvf vf30 (&-> math-cam camera-temp vector 2 quad)) - (.lvf vf31 (&-> math-cam camera-temp vector 3 quad)))) - ;; draw the drawables! - ;; start a profile bar. - (when (nonzero? (-> this drawable-trees)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) - ;; draw! - (let ((trees (-> this drawable-trees))) (draw trees trees disp-frame)) - ;; end a profile bar - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x80 :g #xc0 :a #x80)))) - ;; run the foreground system (0 check added) - (when (nonzero? foreground-engine-execute) + (local-vars (inverted-quad uint128) (flipped-quad uint128)) + (slet (spad terrain-context) + (let ((lev (-> this level))) + ;; cache level info in scratchpad + (set! (-> spad bsp lev-index) (-> lev index)) + (set! (-> spad bsp mood) (-> lev mood)) + + ;; update background rendering subdivision (level of detail) thresholds consumed by + ;; renderers. level "3" is set from the menu + (if *artist-use-menu-subdiv* + (update-subdivide-settings! *subdivide-settings* *math-camera* 3) + (update-subdivide-settings! *subdivide-settings* *math-camera* (-> lev index))) + + ;; set up interrupts on texture bucket completion + ;; texture upload DMA is already built, using info from the last frame. + (add-irq-to-tex-buckets! lev) + + ;; upload visibility list to scratchpad (no sync happens, this is a race) + (let ((vis-list-qwc (/ (+ (-> this visible-list-length) 15) 16))) + (dma-send-to-spr-no-flush (-> spad work background vis-list) (-> lev vis-bits) (the-as uint vis-list-qwc) #f))) + + ;; flip visibility bits. Note that not every bit in the string is used and we only toggle used bits. + (when *artist-flip-visible* + (let ((vis-list-qwc2 (/ (+ (-> this visible-list-length) 15) 16)) + (vis-list-spad (the-as (pointer uint128) (-> spad work background vis-list))) + (vis-list-lev (the-as (pointer uint128) (-> this all-visible-list)))) + (dotimes (current-qw vis-list-qwc2) + ;; invert all bits + (let ((scratch-quad (-> vis-list-spad current-qw))) + (.pnor inverted-quad scratch-quad 0)) + ;; mask with all-visible list + (let ((all-visible-quad (-> vis-list-lev current-qw))) + (.pand flipped-quad inverted-quad all-visible-quad)) + (set! (-> vis-list-spad current-qw) flipped-quad)))) + + ;; set up VU0 registers prior to traversing drawable trees + (setup-drawing-registers) + + (when (nonzero? (-> this drawable-trees)) + (add-profile-frame! #x40 0 #x40 #x80 'draw) + (let ((trees (-> this drawable-trees))) + ;; draw! This ends up doing relatively little, most trees defer their drawing, + ;; and this just does book-keeping on what needs drawing later. + (draw trees trees disp-frame)) + (add-profile-frame! #x80 #xc0 0 #x80 'draw)) + + ;; draw foreground geometry. The foreground engines store the process-drawables associated with + ;; this level. (let ((frame (current-frame))) - ;; 0 - (foreground-engine-execute (-> this level foreground-draw-engine 0) - frame - (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) lev-index) - 0) - ;; 1 - (foreground-engine-execute (-> this level foreground-draw-engine 1) - frame - (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) lev-index) - 1) - ;; 2 - (foreground-engine-execute (-> this level foreground-draw-engine 2) - frame - (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) lev-index) - 2))) - (none)) + (foreground-engine-execute (-> this level foreground-draw-engine 0) frame (-> spad bsp lev-index) 0) + (foreground-engine-execute (-> this level foreground-draw-engine 1) frame (-> spad bsp lev-index) 1) + (foreground-engine-execute (-> this level foreground-draw-engine 2) frame (-> spad bsp lev-index) 2)) + (none))) (defmethod debug-draw ((this bsp-header) (other-draw drawable) (disp-frame display-frame)) "Prepare the level's scratchpad visibility and math-camera VU registers, then submit debug geometry from its drawable trees." - (let ((lev (-> this level))) - ;; set up some stuff in the scratchpad - (set! (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) lev-index) (-> lev index)) - (set! (-> (scratchpad-object terrain-bsp :offset TERRAIN_BSP_SCRATCHPAD) mood) (-> lev mood)) - (add-irq-to-tex-buckets! lev) - (let ((vis-list-qwc (/ (+ (-> this visible-list-length) 15) 16))) - (dma-send-to-spr-no-flush (scratchpad-object uint :offset VISIBLE_LIST_SCRATCHPAD) - (the-as uint (-> lev vis-bits)) - (the-as uint vis-list-qwc) - #f))) - (let ((math-cam *math-camera*)) - (with-vf (vf16 vf17 vf18 vf19 vf20 vf21 vf22 vf23 vf24 vf25 vf26 vf27 vf28 vf29 vf30 vf31) - :rw 'write - (.lvf vf16 (&-> math-cam plane 0 quad)) - (.lvf vf17 (&-> math-cam plane 1 quad)) - (.lvf vf18 (&-> math-cam plane 2 quad)) - (.lvf vf19 (&-> math-cam plane 3 quad)) - (.lvf vf20 (&-> math-cam shrub-mat vector 0 quad)) - (.lvf vf21 (&-> math-cam shrub-mat vector 1 quad)) - (.lvf vf22 (&-> math-cam shrub-mat vector 2 quad)) - (.lvf vf23 (&-> math-cam shrub-mat vector 3 quad)) - (.lvf vf24 (&-> math-cam camera-rot vector 0 quad)) - (.lvf vf25 (&-> math-cam camera-rot vector 1 quad)) - (.lvf vf26 (&-> math-cam camera-rot vector 2 quad)) - (.lvf vf27 (&-> math-cam camera-rot vector 3 quad)) - (.lvf vf28 (&-> math-cam camera-temp vector 0 quad)) - (.lvf vf29 (&-> math-cam camera-temp vector 1 quad)) - (.lvf vf30 (&-> math-cam camera-temp vector 2 quad)) - (.lvf vf31 (&-> math-cam camera-temp vector 3 quad)))) - (when (nonzero? (-> this drawable-trees)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x40 :b #x40 :a #x80))) - (let ((trees (-> this drawable-trees))) (debug-draw trees trees disp-frame)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) - 'draw - (new 'static 'rgba :r #x80 :g #xc0 :a #x80)))) - (none)) + (slet (spad terrain-context) + (let ((lev (-> this level))) + (set! (-> spad bsp lev-index) (-> lev index)) + (set! (-> spad bsp mood) (-> lev mood)) + (add-irq-to-tex-buckets! lev) + (let ((vis-list-qwc (/ (+ (-> this visible-list-length) 15) 16))) + (dma-send-to-spr-no-flush (-> spad work background vis-list) (-> lev vis-bits) (the-as uint vis-list-qwc) #f))) + + (setup-drawing-registers) + + (when (nonzero? (-> this drawable-trees)) + (add-profile-frame! #x40 0 #x40 #x80 'draw) + (let ((trees (-> this drawable-trees))) + (debug-draw trees trees disp-frame)) + (add-profile-frame! #x80 #xc0 0 #x80 'draw)) + (none))) (defmethod collect-stats ((this bsp-header)) "Upload the current visibility bits and math-camera VU registers, then collect renderer statistics from the level's drawable trees." - (let ((lev (-> this level)) - (vis-list-qwc (/ (+ (-> this visible-list-length) 15) 16))) - (dma-send-to-spr-no-flush (scratchpad-object uint :offset VISIBLE_LIST_SCRATCHPAD) - (the-as uint (-> lev vis-bits)) - (the-as uint vis-list-qwc) - #f)) - (let ((math-cam *math-camera*)) - (with-vf (vf16 vf17 vf18 vf19 vf20 vf21 vf22 vf23 vf24 vf25 vf26 vf27 vf28 vf29 vf30 vf31) - :rw 'write - (.lvf vf16 (&-> math-cam plane 0 quad)) - (.lvf vf17 (&-> math-cam plane 1 quad)) - (.lvf vf18 (&-> math-cam plane 2 quad)) - (.lvf vf19 (&-> math-cam plane 3 quad)) - (.lvf vf20 (&-> math-cam shrub-mat vector 0 quad)) - (.lvf vf21 (&-> math-cam shrub-mat vector 1 quad)) - (.lvf vf22 (&-> math-cam shrub-mat vector 2 quad)) - (.lvf vf23 (&-> math-cam shrub-mat vector 3 quad)) - (.lvf vf24 (&-> math-cam camera-rot vector 0 quad)) - (.lvf vf25 (&-> math-cam camera-rot vector 1 quad)) - (.lvf vf26 (&-> math-cam camera-rot vector 2 quad)) - (.lvf vf27 (&-> math-cam camera-rot vector 3 quad)) - (.lvf vf28 (&-> math-cam camera-temp vector 0 quad)) - (.lvf vf29 (&-> math-cam camera-temp vector 1 quad)) - (.lvf vf30 (&-> math-cam camera-temp vector 2 quad)) - (.lvf vf31 (&-> math-cam camera-temp vector 3 quad)))) - (if (nonzero? (-> this drawable-trees)) (collect-stats (-> this drawable-trees))) - (none)) + (slet (spad terrain-context) + (let ((lev (-> this level)) + (vis-list-qwc (/ (+ (-> this visible-list-length) 15) 16))) + (dma-send-to-spr-no-flush (-> spad work background vis-list) (-> lev vis-bits) (the-as uint vis-list-qwc) #f)) + (setup-drawing-registers) + (if (nonzero? (-> this drawable-trees)) + (collect-stats (-> this drawable-trees))) + (none))) + (#unless PC_PORT (defun bsp-camera-asm ((header bsp-header) (camera-position vector)) @@ -302,36 +237,24 @@ (m result r0) (jr ra :delay (add sp sp r0))))) -(#when PC_PORT - (defun bsp-camera-asm ((header bsp-header) (camera-position vector)) - "Traverse the BSP for camera-position. At each split, select front when - dot(camera-position, plane.xyz) - plane.w is nonnegative. Store the terminal leaf index and - that side's packed neighboring-level flags in header." - (local-vars (plane-side int) (node bsp-node)) - (rlet ((vf1 :class vf) - (vf2 :class vf)) - (nop!) - (let ((next-node (the-as bsp-node (-> header nodes)))) - (.lvf vf1 (&-> camera-position quad)) - (label cfg-1) - (b! (< (the-as int next-node) 0) cfg-4 :delay (set! node next-node)) - (.lvf vf2 (&-> node plane quad)) - (.mul.vf.xyz vf2 vf2 vf1) - (.add.x.vf.y vf2 vf2 vf2) - (.add.z.vf.y vf2 vf2 vf2) - (.sub.w.vf.y vf2 vf2 vf2) - ;; The integer sign test reads the y lane's sign after the VU-to-EE transfer. - (.mov plane-side vf2) - (let ((side-flags (-> node front-flags))) - (b! (>= plane-side 0) cfg-1 :delay (set! next-node (the-as bsp-node (-> node front)))) - (set! side-flags (-> node back-flags)) - (b! #t cfg-1 :delay (set! next-node (the-as bsp-node (-> node back)))) - (label cfg-4) - (set! (-> header current-leaf-idx) (the-as uint next-node)) - (set! (-> header current-bsp-flags) side-flags) - )) - 0 - (none)))) +(defun bsp-camera-asm ((bsp bsp-header) (pos vector)) + "Look up the leaf node containing pos and store its index in flags in the bsp-header" + (let ((iter (-> bsp nodes 0)) + (flags (the uint 0))) + (while (>= (the int iter) 0) + ;; do front/back test + (let ((plane-test (- (vector-dot pos (-> iter plane)) (-> iter plane w)))) + (cond + ((>= plane-test 0.0) + (set! flags (-> iter front-flags)) + (set! iter (the bsp-node (-> iter front)))) + (else + (set! flags (-> iter back-flags)) + (set! iter (the bsp-node (-> iter back))))))) + ;; update header - lower 16-bits of the final front/back value + (set! (-> bsp current-leaf-idx) (the uint iter)) + (set! (-> bsp current-bsp-flags) flags)) + (none)) ;;;;;;;;;;;;;;; ;; Collision @@ -356,10 +279,12 @@ (none)) (defmethod collide-ray ((this bsp-header) (length int) (result collide-list)) - "Forward the active swept-sphere ray query to every drawable tree and append their geometry to - result. length is passed through for child implementations that use it." + "Forward the active swept-sphere ray query to every drawable tree and append their geometry + to result. length is passed through for child implementations that use it." (+! (-> *collide-stats* calls) 1) - (let ((trees (-> this drawable-trees))) (dotimes (i (-> trees length)) (collide-ray (-> trees trees i) length result))) + (let ((trees (-> this drawable-trees))) + (dotimes (i (-> trees length)) + (collide-ray (-> trees trees i) length result))) (none)) (defmethod collect-ambients ((this bsp-header) (query-sphere sphere) (length int) (result ambient-list)) @@ -400,10 +325,7 @@ "Print the frame's collision counts, per-call averages, and target timing breakdown, then clear the counters and restart all three collision stopwatches." ;; for some unknown reason, we profile this. - (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) (format *stdcon* "~0k frags tris output~%") (print-cl-stat (-> *collide-stats* other) "other") (format *stdcon* "~0k---------------------------------------------------------------~%") @@ -431,43 +353,77 @@ (stopwatch-init (-> *collide-stats* total-target)) (stopwatch-init (-> *collide-stats* target-cache-fill)) (stopwatch-init (-> *collide-stats* target-ray-poly)) - (if *debug-segment* - (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) 'draw (new 'static 'rgba :b #xff :a #x80))) + (add-profile-frame! 0 0 #xff #x80 'draw) 0 (none)) (defun level-remap-texture ((tex-id texture-id)) - "Binary-search the active level's sorted texture-remap table using the texture ID's upper 24 - bits. Return the original ID when no level BSP or matching entry exists; otherwise return the - entry's remapped ID with the canonical #x14 low-byte tag." - (let ((bsp-hdr (-> *level* log-in-level-bsp))) - (when bsp-hdr - (let* ((table-size (-> bsp-hdr texture-remap-table-len)) ;; in 64-bit entries - (search-start (-> bsp-hdr texture-remap-table)) - (table-start search-start) - (entry-align-mask (the-as uint #xfffffff8)) ;; mask for table entry addresses - (lookup-id (logand (the-as uint #xffffff00) tex-id)) ;; bits of tex-id we care about - (search-end (&+ table-start (* table-size 8)))) - ;; top of binary search - (label cfg-2) - ;; if we didn't find anything, quit - (b! (= search-start search-end) cfg-8) - ;; find the middle entry. - (let ((midpoint (logand (/ (+ (the-as int search-start) (the-as int search-end)) 2) entry-align-mask))) - ;; how did we do? - (let ((diff (- (-> (the-as (pointer int32) midpoint) 0) (the-as int lookup-id)))) - (b! (zero? diff) cfg-7) - (b! (< diff 0) cfg-6 :delay (nop!))) - ;; in the lower section - (b! #t cfg-2 :delay (set! search-end (the-as (pointer uint64) midpoint))) - ;; in the upper section (not including the midpoint) - (label cfg-6) - (b! #t cfg-2 :delay (set! search-start (the-as (pointer uint64) (+ (the-as int midpoint) 8)))) - ;; exact match - (label cfg-7) - ;; The key ignores the low byte. The matched value has that byte clear, so restore the - ;; canonical #x14 tag used by in-game texture IDs. - (set! tex-id (the-as texture-id (logior (-> (the-as (pointer int32) midpoint) 1) 20))))) - (label cfg-8) - 0)) - (the-as texture-id tex-id)) + "Remap an actor texture ID to its combined level texture-page ID." + ;; actor art data references textures in a per-actor texture page. + ;; levels contain a larger texture page merging all the per-actor pages and this function + ;; remaps from the per-actor texture page to the big level page. + + ;; simplified GOAL version + (let ((bsp (-> *level* log-in-level-bsp))) + (when bsp + (let ((table (the-as (pointer texture-id) (-> bsp texture-remap-table))) + (lookup-id (the texture-id (logand tex-id #xffffff00))) + (begin 0) + (end (-> bsp texture-remap-table-len))) + (while (< begin end) + (let* ((midpoint (/ (+ begin end) 2)) + (entry-index (* midpoint 2)) + (source-id (-> table entry-index))) + (cond + ((< source-id lookup-id) + (set! begin (+ midpoint 1))) + ((> source-id lookup-id) + (set! end midpoint)) + (else + (let ((remapped-id (-> table (+ entry-index 1)))) + ;; restore 0x14 here: low bytes of texture-id are part of A+D + ;; format data, this 0x14 specifies TEX1_1 register. + (return (the texture-id (logior remapped-id #x14))))))))))) + tex-id) + +(#unless PC_PORT + (defun level-remap-texture ((tex-id texture-id)) + "Binary-search the active level's sorted texture-remap table using the texture ID's upper 24 + bits. Return the original ID when no level BSP or matching entry exists; otherwise return the + entry's remapped ID with the canonical #x14 low-byte tag." + + ;; actor art data references textures in a per-actor texture page. + ;; levels contain a larger texture page merging all the per-actor pages and this function + ;; remaps from the per-actor texture page to the big level page. + (let ((bsp-hdr (-> *level* log-in-level-bsp))) + (when bsp-hdr + (let* ((table-size (-> bsp-hdr texture-remap-table-len)) ;; in 64-bit entries + (search-start (-> bsp-hdr texture-remap-table)) + (table-start search-start) + (entry-align-mask (the-as uint #xfffffff8)) ;; mask for table entry addresses + (lookup-id (logand (the-as uint #xffffff00) tex-id)) ;; bits of tex-id we care about + (search-end (&+ table-start (* table-size 8)))) + ;; top of binary search + (label cfg-2) + ;; if we didn't find anything, quit + (b! (= search-start search-end) cfg-8) + ;; find the middle entry. + (let ((midpoint (logand (/ (+ (the-as int search-start) (the-as int search-end)) 2) entry-align-mask))) + ;; how did we do? + (let ((diff (- (-> (the-as (pointer int32) midpoint) 0) (the-as int lookup-id)))) + (b! (zero? diff) cfg-7) + (b! (< diff 0) cfg-6 :delay (nop!))) + ;; in the lower section + (b! #t cfg-2 :delay (set! search-end (the-as (pointer uint64) midpoint))) + ;; in the upper section (not including the midpoint) + (label cfg-6) + (b! #t cfg-2 :delay (set! search-start (the-as (pointer uint64) (+ (the-as int midpoint) 8)))) + ;; exact match + (label cfg-7) + ;; The key ignores the low byte. The matched value has that byte clear, so restore the + ;; canonical #x14 tag used by in-game texture IDs. + (set! tex-id (the-as texture-id (logior (-> (the-as (pointer int32) midpoint) 1) 20))))) + (label cfg-8) + 0)) + (the-as texture-id tex-id))) +