Skip to content

Commit

Permalink
Optimize threading-sensitive node data for single-threaded processing
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomShaper committed May 17, 2023
1 parent 44cc0d2 commit 50cf3d6
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 60 deletions.
5 changes: 4 additions & 1 deletion core/templates/safe_refcount.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@
// value and, as an important benefit, you can be sure the value is properly synchronized
// even with threads that are already running.

// This is used in very specific areas of the engine where it's critical that these guarantees are held
// These are used in very specific areas of the engine where it's critical that these guarantees are held
#define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type) \
static_assert(sizeof(SafeNumeric<m_type>) == sizeof(m_type)); \
static_assert(alignof(SafeNumeric<m_type>) == alignof(m_type)); \
static_assert(std::is_trivially_destructible<std::atomic<m_type>>::value);
#define SAFE_FLAG_TYPE_PUN_GUARANTEES \
static_assert(sizeof(SafeFlag) == sizeof(bool)); \
static_assert(alignof(SafeFlag) == alignof(bool));

template <class T>
class SafeNumeric {
Expand Down
50 changes: 31 additions & 19 deletions scene/2d/node_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,24 @@ void Node2D::_edit_set_rect(const Rect2 &p_edit_rect) {
}
#endif

void Node2D::_update_xform_values() {
void Node2D::_set_xform_dirty(bool p_dirty) const {
if (is_group_processing()) {
if (p_dirty) {
xform_dirty.mt.set();
} else {
xform_dirty.mt.clear();
}
} else {
xform_dirty.st = p_dirty;
}
}

void Node2D::_update_xform_values() const {
rotation = transform.get_rotation();
skew = transform.get_skew();
position = transform.columns[2];
scale = transform.get_scale();
xform_dirty.clear();
_set_xform_dirty(false);
}

void Node2D::_update_transform() {
Expand All @@ -144,17 +156,17 @@ void Node2D::reparent(Node *p_parent, bool p_keep_global_transform) {

void Node2D::set_position(const Point2 &p_pos) {
ERR_THREAD_GUARD;
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}
position = p_pos;
_update_transform();
}

void Node2D::set_rotation(real_t p_radians) {
ERR_THREAD_GUARD;
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}
rotation = p_radians;
_update_transform();
Expand All @@ -167,17 +179,17 @@ void Node2D::set_rotation_degrees(real_t p_degrees) {

void Node2D::set_skew(real_t p_radians) {
ERR_THREAD_GUARD;
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}
skew = p_radians;
_update_transform();
}

void Node2D::set_scale(const Size2 &p_scale) {
ERR_THREAD_GUARD;
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}
scale = p_scale;
// Avoid having 0 scale values, can lead to errors in physics and rendering.
Expand All @@ -192,17 +204,17 @@ void Node2D::set_scale(const Size2 &p_scale) {

Point2 Node2D::get_position() const {
ERR_READ_THREAD_GUARD_V(Point2());
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}

return position;
}

real_t Node2D::get_rotation() const {
ERR_READ_THREAD_GUARD_V(0);
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}

return rotation;
Expand All @@ -215,17 +227,17 @@ real_t Node2D::get_rotation_degrees() const {

real_t Node2D::get_skew() const {
ERR_READ_THREAD_GUARD_V(0);
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}

return skew;
}

Size2 Node2D::get_scale() const {
ERR_READ_THREAD_GUARD_V(Size2());
if (xform_dirty.is_set()) {
const_cast<Node2D *>(this)->_update_xform_values();
if (_is_xform_dirty()) {
_update_xform_values();
}

return scale;
Expand Down Expand Up @@ -362,7 +374,7 @@ void Node2D::set_global_scale(const Size2 &p_scale) {
void Node2D::set_transform(const Transform2D &p_transform) {
ERR_THREAD_GUARD;
transform = p_transform;
xform_dirty.set();
_set_xform_dirty(true);

RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), transform);

Expand Down
15 changes: 9 additions & 6 deletions scene/2d/node_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,20 @@
class Node2D : public CanvasItem {
GDCLASS(Node2D, CanvasItem);

SafeFlag xform_dirty;
Point2 position;
real_t rotation = 0.0;
Size2 scale = Vector2(1, 1);
real_t skew = 0.0;
mutable MTFlag xform_dirty;
mutable Point2 position;
mutable real_t rotation = 0.0;
mutable Size2 scale = Vector2(1, 1);
mutable real_t skew = 0.0;

Transform2D transform;

_FORCE_INLINE_ bool _is_xform_dirty() const { return is_group_processing() ? xform_dirty.mt.is_set() : xform_dirty.st; }
void _set_xform_dirty(bool p_dirty) const;

void _update_transform();

void _update_xform_values();
void _update_xform_values() const;

protected:
void _notification(int p_notification);
Expand Down
75 changes: 50 additions & 25 deletions scene/3d/node_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ void Node3D::_notify_dirty() {
void Node3D::_update_local_transform() const {
// This function is called when the local transform (data.local_transform) is dirty and the right value is contained in the Euler rotation and scale.
data.local_transform.basis.set_euler_scale(data.euler_rotation, data.scale, data.euler_rotation_order);
data.dirty.bit_and(~DIRTY_LOCAL_TRANSFORM);
_clear_dirty_bits(DIRTY_LOCAL_TRANSFORM);
}

void Node3D::_update_rotation_and_scale() const {
// This function is called when the Euler rotation (data.euler_rotation) is dirty and the right value is contained in the local transform

data.scale = data.local_transform.basis.get_scale();
data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
}

void Node3D::_propagate_transform_changed_deferred() {
Expand Down Expand Up @@ -127,7 +127,7 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
MessageQueue::get_singleton()->push_callable(callable_mp(this, &Node3D::_propagate_transform_changed_deferred));
}
}
data.dirty.bit_or(DIRTY_GLOBAL_TRANSFORM);
_set_dirty_bits(DIRTY_GLOBAL_TRANSFORM);
}

void Node3D::_notification(int p_what) {
Expand All @@ -151,12 +151,12 @@ void Node3D::_notification(int p_what) {
if (data.top_level && !Engine::get_singleton()->is_editor_hint()) {
if (data.parent) {
data.local_transform = data.parent->get_global_transform() * get_transform();
data.dirty.set(DIRTY_EULER_ROTATION_AND_SCALE); // As local transform was updated, rot/scale should be dirty.
_replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // As local transform was updated, rot/scale should be dirty.
}
data.top_level_active = true;
}

data.dirty.bit_or(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene.
_set_dirty_bits(DIRTY_GLOBAL_TRANSFORM); // Global is always dirty upon entering a scene.
_notify_dirty();

notification(NOTIFICATION_ENTER_WORLD);
Expand Down Expand Up @@ -230,16 +230,16 @@ void Node3D::set_basis(const Basis &p_basis) {
void Node3D::set_quaternion(const Quaternion &p_quaternion) {
ERR_THREAD_GUARD;

if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// We need the scale part, so if these are dirty, update it
data.scale = data.local_transform.basis.get_scale();
data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
}
data.local_transform.basis = Basis(p_quaternion, data.scale);
// Rotscale should not be marked dirty because that would cause precision loss issues with the scale. Instead reconstruct rotation now.
data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);

data.dirty.set(DIRTY_NONE);
_replace_dirty_mask(DIRTY_NONE);

_propagate_transform_changed(this);
if (data.notify_local_transform) {
Expand Down Expand Up @@ -286,7 +286,7 @@ void Node3D::set_global_rotation_degrees(const Vector3 &p_euler_degrees) {
void Node3D::set_transform(const Transform3D &p_transform) {
ERR_THREAD_GUARD;
data.local_transform = p_transform;
data.dirty.set(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty.
_replace_dirty_mask(DIRTY_EULER_ROTATION_AND_SCALE); // Make rot/scale dirty.

_propagate_transform_changed(this);
if (data.notify_local_transform) {
Expand Down Expand Up @@ -314,7 +314,7 @@ void Node3D::set_global_transform(const Transform3D &p_transform) {

Transform3D Node3D::get_transform() const {
ERR_READ_THREAD_GUARD_V(Transform3D());
if (data.dirty.get() & DIRTY_LOCAL_TRANSFORM) {
if (_test_dirty_bits(DIRTY_LOCAL_TRANSFORM)) {
// This update can happen if needed over multiple threads.
_update_local_transform();
}
Expand All @@ -330,7 +330,7 @@ Transform3D Node3D::get_global_transform() const {
* the dirty/update process is thread safe by utilizing atomic copies.
*/

uint32_t dirty = data.dirty.get();
uint32_t dirty = _read_dirty_mask();
if (dirty & DIRTY_GLOBAL_TRANSFORM) {
if (dirty & DIRTY_LOCAL_TRANSFORM) {
_update_local_transform(); // Update local transform atomically.
Expand All @@ -348,7 +348,7 @@ Transform3D Node3D::get_global_transform() const {
}

data.global_transform = new_global;
data.dirty.bit_and(~DIRTY_GLOBAL_TRANSFORM);
_clear_dirty_bits(DIRTY_GLOBAL_TRANSFORM);
}

return data.global_transform;
Expand Down Expand Up @@ -404,14 +404,14 @@ void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) {
}

bool transform_changed = false;
if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !(data.dirty.get() & DIRTY_LOCAL_TRANSFORM)) {
if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && !_test_dirty_bits(DIRTY_LOCAL_TRANSFORM)) {
data.local_transform.orthogonalize();
transform_changed = true;
}

data.rotation_edit_mode = p_mode;

if (p_mode == ROTATION_EDIT_MODE_EULER && (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE)) {
if (p_mode == ROTATION_EDIT_MODE_EULER && _test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// If going to Euler mode, ensure that vectors are _not_ dirty, else the retrieved value may be wrong.
// Otherwise keep what is there, so switching back and forth between modes does not break the vectors.

Expand Down Expand Up @@ -442,13 +442,14 @@ void Node3D::set_rotation_order(EulerOrder p_order) {
ERR_FAIL_INDEX(int32_t(p_order), 6);
bool transform_changed = false;

if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
uint32_t dirty = _read_dirty_mask();
if ((dirty & DIRTY_EULER_ROTATION_AND_SCALE)) {
_update_rotation_and_scale();
} else if (data.dirty.get() & DIRTY_LOCAL_TRANSFORM) {
} else if ((dirty & DIRTY_LOCAL_TRANSFORM)) {
data.euler_rotation = Basis::from_euler(data.euler_rotation, data.euler_rotation_order).get_euler_normalized(p_order);
transform_changed = true;
} else {
data.dirty.bit_or(DIRTY_LOCAL_TRANSFORM);
_set_dirty_bits(DIRTY_LOCAL_TRANSFORM);
transform_changed = true;
}

Expand All @@ -470,14 +471,14 @@ EulerOrder Node3D::get_rotation_order() const {

void Node3D::set_rotation(const Vector3 &p_euler_rad) {
ERR_THREAD_GUARD;
if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// Update scale only if rotation and scale are dirty, as rotation will be overridden.
data.scale = data.local_transform.basis.get_scale();
data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
}

data.euler_rotation = p_euler_rad;
data.dirty.set(DIRTY_LOCAL_TRANSFORM);
_replace_dirty_mask(DIRTY_LOCAL_TRANSFORM);
_propagate_transform_changed(this);
if (data.notify_local_transform) {
notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
Expand All @@ -492,14 +493,14 @@ void Node3D::set_rotation_degrees(const Vector3 &p_euler_degrees) {

void Node3D::set_scale(const Vector3 &p_scale) {
ERR_THREAD_GUARD;
if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
// Update rotation only if rotation and scale are dirty, as scale will be overridden.
data.euler_rotation = data.local_transform.basis.get_euler_normalized(data.euler_rotation_order);
data.dirty.bit_and(~DIRTY_EULER_ROTATION_AND_SCALE);
_clear_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE);
}

data.scale = p_scale;
data.dirty.set(DIRTY_LOCAL_TRANSFORM);
_replace_dirty_mask(DIRTY_LOCAL_TRANSFORM);
_propagate_transform_changed(this);
if (data.notify_local_transform) {
notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
Expand All @@ -513,7 +514,7 @@ Vector3 Node3D::get_position() const {

Vector3 Node3D::get_rotation() const {
ERR_READ_THREAD_GUARD_V(Vector3());
if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
_update_rotation_and_scale();
}

Expand All @@ -528,7 +529,7 @@ Vector3 Node3D::get_rotation_degrees() const {

Vector3 Node3D::get_scale() const {
ERR_READ_THREAD_GUARD_V(Vector3());
if (data.dirty.get() & DIRTY_EULER_ROTATION_AND_SCALE) {
if (_test_dirty_bits(DIRTY_EULER_ROTATION_AND_SCALE)) {
_update_rotation_and_scale();
}

Expand Down Expand Up @@ -645,6 +646,30 @@ Vector<Ref<Node3DGizmo>> Node3D::get_gizmos() const {
#endif
}

void Node3D::_replace_dirty_mask(uint32_t p_mask) const {
if (is_group_processing()) {
data.dirty.mt.set(p_mask);
} else {
data.dirty.st = p_mask;
}
}

void Node3D::_set_dirty_bits(uint32_t p_bits) const {
if (is_group_processing()) {
data.dirty.mt.bit_or(p_bits);
} else {
data.dirty.st |= p_bits;
}
}

void Node3D::_clear_dirty_bits(uint32_t p_bits) const {
if (is_group_processing()) {
data.dirty.mt.bit_and(~p_bits);
} else {
data.dirty.st &= ~p_bits;
}
}

void Node3D::_update_gizmos() {
#ifdef TOOLS_ENABLED
if (data.gizmos_disabled || !is_inside_world() || !data.gizmos_dirty) {
Expand Down
Loading

0 comments on commit 50cf3d6

Please sign in to comment.