diff --git a/src/debug_settings.h b/src/debug_settings.h index 08e5d23e4d..b72a4f124d 100644 --- a/src/debug_settings.h +++ b/src/debug_settings.h @@ -35,6 +35,7 @@ enum NewGRFOptimiserFlags { NGOF_NO_OPT_VARACT2_SIMPLIFY_STORES = 4, NGOF_NO_OPT_VARACT2_ADJUST_ORDERING = 5, NGOF_NO_OPT_VARACT2_INSERT_JUMPS = 6, + NGOF_NO_OPT_VARACT2_CB_QUICK_EXIT = 7, }; inline bool HasGrfOptimiserFlag(NewGRFOptimiserFlags flag) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 94270717c5..0117106d3b 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -5539,10 +5539,8 @@ static void SkipAct1(ByteReader *buf) grfmsg(3, "SkipAct1: Skipping %d sprites", _cur.skip_sprites); } -static const CallbackResultSpriteGroup *NewCallbackResultSpriteGroup(uint16 groupid) +const CallbackResultSpriteGroup *NewCallbackResultSpriteGroupNoTransform(uint16 result) { - uint16 result = CallbackResultSpriteGroup::TransformResultValue(groupid, _cur.grffile->grf_version >= 8); - const CallbackResultSpriteGroup *&ptr = _callback_result_cache[result]; if (ptr == nullptr) { assert(CallbackResultSpriteGroup::CanAllocateItem()); @@ -5551,6 +5549,12 @@ static const CallbackResultSpriteGroup *NewCallbackResultSpriteGroup(uint16 grou return ptr; } +static const CallbackResultSpriteGroup *NewCallbackResultSpriteGroup(uint16 groupid) +{ + uint16 result = CallbackResultSpriteGroup::TransformResultValue(groupid, _cur.grffile->grf_version >= 8); + return NewCallbackResultSpriteGroupNoTransform(result); +} + /* Helper function to either create a callback or link to a previously * defined spritegroup. */ static const SpriteGroup *GetGroupFromGroupID(byte setid, byte type, uint16 groupid) diff --git a/src/newgrf_optimiser.cpp b/src/newgrf_optimiser.cpp index a75e5b0f28..176eed031d 100644 --- a/src/newgrf_optimiser.cpp +++ b/src/newgrf_optimiser.cpp @@ -94,6 +94,11 @@ static bool IsFeatureUsableForDSE(GrfSpecFeature feature) return (feature != GSF_STATIONS); } +static bool IsFeatureUsableForCBQuickExit(GrfSpecFeature feature) +{ + return true; +} + static bool IsIdenticalValueLoad(const DeterministicSpriteGroupAdjust *a, const DeterministicSpriteGroupAdjust *b) { if (a == nullptr && b == nullptr) return true; @@ -955,6 +960,9 @@ void OptimiseVarAction2Adjust(VarAction2OptimiseState &state, const GrfSpecFeatu if (!state.seen_procedure_call && ((const DeterministicSpriteGroup*)sg)->dsg_flags & DSGF_REQUIRES_VAR1C) { group->dsg_flags |= DSGF_REQUIRES_VAR1C; } + if (((const DeterministicSpriteGroup*)sg)->dsg_flags & DSGF_CB_HANDLER) { + group->dsg_flags |= DSGF_CB_HANDLER; + } handle_proc_stores(sg); } }); @@ -2217,8 +2225,11 @@ void OptimiseVarAction2DeterministicSpriteGroup(VarAction2OptimiseState &state, { if (unlikely(HasGrfOptimiserFlag(NGOF_NO_OPT_VARACT2))) return; + bool possible_callback_handler = false; for (DeterministicSpriteGroupAdjust &adjust : group->adjusts) { if (adjust.variable == 0x7D) adjust.parameter &= 0xFF; // Clear temporary version tags + if (adjust.variable == 0xC) possible_callback_handler = true; + if (adjust.operation == DSGA_OP_STOP) possible_callback_handler = true; } if (!HasGrfOptimiserFlag(NGOF_NO_OPT_VARACT2_GROUP_PRUNE) && (state.inference & VA2AIF_HAVE_CONSTANT) && !group->calculated_result) { @@ -2247,7 +2258,21 @@ void OptimiseVarAction2DeterministicSpriteGroup(VarAction2OptimiseState &state, bool seen_pending = false; bool seen_req_var1C = false; if (!group->calculated_result) { - auto handle_group = y_combinator([&](auto handle_group, const SpriteGroup *sg) -> void { + bool is_cb_switch = false; + if (possible_callback_handler && group->adjusts.size() == 1 && !group->calculated_result && + IsFeatureUsableForCBQuickExit(group->feature) && !HasGrfOptimiserFlag(NGOF_NO_OPT_VARACT2_CB_QUICK_EXIT)) { + const auto &adjust = group->adjusts[0]; + if (adjust.variable == 0xC && (adjust.operation == DSGA_OP_ADD || adjust.operation == DSGA_OP_RST) && + adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + is_cb_switch = true; + } + } + + struct HandleGroupState { + bool ignore_cb_handler = false; + bool have_cb_handler = false; + }; + auto handle_group = y_combinator([&](auto handle_group, const SpriteGroup *sg, HandleGroupState &state) -> void { if (sg != nullptr && sg->type == SGT_DETERMINISTIC) { VarAction2GroupVariableTracking *var_tracking = _cur.GetVarAction2GroupVariableTracking(sg, false); const DeterministicSpriteGroup *dsg = (const DeterministicSpriteGroup*)sg; @@ -2258,11 +2283,15 @@ void OptimiseVarAction2DeterministicSpriteGroup(VarAction2OptimiseState &state, if (var_tracking != nullptr) bits |= var_tracking->in; } if (dsg->dsg_flags & DSGF_REQUIRES_VAR1C) seen_req_var1C = true; + if ((dsg->dsg_flags & DSGF_CB_HANDLER) && !state.ignore_cb_handler) { + group->dsg_flags |= DSGF_CB_HANDLER; + state.have_cb_handler = true; + } } if (sg != nullptr && sg->type == SGT_RANDOMIZED) { const RandomizedSpriteGroup *rsg = (const RandomizedSpriteGroup*)sg; for (const auto &group : rsg->groups) { - handle_group(group); + handle_group(group, state); } } if (sg != nullptr && sg->type == SGT_TILELAYOUT) { @@ -2300,10 +2329,36 @@ void OptimiseVarAction2DeterministicSpriteGroup(VarAction2OptimiseState &state, } } }); - handle_group(group->default_group); + + HandleGroupState default_group_state; + handle_group(group->default_group, default_group_state); + + HandleGroupState ranges_state; for (const auto &range : group->ranges) { - handle_group(range.group); + ranges_state.ignore_cb_handler = is_cb_switch && range.low == 0 && range.high == 0; + handle_group(range.group, ranges_state); } + + if (!default_group_state.have_cb_handler && is_cb_switch) { + bool found_zero_value = false; + bool found_non_zero_value = false; + for (const auto &range : group->ranges) { + if (range.low == 0) found_zero_value = true; + if (range.high > 0) found_non_zero_value = true; + } + if (!found_non_zero_value) { + /* Group looks at var C but has no branches for non-zero cases, so don't consider it a callback handler. + * This pattern is generally only used to implement an "always fail" group. + */ + possible_callback_handler = false; + } + if (!found_zero_value) { + group->ranges.insert(group->ranges.begin(), { group->default_group, 0, 0 }); + extern const CallbackResultSpriteGroup *NewCallbackResultSpriteGroupNoTransform(uint16 result); + group->default_group = NewCallbackResultSpriteGroupNoTransform(CALLBACK_FAILED); + } + } + if (bits.any()) { state.GetVarTracking(group)->out = bits; std::bitset<256> in_bits = bits | pending_bits; @@ -2313,6 +2368,7 @@ void OptimiseVarAction2DeterministicSpriteGroup(VarAction2OptimiseState &state, state.GetVarTracking(group)->in |= in_bits; } } + if (possible_callback_handler) group->dsg_flags |= DSGF_CB_HANDLER; if (!HasGrfOptimiserFlag(NGOF_NO_OPT_VARACT2_GROUP_PRUNE) && group->ranges.empty() && !group->calculated_result && !seen_req_var1C) { /* There is only one option, remove any redundant adjustments when the result will be ignored anyway */ diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index a293c10942..018b50779a 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -605,6 +605,12 @@ void SpriteGroupDumper::DumpSpriteGroup(const SpriteGroup *sg, const char *paddi if (adjust.variable == 0xC && (adjust.operation == DSGA_OP_ADD || adjust.operation == DSGA_OP_RST) && adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { is_callback_group = true; + if (*padding == 0 && !dsg->calculated_result && ranges->size() > 0) { + const DeterministicSpriteGroupRange &first_range = (*ranges)[0]; + if (first_range.low == 0 && first_range.high == 0 && first_range.group != nullptr) { + this->top_graphics_group = first_range.group; + } + } } } @@ -617,6 +623,12 @@ void SpriteGroupDumper::DumpSpriteGroup(const SpriteGroup *sg, const char *paddi print(); return; } + if (dsg == this->top_graphics_group && !((flags & SGDF_RANGE) && strlen(padding) == 2)) { + seprintf(this->buffer, lastof(this->buffer), "%sTOP LEVEL GRAPHICS GROUP: Deterministic (%s, %s), [%u]", + padding, _sg_scope_names[dsg->var_scope], _sg_size_names[dsg->size], dsg->nfo_line); + print(); + return; + } auto res = this->seen_dsgs.insert(dsg); if (!res.second) { seprintf(this->buffer, lastof(this->buffer), "%sGROUP SEEN ABOVE: Deterministic (%s, %s), [%u]", @@ -634,6 +646,7 @@ void SpriteGroupDumper::DumpSpriteGroup(const SpriteGroup *sg, const char *paddi if (dsg->dsg_flags & DSGF_REQUIRES_VAR1C) p += seprintf(p, lastof(this->buffer), ", REQ_1C"); if (dsg->dsg_flags & DSGF_CHECK_EXPENSIVE_VARS) p += seprintf(p, lastof(this->buffer), ", CHECK_EXP_VAR"); if (dsg->dsg_flags & DSGF_CHECK_INSERT_JUMP) p += seprintf(p, lastof(this->buffer), ", CHECK_INS_JMP"); + if (dsg->dsg_flags & DSGF_CB_HANDLER) p += seprintf(p, lastof(this->buffer), ", CB_HANDLER"); } print(); emit_start(); @@ -668,7 +681,7 @@ void SpriteGroupDumper::DumpSpriteGroup(const SpriteGroup *sg, const char *paddi } } print(); - this->DumpSpriteGroup(range.group, subgroup_padding.c_str(), 0); + this->DumpSpriteGroup(range.group, subgroup_padding.c_str(), SGDF_RANGE); } if (default_group != nullptr) { seprintf(this->buffer, lastof(this->buffer), "%sdefault", padding); diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index a71eafcfdf..338cf62730 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -432,6 +432,7 @@ enum DeterministicSpriteGroupFlags : uint8 { DSGF_REQUIRES_VAR1C = 1 << 3, DSGF_CHECK_EXPENSIVE_VARS = 1 << 4, DSGF_CHECK_INSERT_JUMP = 1 << 5, + DSGF_CB_HANDLER = 1 << 6, }; DECLARE_ENUM_AS_BIT_SET(DeterministicSpriteGroupFlags) @@ -727,10 +728,12 @@ private: DumpSpriteGroupPrinter print_fn; const SpriteGroup *top_default_group = nullptr; + const SpriteGroup *top_graphics_group = nullptr; btree::btree_set seen_dsgs; enum SpriteGroupDumperFlags { SGDF_DEFAULT = 1 << 0, + SGDF_RANGE = 1 << 1, }; void DumpSpriteGroup(const SpriteGroup *sg, const char *prefix, uint flags);