OpenTTD Source 20250523-master-g321f7e8683
newgrf_spritegroup.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "debug.h"
12#include "newgrf_spritegroup.h"
13#include "newgrf_profiling.h"
14#include "core/pool_func.hpp"
15
16#include "safeguards.h"
17
18SpriteGroupPool _spritegroup_pool("SpriteGroup");
20
21/* static */ TemporaryStorageArray<int32_t, 0x110> ResolverObject::temp_store;
22
23
34/* static */ ResolverResult SpriteGroup::Resolve(const SpriteGroup *group, ResolverObject &object, bool top_level)
35{
36 if (group == nullptr) return std::monostate{};
37
38 const GRFFile *grf = object.grffile;
39 auto profiler = std::ranges::find(_newgrf_profilers, grf, &NewGRFProfiler::grffile);
40
41 if (profiler == _newgrf_profilers.end() || !profiler->active) {
42 return group->Resolve(object);
43 } else if (top_level) {
44 profiler->BeginResolve(object);
45 auto result = group->Resolve(object);
46 profiler->EndResolve(result);
47 return result;
48 } else {
49 profiler->RecursiveResolve();
50 return group->Resolve(object);
51 }
52}
53
54static inline uint32_t GetVariable(const ResolverObject &object, ScopeResolver *scope, uint8_t variable, uint32_t parameter, bool &available)
55{
56 uint32_t value;
57 switch (variable) {
58 case 0x0C: return object.callback;
59 case 0x10: return object.callback_param1;
60 case 0x18: return object.callback_param2;
61 case 0x1C: return object.last_value;
62
63 case 0x5F: return (scope->GetRandomBits() << 8) | scope->GetRandomTriggers();
64
65 case 0x7D: return object.GetRegister(parameter);
66
67 case 0x7F:
68 if (object.grffile == nullptr) return 0;
69 return object.grffile->GetParam(parameter);
70
71 default:
72 /* First handle variables common with Action7/9/D */
73 if (variable < 0x40 && GetGlobalVariable(variable, &value, object.grffile)) return value;
74 /* Not a common variable, so evaluate the feature specific variables */
75 return scope->GetVariable(variable, parameter, available);
76 }
77}
78
83/* virtual */ uint32_t ScopeResolver::GetRandomBits() const
84{
85 return 0;
86}
87
92/* virtual */ uint32_t ScopeResolver::GetRandomTriggers() const
93{
94 return 0;
95}
96
104/* virtual */ uint32_t ScopeResolver::GetVariable(uint8_t variable, [[maybe_unused]] uint32_t parameter, bool &available) const
105{
106 Debug(grf, 1, "Unhandled scope variable 0x{:X}", variable);
107 available = false;
108 return UINT_MAX;
109}
110
114/* virtual */ void ScopeResolver::StorePSA(uint, int32_t) {}
115
121/* virtual */ const SpriteGroup *ResolverObject::ResolveReal(const RealSpriteGroup &group) const
122{
123 if (!group.loaded.empty()) return group.loaded[0];
124 if (!group.loading.empty()) return group.loading[0];
125
126 return nullptr;
127}
128
134{
135 return &this->default_scope;
136}
137
138/* Evaluate an adjustment for a variable of the given size.
139 * U is the unsigned type and S is the signed type to use. */
140template <typename U, typename S>
141static U EvalAdjustT(const DeterministicSpriteGroupAdjust &adjust, ResolverObject &object, ScopeResolver *scope, U last_value, uint32_t value)
142{
143 value >>= adjust.shift_num;
144 value &= adjust.and_mask;
145
146 switch (adjust.type) {
147 case DSGA_TYPE_DIV: value = ((S)value + (S)adjust.add_val) / (S)adjust.divmod_val; break;
148 case DSGA_TYPE_MOD: value = ((S)value + (S)adjust.add_val) % (S)adjust.divmod_val; break;
149 case DSGA_TYPE_NONE: break;
150 }
151
152 switch (adjust.operation) {
153 case DSGA_OP_ADD: return last_value + value;
154 case DSGA_OP_SUB: return last_value - value;
155 case DSGA_OP_SMIN: return std::min<S>(last_value, value);
156 case DSGA_OP_SMAX: return std::max<S>(last_value, value);
157 case DSGA_OP_UMIN: return std::min<U>(last_value, value);
158 case DSGA_OP_UMAX: return std::max<U>(last_value, value);
159 case DSGA_OP_SDIV: return value == 0 ? (S)last_value : (S)last_value / (S)value;
160 case DSGA_OP_SMOD: return value == 0 ? (S)last_value : (S)last_value % (S)value;
161 case DSGA_OP_UDIV: return value == 0 ? (U)last_value : (U)last_value / (U)value;
162 case DSGA_OP_UMOD: return value == 0 ? (U)last_value : (U)last_value % (U)value;
163 case DSGA_OP_MUL: return last_value * value;
164 case DSGA_OP_AND: return last_value & value;
165 case DSGA_OP_OR: return last_value | value;
166 case DSGA_OP_XOR: return last_value ^ value;
167 case DSGA_OP_STO: object.SetRegister((U)value, (S)last_value); return last_value;
168 case DSGA_OP_RST: return value;
169 case DSGA_OP_STOP: scope->StorePSA((U)value, (S)last_value); return last_value;
170 case DSGA_OP_ROR: return std::rotr<uint32_t>((U)last_value, (U)value & 0x1F); // mask 'value' to 5 bits, which should behave the same on all architectures.
171 case DSGA_OP_SCMP: return ((S)last_value == (S)value) ? 1 : ((S)last_value < (S)value ? 0 : 2);
172 case DSGA_OP_UCMP: return ((U)last_value == (U)value) ? 1 : ((U)last_value < (U)value ? 0 : 2);
173 case DSGA_OP_SHL: return (uint32_t)(U)last_value << ((U)value & 0x1F); // Same behaviour as in ParamSet, mask 'value' to 5 bits, which should behave the same on all architectures.
174 case DSGA_OP_SHR: return (uint32_t)(U)last_value >> ((U)value & 0x1F);
175 case DSGA_OP_SAR: return (int32_t)(S)last_value >> ((U)value & 0x1F);
176 default: return value;
177 }
178}
179
180
181static bool RangeHighComparator(const DeterministicSpriteGroupRange &range, uint32_t value)
182{
183 return range.high < value;
184}
185
187{
188 uint32_t last_value = 0;
189 uint32_t value = 0;
190
191 ScopeResolver *scope = object.GetScope(this->var_scope);
192
193 for (const auto &adjust : this->adjusts) {
194 /* Try to get the variable. We shall assume it is available, unless told otherwise. */
195 bool available = true;
196 if (adjust.variable == 0x7E) {
197 auto subgroup = SpriteGroup::Resolve(adjust.subroutine, object, false);
198 auto *subvalue = std::get_if<CallbackResult>(&subgroup);
199 value = subvalue != nullptr ? *subvalue : UINT16_MAX;
200
201 /* Note: 'last_value' and 'reseed' are shared between the main chain and the procedure */
202 } else if (adjust.variable == 0x7B) {
203 value = GetVariable(object, scope, adjust.parameter, last_value, available);
204 } else {
205 value = GetVariable(object, scope, adjust.variable, adjust.parameter, available);
206 }
207
208 if (!available) {
209 /* Unsupported variable: skip further processing and return either
210 * the group from the first range or the default group. */
211 return SpriteGroup::Resolve(this->error_group, object, false);
212 }
213
214 switch (this->size) {
215 case DSG_SIZE_BYTE: value = EvalAdjustT<uint8_t, int8_t> (adjust, object, scope, last_value, value); break;
216 case DSG_SIZE_WORD: value = EvalAdjustT<uint16_t, int16_t>(adjust, object, scope, last_value, value); break;
217 case DSG_SIZE_DWORD: value = EvalAdjustT<uint32_t, int32_t>(adjust, object, scope, last_value, value); break;
218 default: NOT_REACHED();
219 }
220 last_value = value;
221 }
222
223 object.last_value = last_value;
224
225 auto result = this->default_result;
226
227 if (this->ranges.size() > 4) {
228 const auto &lower = std::lower_bound(this->ranges.begin(), this->ranges.end(), value, RangeHighComparator);
229 if (lower != this->ranges.end() && lower->low <= value) {
230 assert(lower->low <= value && value <= lower->high);
231 result = lower->result;
232 }
233 } else {
234 for (const auto &range : this->ranges) {
235 if (range.low <= value && value <= range.high) {
236 result = range.result;
237 break;
238 }
239 }
240 }
241
242 if (result.calculated_result) {
243 return static_cast<CallbackResult>(GB(value, 0, 15));
244 }
245 return SpriteGroup::Resolve(result.group, object, false);
246}
247
248
250{
251 ScopeResolver *scope = object.GetScope(this->var_scope, this->count);
252 if (object.callback == CBID_RANDOM_TRIGGER) {
253 /* Handle triggers */
254 uint8_t match = this->triggers & object.GetWaitingRandomTriggers();
255 bool res = (this->cmp_mode == RSG_CMP_ANY) ? (match != 0) : (match == this->triggers);
256
257 if (res) {
258 object.AddUsedRandomTriggers(match);
259 object.reseed[this->var_scope] |= (this->groups.size() - 1) << this->lowest_randbit;
260 }
261 }
262
263 uint32_t mask = ((uint)this->groups.size() - 1) << this->lowest_randbit;
264 uint8_t index = (scope->GetRandomBits() & mask) >> this->lowest_randbit;
265
266 return SpriteGroup::Resolve(this->groups[index], object, false);
267}
268
270{
271 return this->result;
272}
273
275{
276 /* Call the feature specific evaluation via ResultSpriteGroup::ResolveReal.
277 * The result is either ResultSpriteGroup, CallbackResultSpriteGroup, or nullptr.
278 */
279 return SpriteGroup::Resolve(object.ResolveReal(*this), object, false);
280}
281
291{
292 if (!this->dts.NeedsPreprocessing()) {
293 if (stage != nullptr && this->dts.consistent_max_offset > 0) *stage = GetConstructionStageOffset(*stage, this->dts.consistent_max_offset);
294 return SpriteLayoutProcessor(this->dts);
295 }
296
297 uint8_t actual_stage = stage != nullptr ? *stage : 0;
298 SpriteLayoutProcessor result(this->dts, 0, 0, 0, actual_stage, false);
299 result.ProcessRegisters(object, 0, 0);
300
301 /* Stage has been processed by PrepareLayout(), set it to zero. */
302 if (stage != nullptr) *stage = 0;
303
304 return result;
305}
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
Add dynamic register values to a sprite layout.
void ProcessRegisters(const struct ResolverObject &object, uint8_t resolved_var10, uint32_t resolved_sprite)
Evaluates the register modifiers and integrates them into the preprocessed sprite layout.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
bool GetGlobalVariable(uint8_t param, uint32_t *value, const GRFFile *grffile)
Reads a variable common to VarAction2 and Action7/9/D.
@ CBID_RANDOM_TRIGGER
Set when calling a randomizing trigger (almost undocumented).
uint GetConstructionStageOffset(uint construction_stage, uint num_sprites)
Determines which sprite to use from a spriteset for a specific construction stage.
Profiling of NewGRF action 2 handling.
Action 2 handling.
@ DSGA_OP_OR
a | b
@ DSGA_OP_XOR
a ^ b
@ DSGA_OP_SUB
a - b
@ DSGA_OP_STOP
store a into persistent storage, indexed by b, return a
@ DSGA_OP_ROR
rotate a b positions to the right
@ DSGA_OP_SHL
a << b
@ DSGA_OP_UCMP
(unsigned) comparison (a < b -> 0, a == b = 1, a > b = 2)
@ DSGA_OP_MUL
a * b
@ DSGA_OP_SAR
(signed) a >> b
@ DSGA_OP_STO
store a into temporary storage, indexed by b. return a
@ DSGA_OP_SMOD
(signed) a % b
@ DSGA_OP_UDIV
(unsigned) a / b
@ DSGA_OP_UMAX
(unsigned) max(a, b)
@ DSGA_OP_SMIN
(signed) min(a, b)
@ DSGA_OP_ADD
a + b
@ DSGA_OP_UMOD
(unsigned) a & b
@ DSGA_OP_SHR
(unsigned) a >> b
@ DSGA_OP_RST
return b
@ DSGA_OP_AND
a & b
@ DSGA_OP_SDIV
(signed) a / b
@ DSGA_OP_SCMP
(signed) comparison (a < b -> 0, a == b = 1, a > b = 2)
@ DSGA_OP_UMIN
(unsigned) min(a, b)
@ DSGA_OP_SMAX
(signed) max(a, b)
VarSpriteGroupScope
std::variant< std::monostate, CallbackResult, const SpriteGroup * > ResolverResult
Result of resolving sprite groups:
Some methods of Pool are placed here in order to reduce compilation time and binary size.
#define INSTANTIATE_POOL_METHODS(name)
Force instantiation of pool methods so we don't get linker errors.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
ResolverResult Resolve(ResolverObject &object) const override
Base sprite group resolver.
uint8_t parameter
Used for variables between 0x60 and 0x7F inclusive.
ResolverResult Resolve(ResolverObject &object) const override
Base sprite group resolver.
Dynamic data of a loaded NewGRF.
Definition newgrf.h:115
const GRFFile * grffile
Which GRF is being profiled.
uint consistent_max_offset
Number of sprites in all referenced spritesets.
bool NeedsPreprocessing() const
Tests whether this spritelayout needs preprocessing by SpriteLayoutProcessor, or whether it can be us...
Tindex index
Index of this pool item.
Base class for all pools.
uint8_t lowest_randbit
Look for this in the per-object randomized bitmask:
VarSpriteGroupScope var_scope
Take this object:
std::vector< const SpriteGroup * > groups
Take the group with appropriate index:
ResolverResult Resolve(ResolverObject &object) const override
Base sprite group resolver.
RandomizedSpriteGroupCompareMode cmp_mode
Check for these triggers:
ResolverResult Resolve(ResolverObject &object) const override
Base sprite group resolver.
std::vector< const SpriteGroup * > loaded
List of loaded groups (can be SpriteIDs or Callback results)
std::vector< const SpriteGroup * > loading
List of loading groups (can be SpriteIDs or Callback results)
Interface for SpriteGroup-s to access the gamestate.
ScopeResolver default_scope
Default implementation of the grf scope.
virtual const SpriteGroup * ResolveReal(const RealSpriteGroup &group) const
Get the real sprites of the grf.
virtual ScopeResolver * GetScope(VarSpriteGroupScope scope=VSG_SCOPE_SELF, uint8_t relative=0)
Get a resolver for the scope.
Interface to query and set values specific to a single VarSpriteGroupScope (action 2 scope).
virtual void StorePSA(uint reg, int32_t value)
Store a value into the persistent storage area (PSA).
virtual uint32_t GetVariable(uint8_t variable, uint32_t parameter, bool &available) const
Get a variable value.
virtual uint32_t GetRandomTriggers() const
Get the triggers.
virtual uint32_t GetRandomBits() const
Get a few random bits.
virtual ResolverResult Resolve(ResolverObject &object) const
Base sprite group resolver.
Class for temporary storage of data.
SpriteLayoutProcessor ProcessRegisters(const ResolverObject &object, uint8_t *stage) const
Process registers and the construction stage into the sprite layout.