Skip to content

Commit

Permalink
Don't recalculate final render transforms for Y-sorted canvas items
Browse files Browse the repository at this point in the history
  • Loading branch information
kleonc committed Jun 17, 2024
1 parent bdc2fca commit c1133a1
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 76 deletions.
153 changes: 86 additions & 67 deletions servers/rendering/renderer_canvas_cull.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));

for (int i = 0; i < p_child_item_count; i++) {
_cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, p_canvas_cull_mask, p_child_items[i].mirror, 1);
_cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, false, p_canvas_cull_mask, p_child_items[i].mirror, 1);
}

RendererCanvasRender::Item *list = nullptr;
Expand Down Expand Up @@ -79,61 +79,71 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
}
}

void RendererCanvasCull::_collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, const Transform2D &p_transform, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
void RendererCanvasCull::_collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
int child_item_count = p_canvas_item->child_items.size();
RendererCanvasCull::Item **child_items = p_canvas_item->child_items.ptrw();
for (int i = 0; i < child_item_count; i++) {
int abs_z = 0;
if (child_items[i]->visible) {
// To y-sort according to the item's final position, physics interpolation
// and transform snapping need to be applied before y-sorting.
Transform2D child_xform;
if (r_items || child_items[i]->sort_y) {
// For Y-sorting to work according to the items' final transforms,
// `child_xform` needs to match `self_xform` used in `_cull_canvas_item()`.

if (!_interpolation_data.interpolation_enabled || !child_items[i]->interpolated) {
child_xform = child_items[i]->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(child_items[i]->xform_prev, child_items[i]->xform_curr, child_xform, f);
}
if (!_interpolation_data.interpolation_enabled || !child_items[i]->interpolated) {
child_xform = child_items[i]->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(child_items[i]->xform_prev, child_items[i]->xform_curr, child_xform, f);
}

if (snapping_2d_transforms_to_pixel) {
child_xform.columns[2] = child_xform.columns[2].round();
}
if (snapping_2d_transforms_to_pixel) {
child_xform.columns[2] = child_xform.columns[2].round();
}

if (r_items) {
r_items[r_index] = child_items[i];
child_items[i]->ysort_xform = p_transform;
child_items[i]->ysort_pos = p_transform.xform(child_xform.columns[2]);
child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
child_items[i]->ysort_modulate = p_modulate;
child_items[i]->ysort_index = r_index;
child_items[i]->ysort_parent_abs_z_index = p_z;

if (!child_items[i]->repeat_source) {
child_items[i]->repeat_size = p_canvas_item->repeat_size;
child_items[i]->repeat_times = p_canvas_item->repeat_times;
}
r_items[r_index] = child_items[i];
child_items[i]->ysort_xform = p_canvas_item->ysort_xform * child_xform;
child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
child_items[i]->ysort_modulate = p_modulate;
child_items[i]->ysort_index = r_index;
child_items[i]->ysort_parent_abs_z_index = p_z;
child_items[i]->ysort_self_xform_scale = child_xform.get_scale();

if (!child_items[i]->repeat_source) {
child_items[i]->repeat_size = p_canvas_item->repeat_size;
child_items[i]->repeat_times = p_canvas_item->repeat_times;
}

// Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
if (child_items[i]->z_relative) {
abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
} else {
abs_z = child_items[i]->z_index;
}
// Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
int abs_z = 0;
if (child_items[i]->z_relative) {
abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
} else {
abs_z = child_items[i]->z_index;
}

r_index++;

if (child_items[i]->sort_y) {
_collect_ysort_children(child_items[i], p_transform * child_xform, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
_collect_ysort_children(child_items[i], child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
}
}
}
}

void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner, RID_Owner<RendererCanvasCull::Item, true> &canvas_item_owner) {
int RendererCanvasCull::_count_ysort_children(RendererCanvasCull::Item *p_canvas_item) {
int ysort_children_count = 0;
int child_item_count = p_canvas_item->child_items.size();
RendererCanvasCull::Item *const *child_items = p_canvas_item->child_items.ptr();
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->visible) {
ysort_children_count++;
if (child_items[i]->sort_y) {
ysort_children_count += _count_ysort_children(child_items[i]);
}
}
}
return ysort_children_count;
}

void RendererCanvasCull::_mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner) {
do {
ysort_owner->ysort_children_count = -1;
ysort_owner = canvas_item_owner.owns(ysort_owner->parent) ? canvas_item_owner.get_or_null(ysort_owner->parent) : nullptr;
Expand Down Expand Up @@ -249,7 +259,7 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
}
}

void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times) {
void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times) {
Item *ci = p_canvas_item;

if (!ci->visible) {
Expand All @@ -274,14 +284,28 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}

Transform2D self_xform;
if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
self_xform = ci->xform_curr;
Transform2D final_xform;
if (p_is_already_y_sorted) {
// Y-sorted item's final transform is calculated before y-sorting,
// and is passed as `p_parent_xform` afterwards. No need to recalculate.
final_xform = p_parent_xform;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, self_xform, f);
}
if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
self_xform = ci->xform_curr;
} else {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, self_xform, f);
}

Transform2D parent_xform = p_parent_xform;
Transform2D parent_xform = p_parent_xform;

if (snapping_2d_transforms_to_pixel) {
self_xform.columns[2] = self_xform.columns[2].round();
parent_xform.columns[2] = parent_xform.columns[2].round();
}

final_xform = parent_xform * self_xform;
}

Point2 repeat_size = p_repeat_size;
int repeat_times = p_repeat_times;
Expand All @@ -295,18 +319,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}

