From 892381663ba729eba95c0849a7338a61bc15d24b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?=
<7645683+bruvzg@users.noreply.github.com>
Date: Wed, 25 Feb 2026 11:53:07 +0200
Subject: [PATCH] Presort text draw calls.
---
doc/classes/RenderingServer.xml | 15 +
drivers/gles3/rasterizer_canvas_gles3.cpp | 4 +-
editor/script/script_text_editor.cpp | 4 +
scene/gui/code_edit.cpp | 3 +
scene/gui/label.cpp | 18 +-
scene/gui/line_edit.cpp | 4 +
scene/gui/line_edit.h | 5 +
scene/gui/rich_text_label.cpp | 36 +--
scene/gui/rich_text_label.h | 5 +-
scene/gui/text_edit.cpp | 14 +-
scene/gui/text_edit.h | 10 +
servers/rendering/renderer_canvas_cull.cpp | 276 +++++++++++++++++-
servers/rendering/renderer_canvas_cull.h | 102 +++++++
servers/rendering/renderer_canvas_render.cpp | 4 +-
servers/rendering/renderer_canvas_render.h | 30 +-
.../renderer_rd/renderer_canvas_render_rd.cpp | 4 +-
servers/rendering/rendering_server.cpp | 3 +
servers/rendering/rendering_server.h | 3 +
servers/rendering/rendering_server_default.h | 3 +
19 files changed, 498 insertions(+), 45 deletions(-)
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 5f8cb39c72..d320f5227f 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -457,6 +457,13 @@
[b]Note:[/b] The equivalent node is [CanvasItem].
+
+
+
+
+ Ends draw call pre-sorting. See [method canvas_item_set_presort_level].
+
+
@@ -618,6 +625,14 @@
Sets a parent [CanvasItem] to the [CanvasItem]. The item will inherit transform, modulation and visibility from its parent, like [CanvasItem] nodes in the scene tree.
+
+
+
+
+
+ Starts draw call pre-sorting or changes the pre-sorting level. All subsequent draw calls until [method canvas_item_flush_presort] is called are sorted according to the [param order] (in ascending order) and texture usage, to ensure draw call batching. The original call order is not preserved, except [method canvas_item_add_set_transform], [method canvas_item_add_clip_ignore], and [method canvas_item_add_animation_slice] effects. Draw calls made before the first [method canvas_item_set_presort_level] call or after [method canvas_item_flush_presort] is called are also not affected. [param order] should be greater or equal to zero.
+
+
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 3b570522c9..cd018933b0 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -446,7 +446,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
if (ci->skeleton.is_valid()) {
- const Item::Command *c = ci->commands;
+ const Item::Command *c = ci->first_command;
while (c) {
if (c->type == Item::Command::TYPE_MESH) {
@@ -876,7 +876,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
}
const RSE::CanvasItemTextureRepeat base_texture_repeat = p_item->texture_repeat == RSE::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT ? state.default_repeat : p_item->texture_repeat;
- const Item::Command *c = p_item->commands;
+ const Item::Command *c = p_item->first_command;
while (c) {
if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) {
c = c->next;
diff --git a/editor/script/script_text_editor.cpp b/editor/script/script_text_editor.cpp
index a609616bd3..42656f6362 100644
--- a/editor/script/script_text_editor.cpp
+++ b/editor/script/script_text_editor.cpp
@@ -581,14 +581,18 @@ Array ScriptTextEditor::_inline_object_parse(const String &p_text) {
}
void ScriptTextEditor::_inline_object_draw(const Dictionary &p_info, const Rect2 &p_rect) {
+ // Note: this method uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order. See TextEdit::DrawStep.
if (_is_valid_color_info(p_info)) {
Rect2 col_rect = p_rect.grow(-4);
if (color_alpha_texture.is_null()) {
color_alpha_texture = inline_color_picker->get_theme_icon("sample_bg", "ColorPicker");
}
RID text_ci = code_editor->get_text_editor()->get_text_canvas_item();
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, TextEdit::DrawStep::DRAW_STEP_TEXT_OUTLINE);
RS::get_singleton()->canvas_item_add_rect(text_ci, p_rect.grow(-3), Color(1, 1, 1));
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, TextEdit::DrawStep::DRAW_STEP_TEXT);
color_alpha_texture->draw_rect(text_ci, col_rect);
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, TextEdit::DrawStep::DRAW_STEP_TEXT_OVERLAY);
RS::get_singleton()->canvas_item_add_rect(text_ci, col_rect, Color(p_info["color"]));
}
}
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 776afd599f..0808d13288 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -1525,6 +1525,7 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const {
}
void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
+ // Note: this method uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order. See TextEdit::DrawStep.
bool hovering = get_hovered_gutter() == Vector2i(main_gutter, p_line);
RID ci = get_text_canvas_item();
if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) {
@@ -1715,6 +1716,7 @@ int CodeEdit::get_line_numbers_min_digits() const {
}
void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
+ // Note: this method uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order. See TextEdit::DrawStep.
if (!Rect2(Vector2(0, 0), get_size()).intersects(p_region)) {
return;
}
@@ -1777,6 +1779,7 @@ bool CodeEdit::is_drawing_fold_gutter() const {
}
void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region) {
+ // Note: this method uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order. See TextEdit::DrawStep.
if (!can_fold_line(p_line) && !is_line_folded(p_line)) {
set_line_gutter_clickable(p_line, fold_gutter, false);
return;
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 87138e5dd7..c9c903c75c 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -804,6 +804,7 @@ void Label::_notification(int p_what) {
int visible_glyphs = total_glyphs * visible_ratio;
int line_index = 0;
+ // Note: part of this method between this comment and `canvas_item_flush_presort` call uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order.
for (int p = 0; p < paragraphs.size(); p++) {
const Paragraph ¶ = paragraphs[p];
if (line_index + para.lines_rid.size() <= lines_skipped) {
@@ -843,12 +844,17 @@ void Label::_notification(int p_what) {
int processed_glyphs_step = 0;
// Draw shadow outline.
+ int draw_step = 0;
if (font_shadow_color.a != 0 && shadow_outline_size > 0) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, font_shadow_color, draw_glyph_shadow_outline, shadow_outline_size, shadow_ofs);
}
// Draw shadow.
if (font_shadow_color.a > 0) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, font_shadow_color, draw_glyph_shadow, shadow_ofs);
}
@@ -859,9 +865,12 @@ void Label::_notification(int p_what) {
for (int draw_iteration_index = draw_iterations - 1; draw_iteration_index >= 0; --draw_iteration_index) {
LabelSettings::StackedShadowData stacked_shadow_data = stacked_shadow_datas[draw_iteration_index];
if (stacked_shadow_data.outline_size > 0) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, stacked_shadow_data.color, draw_glyph_shadow_outline, stacked_shadow_data.outline_size, stacked_shadow_data.offset);
}
-
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, stacked_shadow_data.color, draw_glyph_shadow, stacked_shadow_data.offset);
}
}
@@ -885,6 +894,8 @@ void Label::_notification(int p_what) {
if (stacked_outline_data.size <= 0) {
continue;
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, stacked_outline_data.color, draw_glyph_outline, stacked_outline_draw_size);
stacked_outline_draw_size -= stacked_outline_data.size;
}
@@ -892,11 +903,15 @@ void Label::_notification(int p_what) {
// Draw outline.
if (outline_size > 0 && font_outline_color.a != 0) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, font_outline_color, draw_glyph_outline, outline_size);
}
// Draw text.
{
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, draw_step);
+ draw_step++;
draw_text(rtl, ellipsis_pos, ellipsis_gl_size, ellipsis_glyphs, trim_chars, para.start, visible_chars, trim_glyphs_ltr, processed_glyphs_step, processed_glyphs, visible_glyphs, trim_glyphs_rtl, total_glyphs, ci, ofs, gl_size, trim_pos, glyphs, font_color, draw_glyph);
}
@@ -907,6 +922,7 @@ void Label::_notification(int p_what) {
line_index += para.lines_rid.size();
}
}
+ RenderingServer::get_singleton()->canvas_item_flush_presort(ci);
} break;
case NOTIFICATION_THEME_CHANGED: {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index b6209ab35f..1d436b9b78 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -1519,11 +1519,13 @@ void LineEdit::_notification(int p_what) {
int gl_size = TS->shaped_text_get_glyph_count(text_rid);
// Draw text.
+ // Note: part of this method between this comment and `canvas_item_flush_presort` call uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order.
ofs.y += TS->shaped_text_get_ascent(text_rid);
Color font_outline_color = theme_cache.font_outline_color;
int outline_size = theme_cache.font_outline_size;
if (outline_size > 0 && font_outline_color.a > 0) {
Vector2 oofs = ofs;
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, DRAW_STEP_OUTLINE);
for (int i = 0; i < gl_size; i++) {
for (int j = 0; j < glyphs[i].repeat; j++) {
if (std::ceil(oofs.x) >= x_ofs && (oofs.x + glyphs[i].advance) <= ofs_max) {
@@ -1538,6 +1540,7 @@ void LineEdit::_notification(int p_what) {
}
}
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, DRAW_STEP_TEXT);
for (int i = 0; i < gl_size; i++) {
bool selected = selection.enabled && glyphs[i].start >= selection.begin && glyphs[i].end <= selection.end;
for (int j = 0; j < glyphs[i].repeat; j++) {
@@ -1554,6 +1557,7 @@ void LineEdit::_notification(int p_what) {
break;
}
}
+ RenderingServer::get_singleton()->canvas_item_flush_presort(ci);
// Draw carets.
ofs.x = x_ofs + scroll_offset;
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index f1e8477eed..303bde6162 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -91,6 +91,11 @@ public:
};
private:
+ enum LEDrawStep {
+ DRAW_STEP_OUTLINE,
+ DRAW_STEP_TEXT,
+ };
+
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
bool editing = false;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 3eae28b750..97733bcc98 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1053,7 +1053,8 @@ void RichTextLabel::_update_table_size(ItemTable *p_table) {
}
}
-int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) {
+int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs, int p_draw_step) {
+ // Note: this method uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order.
ERR_FAIL_NULL_V(p_frame, 0);
ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0);
@@ -1084,8 +1085,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
bool skip_dc = (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs));
if (!skip_dc) {
if (l.dc_ol_size > 0) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_OUTLINE);
l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color);
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_TEXT);
l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color);
}
@@ -1153,20 +1156,24 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Color font_outline_color = l.prefix_outline_color == Color(0, 0, 0, 0) ? _find_outline_color(l.from, p_base_color) : l.prefix_outline_color;
Color font_shadow_color = p_font_shadow_color * Color(1, 1, 1, font_color.a);
if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_OUTLINE);
if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) {
l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x + length, 0) + p_shadow_ofs, p_shadow_outline_size, font_shadow_color);
}
if (outline_size > 0 && font_outline_color.a != 0.0) {
l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x + length, 0), outline_size, font_outline_color);
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_TEXT);
l.text_prefix->draw(ci, p_ofs + Vector2(off.x + length, 0), font_color);
} else {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_OUTLINE);
if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) {
l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x - l.text_prefix->get_size().x, 0) + p_shadow_ofs, p_shadow_outline_size, font_shadow_color);
}
if (outline_size > 0 && font_outline_color.a != 0.0) {
l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x - l.text_prefix->get_size().x, 0), outline_size, font_outline_color);
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_TEXT);
l.text_prefix->draw(ci, p_ofs + Vector2(off.x - l.text_prefix->get_size().x, 0), font_color);
}
}
@@ -1200,7 +1207,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
int processed_glyphs_step = 0;
- for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) {
+ for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_TEXT_MAX; step++) {
if (step == DRAW_STEP_TEXT) {
// Draw inlined objects.
Array objects = TS->shaped_text_get_objects(rid);
@@ -1221,6 +1228,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
switch (it->type) {
case ITEM_IMAGE: {
ItemImage *img = static_cast(it);
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_TEXT);
if (img->pad) {
Size2 pad_size = rect.size.min(img->image->get_size());
Vector2 pad_off = (rect.size - pad_size) / 2;
@@ -1249,7 +1257,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
table_ofs -= Vector2(h_separation * 0.5, v_separation * 0.5).floor();
}
- bool right_has_border = false;
int idx = 0;
for (Item *E : table->subitems) {
ItemFrame *frame = static_cast(E);
@@ -1276,29 +1283,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
row_bg = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg;
}
if (row_bg.a > 0.0) {
- if (right_has_border) {
- // To prevent the border of the right cell from being covered.
- cell_rect.size.x -= 1;
- draw_rect(cell_rect, row_bg, true);
- cell_rect.size.x += 1;
- } else {
- draw_rect(cell_rect, row_bg, true);
- }
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_CELL_BACKGROUND);
+ draw_rect(cell_rect, row_bg, true);
}
- right_has_border = false;
-
Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border;
if (bc.a > 0.0) {
- if (rtl && col < col_count - 1) {
- right_has_border = true;
- }
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + DRAW_STEP_CELL_BORDER);
draw_rect(cell_rect, bc, false);
}
}
for (int j = 0; j < (int)frame->lines.size(); j++) {
- _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, 0, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs);
+ _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, 0, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs, p_draw_step + DRAW_STEP_MAX);
}
idx++;
}
@@ -1309,6 +1306,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
}
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(ci, p_draw_step + step);
Vector2 off_step = off;
processed_glyphs_step = r_processed_glyphs;
@@ -2802,10 +2800,11 @@ void RichTextLabel::_notification(int p_what) {
// New cache draw.
Point2 ofs = text_rect.get_position() + Vector2(0, vbegin + main->lines[from_line].offset.y - vofs);
int processed_glyphs = 0;
+ // Note: this loop uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order.
while (ofs.y < size.height - v_limit && from_line < to_line) {
MutexLock lock(main->lines[from_line].text_buf->get_mutex());
- int drawn_lines = _draw_line(main, from_line, ofs, text_rect.size.x, vsep, theme_cache.default_color, theme_cache.outline_size, theme_cache.font_outline_color, theme_cache.font_shadow_color, theme_cache.shadow_outline_size, shadow_ofs, processed_glyphs);
+ int drawn_lines = _draw_line(main, from_line, ofs, text_rect.size.x, vsep, theme_cache.default_color, theme_cache.outline_size, theme_cache.font_outline_color, theme_cache.font_shadow_color, theme_cache.shadow_outline_size, shadow_ofs, processed_glyphs, 0);
visible_line_count += drawn_lines;
if (drawn_lines > 0) {
visible_paragraph_count++;
@@ -2813,6 +2812,7 @@ void RichTextLabel::_notification(int p_what) {
ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * (theme_cache.line_separation + vsep) + (theme_cache.paragraph_separation);
from_line++;
}
+ RenderingServer::get_singleton()->canvas_item_flush_presort(ci);
if (scroll_follow_visible_characters && scroll_active) {
scroll_visible = follow_vc_pos > 0;
if (scroll_visible) {
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index ed2e675888..d265729b2b 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -57,6 +57,9 @@ class RichTextLabel : public Control {
DRAW_STEP_OUTLINE,
DRAW_STEP_TEXT,
DRAW_STEP_FOREGROUND,
+ DRAW_STEP_TEXT_MAX,
+ DRAW_STEP_CELL_BACKGROUND = DRAW_STEP_TEXT_MAX,
+ DRAW_STEP_CELL_BORDER,
DRAW_STEP_MAX,
};
@@ -667,7 +670,7 @@ private:
void _update_table_size(ItemTable *p_table);
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref &p_base_font, int p_base_font_size);
- int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
+ int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs, int p_draw_step);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false);
void _accessibility_update_line(RID p_id, ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index a8c11414c0..de5f94fdfc 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1336,6 +1336,7 @@ void TextEdit::_notification(int p_what) {
_draw_guidelines();
// Draw main text.
+ // Note: part of this method between this comment and `canvas_item_flush_presort` call uses pre-sorted draw, draw call order is ignored, use `canvas_item_set_presort_level` to specify draw order.
line_drawing_cache.clear();
int row_height = draw_placeholder ? placeholder_line_height + theme_cache.line_spacing : get_line_height();
int line = first_vis_line;
@@ -1408,6 +1409,7 @@ void TextEdit::_notification(int p_what) {
break;
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_LINE_BACKGROUND);
if (text.get_line_background_color(line).a > 0.0) {
if (rtl) {
RS::get_singleton()->canvas_item_add_rect(text_ci, Rect2(size.width - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
@@ -1417,6 +1419,7 @@ void TextEdit::_notification(int p_what) {
}
// Draw current line highlight.
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_LINE_HIGHLIGHT);
if (highlight_current_line && highlighted_lines.has(Pair(line, line_wrap_index))) {
if (rtl) {
RS::get_singleton()->canvas_item_add_rect(text_ci, Rect2(size.width - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
@@ -1451,8 +1454,10 @@ void TextEdit::_notification(int p_what) {
int yofs = ofs_y + (row_height - tl->get_size().y) / 2;
if (theme_cache.outline_size > 0 && theme_cache.outline_color.a > 0) {
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT_OUTLINE);
tl->draw_outline(text_ci, Point2(gutter_offset, yofs), theme_cache.outline_size, theme_cache.outline_color);
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT);
tl->draw(text_ci, Point2(gutter_offset, yofs), get_line_gutter_item_color(line, g));
} break;
case GUTTER_TYPE_ICON: {
@@ -1484,7 +1489,7 @@ void TextEdit::_notification(int p_what) {
if (rtl) {
gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x;
}
-
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT);
icon->draw_rect(text_ci, gutter_rect, false, get_line_gutter_item_color(line, g));
} break;
case GUTTER_TYPE_CUSTOM: {
@@ -1493,6 +1498,7 @@ void TextEdit::_notification(int p_what) {
if (rtl) {
gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x;
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT);
gutter.custom_draw_callback.call(line, g, Rect2(gutter_rect));
}
} break;
@@ -1526,6 +1532,7 @@ void TextEdit::_notification(int p_what) {
// Draw selections.
float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT_SELECTION);
for (int c = 0; c < get_caret_count(); c++) {
if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) {
int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
@@ -1568,6 +1575,7 @@ void TextEdit::_notification(int p_what) {
}
int start = TS->shaped_text_get_range(rid).x;
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_SEARCH_HIGHLIGHT);
if (!clipped && !search_text.is_empty()) { // Search highlight
int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
int search_text_len = search_text.length();
@@ -1654,6 +1662,7 @@ void TextEdit::_notification(int p_what) {
int last_visible_char = TS->shaped_text_get_range(rid).x;
float char_ofs = 0;
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT_OUTLINE);
if (theme_cache.outline_size > 0 && theme_cache.outline_color.a > 0) {
for (int j = 0; j < gl_size; j++) {
for (int k = 0; k < glyphs[j].repeat; k++) {
@@ -1680,6 +1689,7 @@ void TextEdit::_notification(int p_what) {
}
}
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT);
for (int j = 0; j < gl_size; j++) {
for (const Pair &color_data : color_map) {
if (color_data.first <= glyphs[j].start) {
@@ -1770,6 +1780,7 @@ void TextEdit::_notification(int p_what) {
cache_entry.first_visible_chars.push_back(first_visible_char);
cache_entry.last_visible_chars.push_back(last_visible_char);
+ RenderingServer::get_singleton()->canvas_item_set_presort_level(text_ci, DRAW_STEP_TEXT_OVERLAY);
// is_line_folded
if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && _is_line_hidden(line + 1)) {
int xofs = char_ofs + char_margin + (_get_folded_eol_icon()->get_width() / 2);
@@ -1954,6 +1965,7 @@ void TextEdit::_notification(int p_what) {
line_drawing_cache[line] = cache_entry;
}
}
+ RenderingServer::get_singleton()->canvas_item_flush_presort(text_ci);
if (has_focus()) {
_update_ime_window_position();
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 6f3cd91799..59e23f2fa0 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -121,6 +121,16 @@ public:
SEARCH_BACKWARDS = 4
};
+ enum DrawStep {
+ DRAW_STEP_LINE_BACKGROUND,
+ DRAW_STEP_LINE_HIGHLIGHT,
+ DRAW_STEP_TEXT_SELECTION,
+ DRAW_STEP_SEARCH_HIGHLIGHT,
+ DRAW_STEP_TEXT_OUTLINE,
+ DRAW_STEP_TEXT,
+ DRAW_STEP_TEXT_OVERLAY,
+ };
+
private:
struct GutterInfo {
GutterType type = GutterType::GUTTER_TYPE_STRING;
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 9a0c442d8f..b4ebb89a9b 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -222,7 +222,7 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
// We have two choices now, if user has drawn something, we must assume users wants to draw the "mask", so compute the size based on this.
// If nothing has been drawn, we just take it over and draw it ourselves.
- if (ci->canvas_group->fit_empty && (ci->commands == nullptr || (ci->commands->next == nullptr && ci->commands->type == RendererCanvasCull::Item::Command::TYPE_RECT && (static_cast(ci->commands)->flags & RendererCanvasRender::CANVAS_RECT_IS_GROUP)))) {
+ if (ci->canvas_group->fit_empty && (ci->first_command == nullptr || (ci->first_command->next == nullptr && ci->first_command->type == RendererCanvasCull::Item::Command::TYPE_RECT && (static_cast(ci->first_command)->flags & RendererCanvasRender::CANVAS_RECT_IS_GROUP)))) {
// No commands, or sole command is the one used to draw, so we (re)create the draw command.
ci->clear();
@@ -257,14 +257,14 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
}
}
- if (((ci->commands != nullptr || ci->visibility_notifier) && p_clip_rect.intersects(p_global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
+ if (((ci->first_command != nullptr || ci->visibility_notifier) && p_clip_rect.intersects(p_global_rect, true)) || ci->vp_render || ci->copy_back_buffer) {
// Something to draw?
if (ci->update_when_visible) {
RenderingServerDefault::redraw_request();
}
- if (ci->commands != nullptr || ci->copy_back_buffer) {
+ if (ci->first_command != nullptr || ci->copy_back_buffer) {
ci->final_transform = !ci->use_identity_transform ? p_transform : _current_camera_transform;
ci->final_modulate = p_modulate * ci->self_modulate;
ci->global_rect_cache = p_global_rect;
@@ -330,6 +330,12 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
return;
}
+#ifdef DEBUG_ENABLED
+ if (!ci->presort_commands.is_empty()) {
+ WARN_PRINT(vformat("Presort list for canvas item %x was not flushed!", ci->self.get_id()));
+ }
+#endif
+
Rect2 rect = ci->get_rect();
if (ci->visibility_notifier) {
@@ -460,7 +466,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
- bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->commands != nullptr);
+ bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->first_command != nullptr);
if (use_canvas_group) {
int zidx = p_z - RSE::CANVAS_ITEM_Z_MIN;
canvas_group_from = r_z_last_list[zidx];
@@ -470,7 +476,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
- bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->commands != nullptr);
+ bool use_canvas_group = ci->canvas_group != nullptr && (ci->canvas_group->fit_empty || ci->first_command != nullptr);
if (use_canvas_group) {
int zidx = p_z - RSE::CANVAS_ITEM_Z_MIN;
canvas_group_from = r_z_last_list[zidx];
@@ -744,12 +750,89 @@ float RendererCanvasCull::canvas_item_get_compensated_antialiasing_width(float p
return p_width;
}
+void RendererCanvasCull::canvas_item_set_presort_level(RID p_item, int64_t p_order) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+ ERR_FAIL_COND(p_order < 0);
+
+ if (canvas_item->presort_level == -1) {
+ canvas_item->presort_prev_command = canvas_item->last_command;
+ if (!canvas_item->presort_prev_state.is_base_state()) {
+ canvas_item->presort_states.push_back(canvas_item->presort_prev_state);
+ canvas_item->presort_state = 0;
+ }
+ }
+ canvas_item->presort_level = p_order;
+}
+
+void RendererCanvasCull::_update_presort_state(Item *p_canvas_item, int32_t p_old_state, int32_t p_new_state, Item::Command **r_prev_command) {
+ const Item::PresortState &old_state = (p_old_state >= 0) ? p_canvas_item->presort_states[p_old_state] : Item::PresortState();
+ const Item::PresortState &new_state = (p_new_state >= 0) ? p_canvas_item->presort_states[p_new_state] : Item::PresortState();
+ if (old_state.transform != new_state.transform) {
+ Item::CommandTransform *tr = p_canvas_item->alloc_command(false);
+ if (tr) {
+ tr->xform = new_state.transform;
+ _insert_command(p_canvas_item, r_prev_command, tr);
+ }
+ }
+ if (old_state.clip_ignore != new_state.clip_ignore) {
+ Item::CommandClipIgnore *ci = p_canvas_item->alloc_command(false);
+ if (ci) {
+ ci->ignore = new_state.clip_ignore;
+ _insert_command(p_canvas_item, r_prev_command, ci);
+ }
+ }
+ if (old_state.animation_length != new_state.animation_length || old_state.slice_begin != new_state.slice_begin || old_state.slice_end != new_state.slice_end || old_state.offset != new_state.offset) {
+ Item::CommandAnimationSlice *as = p_canvas_item->alloc_command(false);
+ if (as) {
+ as->animation_length = new_state.animation_length;
+ as->slice_begin = new_state.slice_begin;
+ as->slice_end = new_state.slice_end;
+ as->offset = new_state.offset;
+ _insert_command(p_canvas_item, r_prev_command, as);
+ }
+ }
+}
+
+void RendererCanvasCull::canvas_item_flush_presort(RID p_item) {
+ Item *canvas_item = canvas_item_owner.get_or_null(p_item);
+ ERR_FAIL_NULL(canvas_item);
+
+ canvas_item->presort_commands.sort_custom();
+ if (!canvas_item->presort_commands.is_empty()) {
+ Item::Command *prev_command = canvas_item->presort_prev_command;
+ int32_t start_presort_state = canvas_item->presort_prev_state.is_base_state() ? -1 : 0;
+ int32_t prev_presort_state = start_presort_state;
+ for (Item::PresortData &pd : canvas_item->presort_commands) {
+ if (prev_presort_state != pd.state) {
+ _update_presort_state(canvas_item, prev_presort_state, pd.state, &prev_command);
+ prev_presort_state = pd.state;
+ }
+ _insert_command(canvas_item, &prev_command, pd.command);
+ }
+ if (start_presort_state != prev_presort_state) {
+ _update_presort_state(canvas_item, prev_presort_state, start_presort_state, &prev_command);
+ }
+ prev_command->next = nullptr;
+ canvas_item->last_command = prev_command;
+ }
+ canvas_item->presort_commands.clear();
+ canvas_item->presort_states.clear();
+ canvas_item->presort_prev_command = nullptr;
+ canvas_item->presort_level = -1;
+ canvas_item->presort_state = -1;
+ canvas_item->presort_prev_state = Item::PresortState();
+}
+
void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from, const Point2 &p_to, const Color &p_color, float p_width, bool p_antialiased) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
Item::CommandPrimitive *line = canvas_item->alloc_command();
ERR_FAIL_NULL(line);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ line, canvas_item->presort_level, canvas_item->presort_state });
+ }
Vector2 diff = (p_from - p_to);
Vector2 dir = diff.orthogonal().normalized();
@@ -806,6 +889,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *left_border = canvas_item->alloc_command();
ERR_FAIL_NULL(left_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ left_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
left_border->points[0] = begin_left;
left_border->points[1] = begin_left + border;
@@ -822,6 +908,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *right_border = canvas_item->alloc_command();
ERR_FAIL_NULL(right_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ right_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
right_border->points[0] = begin_right;
right_border->points[1] = begin_right - border;
@@ -838,6 +927,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *top_border = canvas_item->alloc_command();
ERR_FAIL_NULL(top_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ top_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
top_border->points[0] = begin_left;
top_border->points[1] = begin_left + border2;
@@ -854,6 +946,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *bottom_border = canvas_item->alloc_command();
ERR_FAIL_NULL(bottom_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ bottom_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
bottom_border->points[0] = end_left;
bottom_border->points[1] = end_left - border2;
@@ -870,6 +965,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *top_left_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(top_left_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ top_left_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
top_left_corner->points[0] = begin_left;
top_left_corner->points[1] = begin_left + border2;
@@ -886,6 +984,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *top_right_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(top_right_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ top_right_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
top_right_corner->points[0] = begin_right;
top_right_corner->points[1] = begin_right + border2;
@@ -902,6 +1003,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *bottom_left_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(bottom_left_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ bottom_left_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
bottom_left_corner->points[0] = end_left;
bottom_left_corner->points[1] = end_left - border2;
@@ -918,6 +1022,9 @@ void RendererCanvasCull::canvas_item_add_line(RID p_item, const Point2 &p_from,
{
Item::CommandPrimitive *bottom_right_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(bottom_right_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ bottom_right_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
bottom_right_corner->points[0] = end_right;
bottom_right_corner->points[1] = end_right - border2;
@@ -989,6 +1096,9 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vectoralloc_command();
ERR_FAIL_NULL(pline);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ pline, canvas_item->presort_level, canvas_item->presort_state });
+ }
if (p_antialiased) {
p_width = canvas_item_get_compensated_antialiasing_width(p_width);
@@ -1065,9 +1175,15 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vectoralloc_command();
ERR_FAIL_NULL(pline_left);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ pline_left, canvas_item->presort_level, canvas_item->presort_state });
+ }
Item::CommandPolygon *pline_right = canvas_item->alloc_command();
ERR_FAIL_NULL(pline_right);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ pline_right, canvas_item->presort_level, canvas_item->presort_state });
+ }
PackedColorArray colors_left;
PackedVector2Array points_left;
@@ -1274,6 +1390,10 @@ void RendererCanvasCull::canvas_item_add_multiline(RID p_item, const Vectoralloc_command();
ERR_FAIL_NULL(pline);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ pline, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
pline->primitive = RSE::PRIMITIVE_LINES;
pline->polygon.create(Vector(), p_points, colors);
} else {
@@ -1306,6 +1426,10 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
Item::CommandRect *rect = canvas_item->alloc_command();
ERR_FAIL_NULL(rect);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ rect, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
rect->modulate = p_color;
rect->rect = rect_adjusted;
@@ -1336,6 +1460,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *left_border = canvas_item->alloc_command();
ERR_FAIL_NULL(left_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ left_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
left_border->points[0] = begin_left;
left_border->points[1] = begin_left + border;
@@ -1352,6 +1479,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *right_border = canvas_item->alloc_command();
ERR_FAIL_NULL(right_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ right_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
right_border->points[0] = begin_right;
right_border->points[1] = begin_right - border;
@@ -1368,6 +1498,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *top_border = canvas_item->alloc_command();
ERR_FAIL_NULL(top_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ top_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
top_border->points[0] = begin_left;
top_border->points[1] = begin_left + border2;
@@ -1384,6 +1517,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *bottom_border = canvas_item->alloc_command();
ERR_FAIL_NULL(bottom_border);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ bottom_border, canvas_item->presort_level, canvas_item->presort_state });
+ }
bottom_border->points[0] = end_left;
bottom_border->points[1] = end_left - border2;
@@ -1400,6 +1536,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *top_left_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(top_left_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ top_left_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
top_left_corner->points[0] = begin_left;
top_left_corner->points[1] = begin_left + border2;
@@ -1416,6 +1555,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *top_right_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(top_right_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ top_right_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
top_right_corner->points[0] = begin_right;
top_right_corner->points[1] = begin_right + border2;
@@ -1432,6 +1574,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *bottom_left_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(bottom_left_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ bottom_left_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
bottom_left_corner->points[0] = end_left;
bottom_left_corner->points[1] = end_left - border2;
@@ -1448,6 +1593,9 @@ void RendererCanvasCull::canvas_item_add_rect(RID p_item, const Rect2 &p_rect, c
{
Item::CommandPrimitive *bottom_right_corner = canvas_item->alloc_command();
ERR_FAIL_NULL(bottom_right_corner);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ bottom_right_corner, canvas_item->presort_level, canvas_item->presort_state });
+ }
bottom_right_corner->points[0] = end_right;
bottom_right_corner->points[1] = end_right - border2;
@@ -1481,6 +1629,9 @@ void RendererCanvasCull::canvas_item_add_ellipse(RID p_item, const Point2 &p_pos
{
Item::CommandPolygon *ellipse = canvas_item->alloc_command();
ERR_FAIL_NULL(ellipse);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ ellipse, canvas_item->presort_level, canvas_item->presort_state });
+ }
ellipse->primitive = RSE::PRIMITIVE_TRIANGLES;
@@ -1526,6 +1677,10 @@ void RendererCanvasCull::canvas_item_add_ellipse(RID p_item, const Point2 &p_pos
Item::CommandPolygon *feather = canvas_item->alloc_command();
ERR_FAIL_NULL(feather);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ feather, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
feather->primitive = RSE::PRIMITIVE_TRIANGLE_STRIP;
Color transparent = Color(p_color, 0.0);
@@ -1573,6 +1728,10 @@ void RendererCanvasCull::canvas_item_add_texture_rect(RID p_item, const Rect2 &p
Item::CommandRect *rect = canvas_item->alloc_command();
ERR_FAIL_NULL(rect);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ rect, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
rect->modulate = p_modulate;
rect->rect = p_rect;
rect->flags = 0;
@@ -1604,6 +1763,10 @@ void RendererCanvasCull::canvas_item_add_msdf_texture_rect_region(RID p_item, co
Item::CommandRect *rect = canvas_item->alloc_command();
ERR_FAIL_NULL(rect);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ rect, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
rect->modulate = p_modulate;
rect->rect = p_rect;
@@ -1638,6 +1801,10 @@ void RendererCanvasCull::canvas_item_add_lcd_texture_rect_region(RID p_item, con
Item::CommandRect *rect = canvas_item->alloc_command();
ERR_FAIL_NULL(rect);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ rect, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
rect->modulate = p_modulate;
rect->rect = p_rect;
@@ -1670,6 +1837,10 @@ void RendererCanvasCull::canvas_item_add_texture_rect_region(RID p_item, const R
Item::CommandRect *rect = canvas_item->alloc_command();
ERR_FAIL_NULL(rect);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ rect, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
rect->modulate = p_modulate;
rect->rect = p_rect;
@@ -1711,6 +1882,9 @@ void RendererCanvasCull::canvas_item_add_nine_patch(RID p_item, const Rect2 &p_r
Item::CommandNinePatch *style = canvas_item->alloc_command();
ERR_FAIL_NULL(style);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ style, canvas_item->presort_level, canvas_item->presort_state });
+ }
style->texture = p_texture;
@@ -1735,6 +1909,9 @@ void RendererCanvasCull::canvas_item_add_primitive(RID p_item, const Vectoralloc_command();
ERR_FAIL_NULL(prim);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ prim, canvas_item->presort_level, canvas_item->presort_state });
+ }
for (int i = 0; i < p_points.size(); i++) {
prim->points[i] = p_points[i];
@@ -1771,7 +1948,12 @@ void RendererCanvasCull::canvas_item_add_polygon(RID p_item, const Vectoralloc_command();
ERR_FAIL_NULL(polygon);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ polygon, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
polygon->primitive = RSE::PRIMITIVE_TRIANGLES;
+
polygon->texture = p_texture;
polygon->polygon.create(indices, p_points, p_colors, p_uvs);
}
@@ -1789,6 +1971,9 @@ void RendererCanvasCull::canvas_item_add_triangle_array(RID p_item, const Vector
Item::CommandPolygon *polygon = canvas_item->alloc_command();
ERR_FAIL_NULL(polygon);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ polygon, canvas_item->presort_level, canvas_item->presort_state });
+ }
polygon->texture = p_texture;
@@ -1800,10 +1985,28 @@ void RendererCanvasCull::canvas_item_add_triangle_array(RID p_item, const Vector
void RendererCanvasCull::canvas_item_add_set_transform(RID p_item, const Transform2D &p_transform) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
+ if (canvas_item->presort_level >= 0) {
+ Item::PresortState st;
+ if (canvas_item->presort_state >= 0) {
+ st = canvas_item->presort_states[canvas_item->presort_state];
+ }
+ if (st.transform == p_transform) {
+ return;
+ }
+ st.transform = p_transform;
+ if (st.is_base_state()) {
+ canvas_item->presort_state = -1;
+ } else {
+ canvas_item->presort_states.push_back(st);
+ canvas_item->presort_state = canvas_item->presort_states.size() - 1;
+ }
+ return;
+ }
Item::CommandTransform *tr = canvas_item->alloc_command();
ERR_FAIL_NULL(tr);
tr->xform = p_transform;
+ canvas_item->presort_prev_state.transform = p_transform;
}
void RendererCanvasCull::canvas_item_add_mesh(RID p_item, const RID &p_mesh, const Transform2D &p_transform, const Color &p_modulate, RID p_texture) {
@@ -1813,6 +2016,10 @@ void RendererCanvasCull::canvas_item_add_mesh(RID p_item, const RID &p_mesh, con
Item::CommandMesh *m = canvas_item->alloc_command();
ERR_FAIL_NULL(m);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ m, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
m->mesh = p_mesh;
if (canvas_item->skeleton.is_valid()) {
m->mesh_instance = RSG::mesh_storage->mesh_instance_create(p_mesh);
@@ -1831,6 +2038,10 @@ void RendererCanvasCull::canvas_item_add_particles(RID p_item, RID p_particles,
Item::CommandParticles *part = canvas_item->alloc_command();
ERR_FAIL_NULL(part);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ part, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
part->particles = p_particles;
part->texture = p_texture;
@@ -1845,6 +2056,10 @@ void RendererCanvasCull::canvas_item_add_multimesh(RID p_item, RID p_mesh, RID p
Item::CommandMultiMesh *mm = canvas_item->alloc_command();
ERR_FAIL_NULL(mm);
+ if (canvas_item->presort_level >= 0) {
+ canvas_item->presort_commands.push_back({ mm, canvas_item->presort_level, canvas_item->presort_state });
+ }
+
mm->multimesh = p_mesh;
mm->texture = p_texture;
@@ -1853,15 +2068,53 @@ void RendererCanvasCull::canvas_item_add_multimesh(RID p_item, RID p_mesh, RID p
void RendererCanvasCull::canvas_item_add_clip_ignore(RID p_item, bool p_ignore) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
+ if (canvas_item->presort_level >= 0) {
+ Item::PresortState st;
+ if (canvas_item->presort_state >= 0) {
+ st = canvas_item->presort_states[canvas_item->presort_state];
+ }
+ if (st.clip_ignore == p_ignore) {
+ return;
+ }
+ st.clip_ignore = p_ignore;
+ if (st.is_base_state()) {
+ canvas_item->presort_state = -1;
+ } else {
+ canvas_item->presort_states.push_back(st);
+ canvas_item->presort_state = canvas_item->presort_states.size() - 1;
+ }
+ return;
+ }
Item::CommandClipIgnore *ci = canvas_item->alloc_command();
ERR_FAIL_NULL(ci);
ci->ignore = p_ignore;
+ canvas_item->presort_prev_state.clip_ignore = p_ignore;
}
void RendererCanvasCull::canvas_item_add_animation_slice(RID p_item, double p_animation_length, double p_slice_begin, double p_slice_end, double p_offset) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
+ if (canvas_item->presort_level >= 0) {
+ Item::PresortState st;
+ if (canvas_item->presort_state >= 0) {
+ st = canvas_item->presort_states[canvas_item->presort_state];
+ }
+ if (st.animation_length == p_animation_length && st.slice_begin == p_slice_begin && st.slice_end == p_slice_end && st.offset == p_offset) {
+ return;
+ }
+ st.animation_length = p_animation_length;
+ st.slice_begin = p_slice_begin;
+ st.slice_end = p_slice_end;
+ st.offset = p_offset;
+ if (st.is_base_state()) {
+ canvas_item->presort_state = -1;
+ } else {
+ canvas_item->presort_states.push_back(st);
+ canvas_item->presort_state = canvas_item->presort_states.size() - 1;
+ }
+ return;
+ }
Item::CommandAnimationSlice *as = canvas_item->alloc_command();
ERR_FAIL_NULL(as);
@@ -1869,6 +2122,11 @@ void RendererCanvasCull::canvas_item_add_animation_slice(RID p_item, double p_an
as->slice_begin = p_slice_begin;
as->slice_end = p_slice_end;
as->offset = p_offset;
+
+ canvas_item->presort_prev_state.animation_length = p_animation_length;
+ canvas_item->presort_prev_state.slice_begin = p_slice_begin;
+ canvas_item->presort_prev_state.slice_end = p_slice_end;
+ canvas_item->presort_prev_state.offset = p_offset;
}
void RendererCanvasCull::canvas_item_set_sort_children_by_y(RID p_item, bool p_enable) {
@@ -1904,7 +2162,7 @@ void RendererCanvasCull::canvas_item_attach_skeleton(RID p_item, RID p_skeleton)
}
canvas_item->skeleton = p_skeleton;
- Item::Command *c = canvas_item->commands;
+ Item::Command *c = canvas_item->first_command;
while (c) {
if (c->type == Item::Command::TYPE_MESH) {
@@ -1946,6 +2204,12 @@ void RendererCanvasCull::canvas_item_clear(RID p_item) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
+ canvas_item->presort_commands.clear();
+ canvas_item->presort_states.clear();
+ canvas_item->presort_prev_command = nullptr;
+ canvas_item->presort_level = -1;
+ canvas_item->presort_state = -1;
+ canvas_item->presort_prev_state = Item::PresortState();
canvas_item->clear();
#ifdef DEBUG_ENABLED
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index de9b929060..8037ad90e9 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -63,6 +63,95 @@ public:
Vector- child_items;
+ struct PresortData {
+ Item::Command *command = nullptr;
+ int32_t level = 0;
+ int32_t state = -1;
+ };
+ struct PresortDataCompare {
+ _FORCE_INLINE_ bool operator()(const PresortData &l, const PresortData &r) const {
+ if (l.level == r.level) {
+ uint64_t l_rid = 0;
+ uint64_t r_rid = 0;
+ switch (l.command->type) {
+ case Item::Command::Type::TYPE_RECT: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_NINEPATCH: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_POLYGON: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_PRIMITIVE: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_MESH: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_MULTIMESH: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_PARTICLES: {
+ l_rid = static_cast(l.command)->texture.get_id();
+ } break;
+ default: {
+ l_rid = 0;
+ } break;
+ }
+ switch (r.command->type) {
+ case Item::Command::Type::TYPE_RECT: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_NINEPATCH: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_POLYGON: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_PRIMITIVE: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_MESH: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_MULTIMESH: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ case Item::Command::Type::TYPE_PARTICLES: {
+ r_rid = static_cast(r.command)->texture.get_id();
+ } break;
+ default: {
+ r_rid = 0;
+ } break;
+ }
+ return l_rid < r_rid;
+ } else {
+ return l.level < r.level;
+ }
+ }
+ };
+
+ struct PresortState {
+ Transform2D transform;
+ bool clip_ignore = false;
+ double animation_length = 0.0;
+ double slice_begin = 0.0;
+ double slice_end = 0.0;
+ double offset = 0.0;
+
+ _FORCE_INLINE_ bool is_base_state() const {
+ return transform == Transform2D() && !clip_ignore && animation_length == 0.0 && slice_begin == 0.0 && slice_end == 0.0 && offset == 0.0;
+ }
+ };
+
+ int32_t presort_level = -1;
+ int32_t presort_state = -1;
+ Item::Command *presort_prev_command = nullptr;
+ Item::PresortState presort_prev_state;
+ LocalVector presort_commands;
+ LocalVector presort_states;
+
struct VisibilityNotifierData {
Rect2 area;
Callable enter_callable;
@@ -105,6 +194,16 @@ public:
}
};
+ _FORCE_INLINE_ void _insert_command(Item *p_canvas_item, Item::Command **r_prev_command, Item::Command *p_command) const {
+ if (*r_prev_command) {
+ (*r_prev_command)->next = p_command;
+ } else {
+ p_canvas_item->first_command = p_command;
+ }
+ *r_prev_command = p_command;
+ }
+ void _update_presort_state(Item *p_canvas_item, int32_t p_old_state, int32_t p_new_state, Item::Command **r_prev_command);
+
void _item_queue_update(Item *p_item, bool p_update_dependencies);
SelfList
- ::List _item_update_list;
@@ -280,6 +379,9 @@ public:
void canvas_item_add_clip_ignore(RID p_item, bool p_ignore);
void canvas_item_add_animation_slice(RID p_item, double p_animation_length, double p_slice_begin, double p_slice_end, double p_offset);
+ void canvas_item_set_presort_level(RID p_item, int64_t p_order);
+ void canvas_item_flush_presort(RID p_item);
+
void canvas_item_set_sort_children_by_y(RID p_item, bool p_enable);
void canvas_item_set_z_index(RID p_item, int p_z);
void canvas_item_set_z_as_relative_to_parent(RID p_item, bool p_enable);
diff --git a/servers/rendering/renderer_canvas_render.cpp b/servers/rendering/renderer_canvas_render.cpp
index a6ad263d1d..f968134e42 100644
--- a/servers/rendering/renderer_canvas_render.cpp
+++ b/servers/rendering/renderer_canvas_render.cpp
@@ -41,7 +41,7 @@ const Rect2 &RendererCanvasRender::Item::get_rect() const {
//must update rect
- if (commands == nullptr) {
+ if (first_command == nullptr) {
rect = Rect2();
rect_dirty = false;
return rect;
@@ -51,7 +51,7 @@ const Rect2 &RendererCanvasRender::Item::get_rect() const {
bool found_xform = false;
bool first = true;
- const Item::Command *c = commands;
+ const Item::Command *c = first_command;
while (c) {
Rect2 r;
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index d452bc3611..735a61519c 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -374,7 +374,8 @@ public:
const Rect2 &get_rect() const;
- Command *commands = nullptr;
+ Command *heap_command = nullptr;
+ Command *first_command = nullptr;
Command *last_command = nullptr;
Vector blocks;
uint32_t current_block;
@@ -383,15 +384,16 @@ public:
#endif
template
- T *alloc_command() {
+ T *alloc_command(bool p_set_next = true) {
T *command = nullptr;
- if (commands == nullptr) {
+ if (heap_command == nullptr) {
// As the most common use case of canvas items is to
// use only one command, the first is done with it's
// own allocation. The rest of them use blocks.
command = memnew(T);
command->next = nullptr;
- commands = command;
+ heap_command = command;
+ first_command = command;
last_command = command;
} else {
//Subsequent commands go into a block.
@@ -418,7 +420,9 @@ public:
void *memory = c->memory + c->usage;
command = memnew_placement(memory, T);
command->next = nullptr;
- last_command->next = command;
+ if (p_set_next) {
+ last_command->next = command;
+ }
last_command = command;
c->usage += sizeof(T);
break;
@@ -430,14 +434,14 @@ public:
}
void clear() {
- // The first one is always allocated on heap
+ // The `heap_command` is always allocated on heap
// the rest go in the blocks
- Command *c = commands;
+ Command *c = first_command;
while (c) {
Command *n = c->next;
- if (c == commands) {
- memdelete(commands);
- commands = nullptr;
+ if (c == heap_command) {
+ memdelete(heap_command);
+ heap_command = nullptr;
} else {
c->~Command();
}
@@ -451,8 +455,9 @@ public:
}
}
+ heap_command = nullptr;
last_command = nullptr;
- commands = nullptr;
+ first_command = nullptr;
current_block = 0;
clip = false;
rect_dirty = true;
@@ -465,8 +470,9 @@ public:
RSE::CanvasItemTextureRepeat texture_repeat;
Item() {
- commands = nullptr;
+ heap_command = nullptr;
last_command = nullptr;
+ first_command = nullptr;
current_block = 0;
light_mask = 1;
vp_render = nullptr;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 9d86f3e91f..c13c23bf05 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -795,7 +795,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
}
if (ci->skeleton.is_valid()) {
- const Item::Command *c = ci->commands;
+ const Item::Command *c = ci->first_command;
while (c) {
if (c->type == Item::Command::TYPE_MESH) {
@@ -2397,7 +2397,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
r_current_batch->use_lighting = use_lighting;
}
- const Item::Command *c = p_item->commands;
+ const Item::Command *c = p_item->first_command;
while (c) {
if (skipping && c->type != Item::Command::TYPE_ANIMATION_SLICE) {
c = c->next;
diff --git a/servers/rendering/rendering_server.cpp b/servers/rendering/rendering_server.cpp
index d2b235f8e3..795eadc851 100644
--- a/servers/rendering/rendering_server.cpp
+++ b/servers/rendering/rendering_server.cpp
@@ -3358,6 +3358,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_copy_to_backbuffer", "item", "enabled", "rect"), &RenderingServer::canvas_item_set_copy_to_backbuffer);
ClassDB::bind_method(D_METHOD("canvas_item_attach_skeleton", "item", "skeleton"), &RenderingServer::canvas_item_attach_skeleton);
+ ClassDB::bind_method(D_METHOD("canvas_item_set_presort_level", "item", "order"), &RenderingServer::canvas_item_set_presort_level);
+ ClassDB::bind_method(D_METHOD("canvas_item_flush_presort", "item"), &RenderingServer::canvas_item_flush_presort);
+
ClassDB::bind_method(D_METHOD("canvas_item_clear", "item"), &RenderingServer::canvas_item_clear);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &RenderingServer::canvas_item_set_draw_index);
ClassDB::bind_method(D_METHOD("canvas_item_set_material", "item", "material"), &RenderingServer::canvas_item_set_material);
diff --git a/servers/rendering/rendering_server.h b/servers/rendering/rendering_server.h
index 4a5f852bcd..3179fa25e8 100644
--- a/servers/rendering/rendering_server.h
+++ b/servers/rendering/rendering_server.h
@@ -839,6 +839,9 @@ public:
virtual void canvas_item_add_clip_ignore(RID p_item, bool p_ignore) = 0;
virtual void canvas_item_add_animation_slice(RID p_item, double p_animation_length, double p_slice_begin, double p_slice_end, double p_offset) = 0;
+ virtual void canvas_item_set_presort_level(RID p_item, int64_t p_order) = 0;
+ virtual void canvas_item_flush_presort(RID p_item) = 0;
+
virtual void canvas_item_set_sort_children_by_y(RID p_item, bool p_enable) = 0;
virtual void canvas_item_set_z_index(RID p_item, int p_z) = 0;
virtual void canvas_item_set_z_as_relative_to_parent(RID p_item, bool p_enable) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 9bc71818ed..21da4a8085 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -1045,6 +1045,9 @@ public:
FUNC2(canvas_item_add_clip_ignore, RID, bool)
FUNC5(canvas_item_add_animation_slice, RID, double, double, double, double)
+ FUNC2(canvas_item_set_presort_level, RID, int64_t);
+ FUNC1(canvas_item_flush_presort, RID);
+
FUNC2(canvas_item_set_sort_children_by_y, RID, bool)
FUNC2(canvas_item_set_z_index, RID, int)
FUNC2(canvas_item_set_z_as_relative_to_parent, RID, bool)