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,109 @@ 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+ SCP_tolower (normalized);
72+ normalized_names.push_back (std::move (normalized));
73+ }
74+
75+ std::sort (normalized_names.begin (), normalized_names.end ());
76+
77+ size_t seed = 0 ;
78+
79+ boost::hash_combine (seed, static_cast <uint32_t >(normalized_names.size ()));
80+ for (const auto & name : normalized_names) {
81+ boost::hash_combine (seed, hash_fnv1a (name));
82+ }
83+
84+ return seed;
85+ }
86+
87+ // Returns TriStateBool::TRUE_ if a new instance was created, TriStateBool::FALSE_ if an existing instance was returned,
88+ // or TriStateBool::UNKNOWN_ if there was an error (and model_instance_out will be set to -1 in this case)
89+ 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)
90+ {
91+ Assertion (model_instance_out != nullptr , " model_instance_out must not be null!" );
92+ if (model_instance_out == nullptr ) {
93+ return TriStateBool::UNKNOWN_;
94+ }
95+
96+ cached_ui_render_instance_key key;
97+ key.model_num = model_num;
98+ key.type = type;
99+ key.state_instance_id = gameseq_get_state_instance_id ();
100+ key.instance_data_hash = instance_data_hash;
101+
102+ auto & entry = Cached_ui_render_instance_cache[key];
103+
104+ auto created_new = false ;
105+
106+ if (entry.model_instance < 0 ) {
107+ entry.model_instance = model_create_instance (model_objnum_special::OBJNUM_NONE, model_num);
108+ if (entry.model_instance < 0 ) {
109+ Warning (LOCATION, " Failed to create cached UI render model instance for model id %d." , model_num);
110+ Cached_ui_render_instance_cache.erase (key);
111+ *model_instance_out = -1 ;
112+ return TriStateBool::UNKNOWN_;
113+ }
114+ created_new = true ;
115+ }
116+
117+ entry.last_used_timestamp = ui_timestamp ();
118+ *model_instance_out = entry.model_instance ;
119+ return created_new ? TriStateBool::TRUE_ : TriStateBool::FALSE_;
120+ }
121+
122+ void model_process_cached_ui_render_instances ()
123+ {
124+ const auto now = ui_timestamp ();
125+
126+ if (Ui_render_instance_cache_last_processed_timestamp.isValid () &&
127+ ui_timestamp_get_delta (Ui_render_instance_cache_last_processed_timestamp, now) == 0 ) {
128+ return ;
129+ }
130+
131+ Ui_render_instance_cache_last_processed_timestamp = now;
132+
133+ for (auto it = Cached_ui_render_instance_cache.begin (); it != Cached_ui_render_instance_cache.end ();) {
134+ if (it->second .model_instance < 0 || !it->second .last_used_timestamp .isValid () ||
135+ ui_timestamp_get_delta (it->second .last_used_timestamp , now) > UI_RENDER_INSTANCE_CACHE_UNUSED_MS_GRACE) {
136+ if (it->second .model_instance >= 0 ) {
137+ model_delete_instance (it->second .model_instance );
138+ }
139+ it = Cached_ui_render_instance_cache.erase (it);
140+ } else {
141+ ++it;
142+ }
143+ }
144+ }
145+
146+ void model_clear_cached_ui_render_instances ()
147+ {
148+ for (auto & instance : Cached_ui_render_instance_cache) {
149+ if (instance.second .model_instance >= 0 ) {
150+ model_delete_instance (instance.second .model_instance );
151+ }
152+ }
153+
154+ Cached_ui_render_instance_cache.clear ();
155+ Ui_render_instance_cache_last_processed_timestamp = UI_TIMESTAMP::invalid ();
156+ }
157+
54158model_batch_buffer TransformBufferHandler;
55159
56160model_render_params::model_render_params () :
@@ -3072,64 +3176,6 @@ void model_render_set_wireframe_color(const color* clr)
30723176 Wireframe_color = *clr;
30733177}
30743178
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-
31333179void modelinstance_replace_active_texture (polymodel_instance* pmi, const char * old_name, const char * new_name)
31343180{
31353181 Assert (pmi != nullptr );
@@ -3165,42 +3211,6 @@ void modelinstance_replace_active_texture(polymodel_instance* pmi, const char* o
31653211 Warning (LOCATION, " Invalid texture '%s' used for replacement texture" , old_name);
31663212}
31673213
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-
32043214// renders a model as if in the tech room or briefing UI
32053215// model_type 1 for ship class, 2 for weapon class, 3 for pof
32063216bool 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 +3327,7 @@ bool render_tech_model(tech_render_type model_type, int x1, int y1, int x2, int
33173327
33183328 // Get a cached UI render instance for ships so repeated UI/Lua renders avoid per-call allocation churn
33193329 if (model_type == TECH_SHIP) {
3320- model_instance = model_get_cached_ui_render_instance (model_num, cached_ui_render_instance_type::tech_room);
3330+ model_get_cached_ui_render_instance (model_num, &model_instance , cached_ui_render_instance_type::tech_room);
33213331 model_set_up_techroom_instance (&Ship_info[class_idx], model_instance);
33223332 }
33233333
0 commit comments