2626#include " nebula/neb.h"
2727#include " particle/particle.h"
2828#include " prop/prop.h"
29+ #include " parse/encrypt.h"
2930#include " render/3dinternal.h"
3031#include " render/batching.h"
3132#include " ship/ship.h"
@@ -51,6 +52,111 @@ extern void interp_generate_arc_segment(SCP_vector<vec3d> &arc_segment_points, c
5152
5253int model_render_determine_elapsed_time (int objnum, uint64_t flags);
5354
55+ SCP_unordered_map<cached_ui_render_instance_key, cached_ui_render_instance_entry, cached_ui_render_instance_key_hash>
56+ Cached_ui_render_instance_cache;
57+ UI_TIMESTAMP Ui_render_instance_cache_last_processed_timestamp = UI_TIMESTAMP ::invalid();
58+ constexpr int UI_RENDER_INSTANCE_CACHE_UNUSED_MS_GRACE = 100 ;
59+
60+ uint32_t model_hash_subsystem_name_list_for_cache (const SCP_vector<SCP_string>& subsystem_names)
61+ {
62+ if (subsystem_names.empty ()) {
63+ return 0 ;
64+ }
65+
66+ SCP_vector<SCP_string> normalized_names;
67+ normalized_names.reserve (subsystem_names.size ());
68+
69+ for (const auto & name : subsystem_names) {
70+ auto normalized = name;
71+ std::transform (normalized.begin (), normalized.end (), normalized.begin (), [](unsigned char ch) {
72+ return static_cast <char >(SCP_tolower (ch));
73+ });
74+ normalized_names.push_back (std::move (normalized));
75+ }
76+
77+ std::sort (normalized_names.begin (), normalized_names.end ());
78+
79+ size_t seed = 0 ;
80+
81+ boost::hash_combine (seed, static_cast <uint32_t >(normalized_names.size ()));
82+ for (const auto & name : normalized_names) {
83+ boost::hash_combine (seed, hash_fnv1a (name));
84+ }
85+
86+ return seed;
87+ }
88+
89+ // Returns TriStateBool::TRUE_ if a new instance was created, TriStateBool::FALSE_ if an existing instance was returned,
90+ // or TriStateBool::UNKNOWN_ if there was an error (and model_instance_out will be set to -1 in this case)
91+ TriStateBool model_get_cached_ui_render_instance (int model_num, int * model_instance_out, cached_ui_render_instance_type type, uint32_t instance_data_hash)
92+ {
93+ Assertion (model_instance_out != nullptr , " model_instance_out must not be null!" );
94+ if (model_instance_out == nullptr ) {
95+ return TriStateBool::UNKNOWN_ ;
96+ }
97+
98+ cached_ui_render_instance_key key;
99+ key.model_num = model_num;
100+ key.type = type;
101+ key.state_instance_id = gameseq_get_state_instance_id ();
102+ key.instance_data_hash = instance_data_hash;
103+
104+ auto & entry = Cached_ui_render_instance_cache[key];
105+
106+ auto created_new = false ;
107+
108+ if (entry.model_instance < 0 ) {
109+ entry.model_instance = model_create_instance (model_objnum_special::OBJNUM_NONE , model_num);
110+ if (entry.model_instance < 0 ) {
111+ Warning (LOCATION , " Failed to create cached UI render model instance for model id %d." , model_num);
112+ Cached_ui_render_instance_cache.erase (key);
113+ *model_instance_out = -1 ;
114+ return TriStateBool::UNKNOWN_ ;
115+ }
116+ created_new = true ;
117+ }
118+
119+ entry.last_used_timestamp = ui_timestamp ();
120+ *model_instance_out = entry.model_instance ;
121+ return created_new ? TriStateBool::TRUE_ : TriStateBool::FALSE_ ;
122+ }
123+
124+ void model_process_cached_ui_render_instances ()
125+ {
126+ const auto now = ui_timestamp ();
127+
128+ if (Ui_render_instance_cache_last_processed_timestamp.isValid () &&
129+ ui_timestamp_get_delta (Ui_render_instance_cache_last_processed_timestamp, now) == 0 ) {
130+ return ;
131+ }
132+
133+ Ui_render_instance_cache_last_processed_timestamp = now;
134+
135+ for (auto it = Cached_ui_render_instance_cache.begin (); it != Cached_ui_render_instance_cache.end ();) {
136+ if (it->second .model_instance < 0 || !it->second .last_used_timestamp .isValid () ||
137+ ui_timestamp_get_delta (it->second .last_used_timestamp , now) > UI_RENDER_INSTANCE_CACHE_UNUSED_MS_GRACE ) {
138+ if (it->second .model_instance >= 0 ) {
139+ model_delete_instance (it->second .model_instance );
140+ }
141+ it = Cached_ui_render_instance_cache.erase (it);
142+ } else {
143+ ++it;
144+ }
145+ }
146+ }
147+
148+ void model_clear_cached_ui_render_instances ()
149+ {
150+ for (auto & instance : Cached_ui_render_instance_cache) {
151+ if (instance.second .model_instance >= 0 ) {
152+ model_delete_instance (instance.second .model_instance );
153+ }
154+ }
155+
156+ Cached_ui_render_instance_cache.clear ();
157+ Ui_render_instance_cache_last_processed_timestamp = UI_TIMESTAMP::invalid ();
158+ }
159+
54160model_batch_buffer TransformBufferHandler;
55161
56162model_render_params::model_render_params () :
@@ -3072,64 +3178,6 @@ void model_render_set_wireframe_color(const color* clr)
30723178 Wireframe_color = *clr;
30733179}
30743180
3075-
3076- namespace {
3077-
3078- struct cached_ui_render_instance_key {
3079- int model_num;
3080- cached_ui_render_instance_type type;
3081- int state_instance_id;
3082-
3083- bool operator ==(const cached_ui_render_instance_key& other) const {
3084- return model_num == other.model_num && type == other.type && state_instance_id == other.state_instance_id ;
3085- }
3086- };
3087-
3088- struct cached_ui_render_instance_key_hash {
3089- size_t operator ()(const cached_ui_render_instance_key& key) const {
3090- size_t seed = 0 ;
3091- auto hash_combine = [&seed](size_t value) {
3092- seed ^= value + 0x9e3779b9 + (seed << 6 ) + (seed >> 2 );
3093- };
3094-
3095- hash_combine (std::hash<int >{}(key.model_num ));
3096- hash_combine (std::hash<int >{}(static_cast <int >(key.type )));
3097- hash_combine (std::hash<int >{}(key.state_instance_id ));
3098- return seed;
3099- }
3100- };
3101-
3102- struct cached_ui_render_instance_entry {
3103- int model_instance = -1 ;
3104- int last_used_frame = -1 ;
3105- };
3106-
3107- SCP_unordered_map<cached_ui_render_instance_key, cached_ui_render_instance_entry, cached_ui_render_instance_key_hash> Cached_ui_render_instance_cache;
3108- int Cached_ui_render_instance_cache_last_gc_frame = -1 ;
3109- constexpr int UI_RENDER_INSTANCE_CACHE_UNUSED_FRAME_GRACE = 2 ;
3110-
3111- void model_gc_cached_ui_render_instances ()
3112- {
3113- if (Cached_ui_render_instance_cache_last_gc_frame == Framecount) {
3114- return ;
3115- }
3116-
3117- Cached_ui_render_instance_cache_last_gc_frame = Framecount;
3118-
3119- for (auto it = Cached_ui_render_instance_cache.begin (); it != Cached_ui_render_instance_cache.end (); ) {
3120- if (it->second .model_instance < 0 || (Framecount - it->second .last_used_frame ) > UI_RENDER_INSTANCE_CACHE_UNUSED_FRAME_GRACE ) {
3121- if (it->second .model_instance >= 0 ) {
3122- model_delete_instance (it->second .model_instance );
3123- }
3124- it = Cached_ui_render_instance_cache.erase (it);
3125- } else {
3126- ++it;
3127- }
3128- }
3129- }
3130-
3131- }
3132-
31333181void modelinstance_replace_active_texture (polymodel_instance* pmi, const char * old_name, const char * new_name)
31343182{
31353183 Assert (pmi != nullptr );
@@ -3165,42 +3213,6 @@ void modelinstance_replace_active_texture(polymodel_instance* pmi, const char* o
31653213 Warning (LOCATION , " Invalid texture '%s' used for replacement texture" , old_name);
31663214}
31673215
3168- int model_get_cached_ui_render_instance (
3169- int model_num,
3170- cached_ui_render_instance_type type)
3171- {
3172- cached_ui_render_instance_key key;
3173- key.model_num = model_num;
3174- key.type = type;
3175- key.state_instance_id = gameseq_get_state_instance_id ();
3176-
3177- auto & entry = Cached_ui_render_instance_cache[key];
3178-
3179- if (entry.model_instance < 0 ) {
3180- entry.model_instance = model_create_instance (model_objnum_special::OBJNUM_NONE , model_num);
3181- }
3182-
3183- entry.last_used_frame = Framecount;
3184- return entry.model_instance ;
3185- }
3186-
3187- void model_process_cached_ui_render_instances ()
3188- {
3189- model_gc_cached_ui_render_instances ();
3190- }
3191-
3192- void model_clear_cached_ui_render_instances ()
3193- {
3194- for (auto & instance : Cached_ui_render_instance_cache) {
3195- if (instance.second .model_instance >= 0 ) {
3196- model_delete_instance (instance.second .model_instance );
3197- }
3198- }
3199-
3200- Cached_ui_render_instance_cache.clear ();
3201- Cached_ui_render_instance_cache_last_gc_frame = -1 ;
3202- }
3203-
32043216// renders a model as if in the tech room or briefing UI
32053217// model_type 1 for ship class, 2 for weapon class, 3 for pof
32063218bool render_tech_model (tech_render_type model_type, int x1, int y1, int x2, int y2, float zoom, bool lighting, int class_idx, const matrix* orient, const SCP_string &pof_filename, float close_zoom, const vec3d *close_pos, const SCP_string& tcolor)
@@ -3317,7 +3329,7 @@ bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int
33173329
33183330 // Get a cached UI render instance for ships so repeated UI/Lua renders avoid per-call allocation churn
33193331 if (model_type == TECH_SHIP ) {
3320- model_instance = model_get_cached_ui_render_instance (model_num, cached_ui_render_instance_type::tech_room);
3332+ model_get_cached_ui_render_instance (model_num, &model_instance , cached_ui_render_instance_type::tech_room);
33213333 model_set_up_techroom_instance (&Ship_info[class_idx], model_instance);
33223334 }
33233335
0 commit comments