Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/main/scene_tree_fti.cpp
9896 views
1
/**************************************************************************/
2
/* scene_tree_fti.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#ifndef _3D_DISABLED
32
33
#include "scene_tree_fti.h"
34
35
#include "core/config/engine.h"
36
#include "core/config/project_settings.h"
37
#include "core/math/transform_interpolator.h"
38
#include "core/os/os.h"
39
#include "scene/3d/visual_instance_3d.h"
40
41
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
42
#include "scene_tree_fti_tests.h"
43
#endif
44
45
#ifdef DEV_ENABLED
46
47
// Uncomment this to enable some slow extra DEV_ENABLED
48
// checks to ensure there aren't more than one object added to the lists.
49
// #define GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
50
51
// Uncomment this to regularly print the tree that is being interpolated.
52
// #define GODOT_SCENE_TREE_FTI_PRINT_TREE
53
54
#endif
55
56
void SceneTreeFTI::_reset_node3d_flags(Node3D &r_node) {
57
r_node.data.fti_on_tick_xform_list = false;
58
r_node.data.fti_on_tick_property_list = false;
59
r_node.data.fti_on_frame_xform_list = false;
60
r_node.data.fti_on_frame_property_list = false;
61
r_node.data.fti_global_xform_interp_set = false;
62
r_node.data.fti_frame_xform_force_update = false;
63
r_node.data.fti_processed = false;
64
}
65
66
void SceneTreeFTI::_reset_flags(Node *p_node) {
67
Node3D *s = Object::cast_to<Node3D>(p_node);
68
69
if (s) {
70
_reset_node3d_flags(*s);
71
72
// In most cases the later NOTIFICATION_RESET_PHYSICS_INTERPOLATION
73
// will reset this, but this should help cover hidden nodes.
74
s->data.local_transform_prev = s->get_transform();
75
}
76
77
for (int n = 0; n < p_node->get_child_count(); n++) {
78
_reset_flags(p_node->get_child(n));
79
}
80
}
81
82
void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) {
83
if (data.enabled == p_enabled) {
84
return;
85
}
86
MutexLock(data.mutex);
87
88
data.tick_xform_list[0].clear();
89
data.tick_xform_list[1].clear();
90
91
data.frame_xform_list.clear();
92
data.frame_xform_list_forced.clear();
93
94
data.tick_property_list[0].clear();
95
data.tick_property_list[1].clear();
96
97
data.frame_property_list.clear();
98
data.request_reset_list.clear();
99
100
_clear_depth_lists();
101
102
// Node3D flags must be reset.
103
if (p_root) {
104
_reset_flags(p_root);
105
}
106
107
data.enabled = p_enabled;
108
}
109
110
void SceneTreeFTI::tick_update() {
111
if (!data.enabled) {
112
return;
113
}
114
MutexLock(data.mutex);
115
116
_update_request_resets();
117
118
uint32_t curr_mirror = data.mirror;
119
uint32_t prev_mirror = curr_mirror ? 0 : 1;
120
121
LocalVector<Node3D *> &curr = data.tick_xform_list[curr_mirror];
122
LocalVector<Node3D *> &prev = data.tick_xform_list[prev_mirror];
123
124
// First detect on the previous list but not on this tick list.
125
for (uint32_t n = 0; n < prev.size(); n++) {
126
Node3D *s = prev[n];
127
if (!s->data.fti_on_tick_xform_list) {
128
// Needs a reset so jittering will stop.
129
s->fti_pump_xform();
130
131
// Optimization - detect whether we have rested at identity xform.
132
s->data.fti_is_identity_xform = s->data.local_transform == Transform3D();
133
134
// This may not get updated so set it to the same as global xform.
135
// TODO: double check this is the best value.
136
s->data.global_transform_interpolated = s->get_global_transform();
137
138
// Remove from interpolation list.
139
if (s->data.fti_on_frame_xform_list) {
140
_node_remove_from_frame_list(*s, false);
141
}
142
143
// Ensure that the node gets at least ONE further
144
// update in the resting position in the next frame update.
145
if (!s->data.fti_frame_xform_force_update) {
146
_node_add_to_frame_list(*s, true);
147
}
148
}
149
}
150
151
LocalVector<Node3D *> &curr_prop = data.tick_property_list[curr_mirror];
152
LocalVector<Node3D *> &prev_prop = data.tick_property_list[prev_mirror];
153
154
// Detect on the previous property list but not on this tick list.
155
for (uint32_t n = 0; n < prev_prop.size(); n++) {
156
Node3D *s = prev_prop[n];
157
158
if (!s->data.fti_on_tick_property_list) {
159
// Needs a reset so jittering will stop.
160
s->fti_pump_xform();
161
162
// Ensure the servers are up to date with the final resting value.
163
s->fti_update_servers_property();
164
165
// Remove from interpolation list.
166
if (s->data.fti_on_frame_property_list) {
167
s->data.fti_on_frame_property_list = false;
168
data.frame_property_list.erase_unordered(s);
169
170
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
171
DEV_CHECK_ONCE(data.frame_property_list.find(s) == -1);
172
#endif
173
}
174
}
175
}
176
177
// Pump all on the property list that are NOT on the tick list.
178
for (uint32_t n = 0; n < curr_prop.size(); n++) {
179
Node3D *s = curr_prop[n];
180
181
// Reset, needs to be marked each tick.
182
s->data.fti_on_tick_property_list = false;
183
s->fti_pump_property();
184
}
185
186
// Now pump all on the current list.
187
for (uint32_t n = 0; n < curr.size(); n++) {
188
Node3D *s = curr[n];
189
190
// Reset, needs to be marked each tick.
191
s->data.fti_on_tick_xform_list = false;
192
193
// Pump.
194
s->fti_pump_xform();
195
}
196
197
// Clear previous list and flip.
198
prev.clear();
199
prev_prop.clear();
200
data.mirror = prev_mirror;
201
}
202
203
void SceneTreeFTI::_update_request_resets() {
204
// For instance when first adding to the tree, when the previous transform is
205
// unset, to prevent streaking from the origin.
206
for (uint32_t n = 0; n < data.request_reset_list.size(); n++) {
207
Node3D *s = data.request_reset_list[n];
208
if (s->_is_physics_interpolation_reset_requested()) {
209
if (s->_is_vi_visible() && !s->_is_using_identity_transform()) {
210
s->notification(Node3D::NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
211
}
212
213
s->_set_physics_interpolation_reset_requested(false);
214
}
215
}
216
217
data.request_reset_list.clear();
218
}
219
220
void SceneTreeFTI::node_3d_request_reset(Node3D *p_node) {
221
DEV_CHECK_ONCE(data.enabled);
222
DEV_ASSERT(p_node);
223
224
MutexLock(data.mutex);
225
226
if (!p_node->_is_physics_interpolation_reset_requested()) {
227
p_node->_set_physics_interpolation_reset_requested(true);
228
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
229
DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
230
#endif
231
data.request_reset_list.push_back(p_node);
232
}
233
}
234
235
void SceneTreeFTI::_node_3d_notify_set_property(Node3D &r_node) {
236
if (!r_node.is_physics_interpolated()) {
237
return;
238
}
239
240
DEV_CHECK_ONCE(data.enabled);
241
242
// Note that a Node3D can be on BOTH the transform list and the property list.
243
if (!r_node.data.fti_on_tick_property_list) {
244
r_node.data.fti_on_tick_property_list = true;
245
246
// Should only appear once in the property list.
247
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
248
DEV_CHECK_ONCE(data.tick_property_list[data.mirror].find(&r_node) == -1);
249
#endif
250
data.tick_property_list[data.mirror].push_back(&r_node);
251
}
252
253
if (!r_node.data.fti_on_frame_property_list) {
254
r_node.data.fti_on_frame_property_list = true;
255
256
// Should only appear once in the property frame list.
257
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
258
DEV_CHECK_ONCE(data.frame_property_list.find(&r_node) == -1);
259
#endif
260
data.frame_property_list.push_back(&r_node);
261
}
262
}
263
264
void SceneTreeFTI::_create_depth_lists() {
265
uint32_t first_list = data.frame_start ? 0 : 1;
266
267
for (uint32_t l = first_list; l < 2; l++) {
268
LocalVector<Node3D *> &source_list = l == 0 ? data.frame_xform_list : data.frame_xform_list_forced;
269
270
#ifdef DEBUG_ENABLED
271
bool log_nodes_moved_on_frame = (data.traversal_mode == TM_DEBUG) && !data.frame_start && data.periodic_debug_log;
272
if (log_nodes_moved_on_frame) {
273
if (source_list.size()) {
274
print_line(String("\n") + itos(source_list.size()) + " nodes moved during frame:");
275
} else {
276
print_line("0 nodes moved during frame.");
277
}
278
}
279
#endif
280
281
for (uint32_t n = 0; n < source_list.size(); n++) {
282
Node3D *s = source_list[n];
283
s->data.fti_processed = false;
284
285
int32_t depth = s->_get_scene_tree_depth();
286
287
// This shouldn't happen, but wouldn't be terrible if it did.
288
DEV_ASSERT(depth >= 0);
289
depth = MIN(depth, (int32_t)data.scene_tree_depth_limit);
290
291
LocalVector<Node3D *> &dest_list = data.dirty_node_depth_lists[depth];
292
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
293
// Shouldn't really happen, but duplicates don't really matter that much.
294
if (dest_list.find(s) != -1) {
295
ERR_FAIL_COND(dest_list.find(s) != -1);
296
}
297
#endif
298
299
#ifdef DEBUG_ENABLED
300
if (log_nodes_moved_on_frame) {
301
print_line("\t" + s->get_name());
302
}
303
#endif
304
305
// Prevent being added to the dest_list twice when on
306
// the frame_xform_list AND the frame_xform_list_forced.
307
if ((l == 0) && s->data.fti_frame_xform_force_update) {
308
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
309
DEV_ASSERT(data.frame_xform_list_forced.find(s) != -1);
310
#endif
311
continue;
312
}
313
314
dest_list.push_back(s);
315
}
316
}
317
}
318
319
void SceneTreeFTI::_clear_depth_lists() {
320
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
321
data.dirty_node_depth_lists[d].clear();
322
}
323
}
324
325
void SceneTreeFTI::_node_add_to_frame_list(Node3D &r_node, bool p_forced) {
326
if (p_forced) {
327
DEV_ASSERT(!r_node.data.fti_frame_xform_force_update);
328
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
329
int64_t found = data.frame_xform_list_forced.find(&r_node);
330
if (found != -1) {
331
ERR_FAIL_COND(found != -1);
332
}
333
#endif
334
data.frame_xform_list_forced.push_back(&r_node);
335
r_node.data.fti_frame_xform_force_update = true;
336
} else {
337
DEV_ASSERT(!r_node.data.fti_on_frame_xform_list);
338
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
339
int64_t found = data.frame_xform_list.find(&r_node);
340
if (found != -1) {
341
ERR_FAIL_COND(found != -1);
342
}
343
#endif
344
data.frame_xform_list.push_back(&r_node);
345
r_node.data.fti_on_frame_xform_list = true;
346
}
347
}
348
349
void SceneTreeFTI::_node_remove_from_frame_list(Node3D &r_node, bool p_forced) {
350
if (p_forced) {
351
DEV_ASSERT(r_node.data.fti_frame_xform_force_update);
352
data.frame_xform_list_forced.erase_unordered(&r_node);
353
r_node.data.fti_frame_xform_force_update = false;
354
} else {
355
DEV_ASSERT(r_node.data.fti_on_frame_xform_list);
356
data.frame_xform_list.erase_unordered(&r_node);
357
r_node.data.fti_on_frame_xform_list = false;
358
}
359
}
360
361
void SceneTreeFTI::_node_3d_notify_set_xform(Node3D &r_node) {
362
DEV_CHECK_ONCE(data.enabled);
363
364
if (!r_node.is_physics_interpolated()) {
365
// Force an update of non-interpolated to servers
366
// on the next traversal.
367
if (!r_node.data.fti_frame_xform_force_update) {
368
_node_add_to_frame_list(r_node, true);
369
}
370
371
// ToDo: Double check this is a win,
372
// non-interpolated nodes we always check for identity,
373
// *just in case*.
374
r_node.data.fti_is_identity_xform = r_node.get_transform() == Transform3D();
375
return;
376
}
377
378
r_node.data.fti_is_identity_xform = false;
379
380
if (!r_node.data.fti_on_tick_xform_list) {
381
r_node.data.fti_on_tick_xform_list = true;
382
383
// Should only appear once in the xform list.
384
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
385
DEV_CHECK_ONCE(data.tick_xform_list[data.mirror].find(&r_node) == -1);
386
#endif
387
data.tick_xform_list[data.mirror].push_back(&r_node);
388
389
// The following flag could have been previously set
390
// (for removal from the tick list).
391
// We no longer need this guarantee,
392
// however there is probably no downside to leaving it set
393
// as it will be cleared on the next frame anyway.
394
// This line is left for reference.
395
// r_node.data.fti_frame_xform_force_update = false;
396
}
397
398
if (!r_node.data.fti_on_frame_xform_list) {
399
_node_add_to_frame_list(r_node, false);
400
}
401
402
// If we are in the second half of a frame, always add to the force update list,
403
// because we ignore the tick update list during the second update.
404
if (data.in_frame) {
405
if (!r_node.data.fti_frame_xform_force_update) {
406
_node_add_to_frame_list(r_node, true);
407
}
408
}
409
}
410
411
void SceneTreeFTI::node_3d_notify_delete(Node3D *p_node) {
412
if (!data.enabled) {
413
return;
414
}
415
416
ERR_FAIL_NULL(p_node);
417
418
MutexLock(data.mutex);
419
420
// Remove from frame lists.
421
if (p_node->data.fti_on_frame_xform_list) {
422
_node_remove_from_frame_list(*p_node, false);
423
}
424
if (p_node->data.fti_frame_xform_force_update) {
425
_node_remove_from_frame_list(*p_node, true);
426
}
427
428
// Ensure this is kept in sync with the lists, in case a node
429
// is removed and re-added to the scene tree multiple times
430
// on the same frame / tick.
431
p_node->_set_physics_interpolation_reset_requested(false);
432
433
// Keep flags consistent for the same as a new node,
434
// because this node may re-enter the scene tree.
435
_reset_node3d_flags(*p_node);
436
437
// This can potentially be optimized for large scenes with large churn,
438
// as it will be doing a linear search through the lists.
439
data.tick_xform_list[0].erase_unordered(p_node);
440
data.tick_xform_list[1].erase_unordered(p_node);
441
442
data.tick_property_list[0].erase_unordered(p_node);
443
data.tick_property_list[1].erase_unordered(p_node);
444
445
data.frame_property_list.erase_unordered(p_node);
446
data.request_reset_list.erase_unordered(p_node);
447
448
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
449
// There should only be one occurrence on the lists.
450
// Check this in DEV_ENABLED builds.
451
DEV_CHECK_ONCE(data.tick_xform_list[0].find(p_node) == -1);
452
DEV_CHECK_ONCE(data.tick_xform_list[1].find(p_node) == -1);
453
454
DEV_CHECK_ONCE(data.tick_property_list[0].find(p_node) == -1);
455
DEV_CHECK_ONCE(data.tick_property_list[1].find(p_node) == -1);
456
457
DEV_CHECK_ONCE(data.frame_property_list.find(p_node) == -1);
458
DEV_CHECK_ONCE(data.request_reset_list.find(p_node) == -1);
459
460
DEV_CHECK_ONCE(data.frame_xform_list.find(p_node) == -1);
461
DEV_CHECK_ONCE(data.frame_xform_list_forced.find(p_node) == -1);
462
#endif
463
}
464
465
void SceneTreeFTI::_update_dirty_nodes(Node *p_node, uint32_t p_current_half_frame, float p_interpolation_fraction, bool p_active, const Transform3D *p_parent_global_xform, int p_depth) {
466
Node3D *s = Object::cast_to<Node3D>(p_node);
467
468
#ifdef DEBUG_ENABLED
469
data.debug_node_count++;
470
#endif
471
472
// Don't recurse into hidden branches.
473
if (s && !s->data.visible) {
474
// NOTE : If we change from recursing entire tree, we should do an is_visible_in_tree()
475
// check for the first of the branch.
476
return;
477
}
478
479
// Temporary direct access to children cache for speed.
480
// Maybe replaced later by a more generic fast access method
481
// for children.
482
p_node->_update_children_cache();
483
Span<Node *> children = p_node->data.children_cache.span();
484
uint32_t num_children = children.size();
485
486
// Not a Node3D.
487
// Could be e.g. a viewport or something
488
// so we should still recurse to children.
489
if (!s) {
490
for (uint32_t n = 0; n < num_children; n++) {
491
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
492
}
493
return;
494
}
495
496
// We are going to be using data.global_transform, so
497
// we need to ensure data.global_transform is not dirty!
498
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_TRANSFORM)) {
499
_ALLOW_DISCARD_ s->get_global_transform();
500
}
501
502
// Start the active interpolation chain from here onwards
503
// as we recurse further into the SceneTree.
504
// Once we hit an active (interpolated) node, we have to fully
505
// process all ancestors because their xform will also change.
506
// Anything not moving (inactive) higher in the tree need not be processed.
507
if (!p_active) {
508
if (data.frame_start) {
509
// On the frame start, activate whenever we hit something that requests interpolation.
510
if (s->data.fti_on_frame_xform_list || s->data.fti_frame_xform_force_update) {
511
p_active = true;
512
}
513
} else {
514
// On the frame end, we want to re-interpolate *anything* that has moved
515
// since the frame start.
516
if (s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM)) {
517
p_active = true;
518
519
#if 0
520
if (data.periodic_debug_log) {
521
print_line("activating on : " + s->get_name());
522
}
523
#endif
524
}
525
}
526
}
527
528
// ToDo : Check global_xform_interp is up to date for nodes
529
// that are not traversed by the depth lists.
530
if (data.frame_start) {
531
// Mark on the Node3D whether we have set global_transform_interp.
532
// This can later be used when calling `get_global_transform_interpolated()`
533
// to know which xform to return.
534
s->data.fti_global_xform_interp_set = p_active;
535
}
536
537
if (p_active) {
538
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
539
bool dirty = s->_test_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
540
541
if (data.periodic_debug_log && !data.use_optimized_traversal_method && !data.frame_start) {
542
String sz;
543
for (int n = 0; n < p_depth; n++) {
544
sz += "\t";
545
}
546
print_line(sz + p_node->get_name() + (dirty ? " DIRTY" : "") + (s->get_transform() == Transform3D() ? "\t[IDENTITY]" : ""));
547
}
548
#endif
549
550
// First calculate our local xform.
551
// This will either use interpolation, or just use the current local if not interpolated.
552
Transform3D local_interp;
553
if (s->is_physics_interpolated()) {
554
// There may be no need to interpolate if the node has not been moved recently
555
// and is therefore not on the tick list...
556
if (s->data.fti_on_tick_xform_list) {
557
// Make sure to call `get_transform()` rather than using local_transform directly, because
558
// local_transform may be dirty and need updating from rotation / scale.
559
TransformInterpolator::interpolate_transform_3d(s->data.local_transform_prev, s->get_transform(), local_interp, p_interpolation_fraction);
560
} else {
561
local_interp = s->get_transform();
562
}
563
} else {
564
local_interp = s->get_transform();
565
}
566
567
// Concatenate parent xform.
568
if (!s->is_set_as_top_level()) {
569
if (p_parent_global_xform) {
570
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? *p_parent_global_xform : ((*p_parent_global_xform) * local_interp);
571
} else {
572
const Node3D *parent = s->get_parent_node_3d();
573
574
if (parent) {
575
const Transform3D &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->get_global_transform();
576
s->data.global_transform_interpolated = s->data.fti_is_identity_xform ? parent_glob : parent_glob * local_interp;
577
} else {
578
s->data.global_transform_interpolated = local_interp;
579
}
580
}
581
} else {
582
s->data.global_transform_interpolated = local_interp;
583
}
584
585
// Watch for this, disable_scale can cause incredibly confusing bugs
586
// and must be checked for when calculating global xforms.
587
if (s->data.disable_scale) {
588
s->data.global_transform_interpolated.basis.orthonormalize();
589
}
590
591
// Upload to RenderingServer the interpolated global xform.
592
s->fti_update_servers_xform();
593
594
// Ensure branches are only processed once on each traversal.
595
s->data.fti_processed = true;
596
597
#ifdef DEBUG_ENABLED
598
data.debug_nodes_processed++;
599
#endif
600
} // if active.
601
602
// Remove the dirty interp flag from EVERYTHING as we go.
603
s->_clear_dirty_bits(Node3D::DIRTY_GLOBAL_INTERPOLATED_TRANSFORM);
604
605
// Recurse to children.
606
for (uint32_t n = 0; n < num_children; n++) {
607
_update_dirty_nodes(children.ptr()[n], p_current_half_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
608
}
609
}
610
611
void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) {
612
if (!data.enabled || !p_root) {
613
return;
614
}
615
MutexLock(data.mutex);
616
617
data.frame_start = p_frame_start;
618
data.in_frame = true;
619
620
_update_request_resets();
621
622
float interpolation_fraction = Engine::get_singleton()->get_physics_interpolation_fraction();
623
uint32_t frame = Engine::get_singleton()->get_frames_drawn();
624
625
uint64_t before = 0;
626
#ifdef DEBUG_ENABLED
627
if (data.traversal_mode == TM_DEBUG) {
628
before = OS::get_singleton()->get_ticks_usec();
629
630
if (p_frame_start && ((frame % ((60 * 15) - 3)) == 0)) {
631
data.periodic_debug_log = true;
632
}
633
}
634
635
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
636
if (data.periodic_debug_log) {
637
print_line(String("\nScene: ") + (data.frame_start ? "start" : "end") + "\n");
638
}
639
#endif
640
#endif
641
642
data.debug_node_count = 0;
643
data.debug_nodes_processed = 0;
644
645
uint32_t half_frame = p_frame_start ? (frame * 2) : ((frame * 2) + 1);
646
647
bool print_debug_stats = false;
648
switch (data.traversal_mode) {
649
case TM_LEGACY: {
650
data.use_optimized_traversal_method = false;
651
} break;
652
case TM_DEBUG: {
653
// Switch on alternate frames between the two methods.
654
data.use_optimized_traversal_method = (frame % 2) == 1;
655
656
// Odd number ensures we debug stats for both methods.
657
print_debug_stats = (frame % ((60 * 8) - 1)) == 0;
658
} break;
659
default: {
660
data.use_optimized_traversal_method = true;
661
} break;
662
}
663
664
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
665
_tests->frame_update(p_root, half_frame, interpolation_fraction);
666
#else
667
668
uint32_t skipped = 0;
669
670
if (!data.use_optimized_traversal_method) {
671
// Reference approach.
672
// Traverse the entire scene tree.
673
// Slow, but robust.
674
_update_dirty_nodes(p_root, half_frame, interpolation_fraction, false);
675
} else {
676
// Optimized approach.
677
// Traverse from depth lists.
678
// Be sure to check against the reference
679
// implementation when making changes.
680
_create_depth_lists();
681
682
for (uint32_t d = 0; d < data.scene_tree_depth_limit; d++) {
683
const LocalVector<Node3D *> &list = data.dirty_node_depth_lists[d];
684
685
#if 0
686
if (list.size() > 0) {
687
print_line("depth " + itos(d) + ", contains " + itos(list.size()));
688
}
689
#endif
690
691
for (uint32_t n = 0; n < list.size(); n++) {
692
// Already processed this frame?
693
Node3D *s = list[n];
694
695
if (s->data.fti_processed) {
696
#ifdef DEBUG_ENABLED
697
skipped++;
698
#endif
699
continue;
700
}
701
702
// The first node requires a recursive visibility check
703
// up the tree, because `is_visible()` only returns the node
704
// local flag.
705
if (Object::cast_to<VisualInstance3D>(s)) {
706
if (!s->_is_vi_visible()) {
707
#ifdef DEBUG_ENABLED
708
skipped++;
709
#endif
710
continue;
711
}
712
} else if (!s->is_visible_in_tree()) {
713
#ifdef DEBUG_ENABLED
714
skipped++;
715
#endif
716
continue;
717
}
718
719
_update_dirty_nodes(s, half_frame, interpolation_fraction, true);
720
}
721
}
722
723
_clear_depth_lists();
724
}
725
726
if (print_debug_stats) {
727
uint64_t after = OS::get_singleton()->get_ticks_usec();
728
print_line(String(data.use_optimized_traversal_method ? "FTI optimized" : "FTI reference") + " nodes traversed : " + itos(data.debug_node_count) + (skipped == 0 ? "" : ", skipped " + itos(skipped)) + ", processed : " + itos(data.debug_nodes_processed) + ", took " + itos(after - before) + " usec " + (data.frame_start ? "(start)" : "(end)"));
729
}
730
731
#endif // not GODOT_SCENE_TREE_FTI_VERIFY
732
733
// In theory we could clear the `force_update` flags from the nodes in the traversal.
734
// The problem is that hidden nodes are not recursed into, therefore the flags would
735
// never get cleared and could get out of sync with the forced list.
736
// So instead we are clearing them here manually.
737
// This is not ideal in terms of cache coherence so perhaps another method can be
738
// explored in future.
739
uint32_t forced_list_size = data.frame_xform_list_forced.size();
740
for (uint32_t n = 0; n < forced_list_size; n++) {
741
Node3D *s = data.frame_xform_list_forced[n];
742
s->data.fti_frame_xform_force_update = false;
743
}
744
data.frame_xform_list_forced.clear();
745
746
if (!p_frame_start && data.periodic_debug_log) {
747
data.periodic_debug_log = false;
748
}
749
750
// Update the properties once off at the end of the frame.
751
// No need for two passes for properties.
752
if (!p_frame_start) {
753
for (uint32_t n = 0; n < data.frame_property_list.size(); n++) {
754
Node3D *s = data.frame_property_list[n];
755
s->fti_update_servers_property();
756
}
757
}
758
759
// Marks the end of the frame.
760
// Enables us to recognize when change notifications
761
// come in _during_ a frame (they get treated differently).
762
if (!data.frame_start) {
763
data.in_frame = false;
764
}
765
}
766
767
SceneTreeFTI::SceneTreeFTI() {
768
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
769
_tests = memnew(SceneTreeFTITests(*this));
770
#endif
771
772
Variant traversal_mode_string = GLOBAL_DEF("physics/3d/physics_interpolation/scene_traversal", "DEFAULT");
773
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "physics/3d/physics_interpolation/scene_traversal", PROPERTY_HINT_ENUM, "DEFAULT,Legacy,Debug"));
774
775
data.traversal_mode = TM_DEFAULT;
776
777
if (traversal_mode_string == "Legacy") {
778
data.traversal_mode = TM_LEGACY;
779
} else if (traversal_mode_string == "Debug") {
780
// Don't allow debug mode in final exports,
781
// it will almost certainly be a mistake.
782
#ifdef DEBUG_ENABLED
783
data.traversal_mode = TM_DEBUG;
784
#else
785
data.traversal_mode = TM_DEFAULT;
786
#endif
787
}
788
789
switch (data.traversal_mode) {
790
default: {
791
print_verbose("SceneTreeFTI: traversal method DEFAULT");
792
} break;
793
case TM_LEGACY: {
794
print_verbose("SceneTreeFTI: traversal method Legacy");
795
} break;
796
case TM_DEBUG: {
797
print_verbose("SceneTreeFTI: traversal method Debug");
798
} break;
799
}
800
801
#ifdef GODOT_SCENE_TREE_FTI_EXTRA_CHECKS
802
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_EXTRA_CHECKS defined");
803
#endif
804
#ifdef GODOT_SCENE_TREE_FTI_PRINT_TREE
805
print_line("SceneTreeFTI : GODOT_SCENE_TREE_FTI_PRINT_TREE defined");
806
#endif
807
}
808
809
SceneTreeFTI::~SceneTreeFTI() {
810
#ifdef GODOT_SCENE_TREE_FTI_VERIFY
811
if (_tests) {
812
memfree(_tests);
813
_tests = nullptr;
814
}
815
#endif
816
}
817
818
#endif // ndef _3D_DISABLED
819
820