if (repeat_size.x || repeat_size.y) {
Size2 scale = final_xform.get_scale();
// Already y-sorted item's final transform is calculated and available but its local transform
// is not preserved (as not needed). The local scale is cached for this calculation though.
Size2 scale = p_is_already_y_sorted ? ci->ysort_self_xform_scale : self_xform.get_scale();

rect.size += repeat_size * repeat_times / scale;
rect.position -= repeat_size / scale * (repeat_times / 2);
}

if (snapping_2d_transforms_to_pixel) {
self_xform.columns[2] = self_xform.columns[2].round();
parent_xform.columns[2] = parent_xform.columns[2].round();
}

Transform2D final_xform = parent_xform * self_xform;

Rect2 global_rect = final_xform.xform(rect);
global_rect.position += p_clip_rect.position;

Expand Down Expand Up @@ -352,29 +372,28 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}

if (ci->sort_y) {
if (p_allow_y_sort) {
if (!p_is_already_y_sorted) {
if (ci->ysort_children_count == -1) {
ci->ysort_children_count = 0;
_collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), nullptr, ci->ysort_children_count, p_z);
ci->ysort_children_count = _count_ysort_children(ci);
}

child_item_count = ci->ysort_children_count + 1;
child_items = (Item **)alloca(child_item_count * sizeof(Item *));

ci->ysort_xform = self_xform.affine_inverse();
ci->ysort_pos = Vector2();
ci->ysort_xform = Transform2D();
ci->ysort_modulate = Color(1, 1, 1, 1);
ci->ysort_index = 0;
ci->ysort_parent_abs_z_index = parent_z;
ci->ysort_self_xform_scale = self_xform.get_scale();
child_items[0] = ci;
int i = 1;
_collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);
_collect_ysort_children(ci, p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);

SortArray<Item *, ItemPtrSort> sorter;
SortArray<Item *, ItemYSort> sorter;
sorter.sort(child_items, child_item_count);

for (i = 0; i < child_item_count; i++) {
_cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times);
_cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, true, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
Expand All @@ -398,14 +417,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
_cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
_cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
}
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
_cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
_cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times);
}
}
}
Expand Down Expand Up @@ -498,7 +517,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
item_owner->child_items.erase(canvas_item);

if (item_owner->sort_y) {
_mark_ysort_dirty(item_owner, canvas_item_owner);
_mark_ysort_dirty(item_owner);
}
}

Expand All @@ -518,7 +537,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
item_owner->children_order_dirty = true;

if (item_owner->sort_y) {
_mark_ysort_dirty(item_owner, canvas_item_owner);
_mark_ysort_dirty(item_owner);
}

} else {
Expand All @@ -535,7 +554,7 @@ void RendererCanvasCull::canvas_item_set_visible(RID p_item, bool p_visible) {

canvas_item->visible = p_visible;

_mark_ysort_dirty(canvas_item, canvas_item_owner);
_mark_ysort_dirty(canvas_item);
}

void RendererCanvasCull::canvas_item_set_light_mask(RID p_item, int p_mask) {
Expand Down Expand Up @@ -1731,7 +1750,7 @@ void RendererCanvasCull::canvas_item_set_sort_children_by_y(RID p_item, bool p_e

canvas_item->sort_y = p_enable;

_mark_ysort_dirty(canvas_item, canvas_item_owner);
_mark_ysort_dirty(canvas_item);
}

void RendererCanvasCull::canvas_item_set_z_index(RID p_item, int p_z) {
Expand Down Expand Up @@ -2412,7 +2431,7 @@ bool RendererCanvasCull::free(RID p_rid) {
item_owner->child_items.erase(canvas_item);

if (item_owner->sort_y) {
_mark_ysort_dirty(item_owner, canvas_item_owner);
_mark_ysort_dirty(item_owner);
}
}
}
Expand Down
21 changes: 12 additions & 9 deletions servers/rendering/renderer_canvas_cull.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class RendererCanvasCull {
bool children_order_dirty;
int ysort_children_count;
Color ysort_modulate;
Transform2D ysort_xform;
Vector2 ysort_pos;
Transform2D ysort_xform; // Relative to y-sorted subtree's root item (identity for such root). Its `origin.y` is used for sorting.
int ysort_index;
int ysort_parent_abs_z_index; // Absolute Z index of parent. Only populated and used when y-sorting.
Vector2 ysort_self_xform_scale; // Needed for parallax repeating. Cached to avoid recalculating local transform after y-sorting.
uint32_t visibility_layer = 0xffffffff;

Vector<Item *> child_items;
Expand Down Expand Up @@ -84,9 +84,9 @@ class RendererCanvasCull {
index = 0;
ysort_children_count = -1;
ysort_xform = Transform2D();
ysort_pos = Vector2();
ysort_index = 0;
ysort_parent_abs_z_index = 0;
ysort_self_xform_scale = Vector2(1, 1);
}
};

Expand All @@ -96,13 +96,14 @@ class RendererCanvasCull {
}
};

struct ItemPtrSort {
struct ItemYSort {
_FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const {
if (Math::is_equal_approx(p_left->ysort_pos.y, p_right->ysort_pos.y)) {
const real_t left_y = p_left->ysort_xform.columns[2].y;
const real_t right_y = p_right->ysort_xform.columns[2].y;
if (Math::is_equal_approx(left_y, right_y)) {
return p_left->ysort_index < p_right->ysort_index;
}

return p_left->ysort_pos.y < p_right->ysort_pos.y;
return left_y < right_y;
}
};

Expand Down Expand Up @@ -188,9 +189,11 @@ class RendererCanvasCull {

private:
void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr);
void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times);
void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times);

void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, Transform2D p_transform, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z);
void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z);
int _count_ysort_children(RendererCanvasCull::Item *p_canvas_item);
void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner);

static constexpr int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1;

Expand Down

0 comments on commit c1133a1

Please sign in to comment.