diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index b6788ad5f3..33ce28bf86 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -96,20 +96,94 @@ static Transform2D _canvas_get_transform(RendererViewport::Viewport *p_viewport, return xf; } -Vector RendererViewport::_sort_active_viewports() { - // We need to sort the viewports in a "topological order", children first and - // parents last. We also need to keep sibling viewports in the original order - // from top to bottom. +Vector RendererViewport::_topological_sort_siblings(const Vector &p_siblings) { + // The parent node (the one calling this function) is NOT in p_siblings. + // The parent has already been placed in the result list before we call this. + // All viewports in p_siblings share the same parent, so they are siblings. + + int number_of_siblings = p_siblings.size(); + if (number_of_siblings <= 1) { + return p_siblings; + } + + // Kahn's algorithm - each node (viewport) is ordered according to dependencies (the in_degree below) + // ...essentially what node must come before it. + + // This stores the index of the sibling that consumes p_siblings[i]'s texture + // i.e. If adjacency_list[i] = j, then p_siblings[i]'s texture is consumed by p_siblings[j] + // In that case, p_siblings[i] must render BEFORE p_siblings[j], + // p_siblings[j] needs p_siblings[i]'s texture to be ready first + // (See for loop below) + LocalVector adjacency_list; + adjacency_list.resize(number_of_siblings); + + // in_degree[i] = number of siblings that must render before p_siblings[i]. + LocalVector in_degree; + in_degree.resize(number_of_siblings); + + for (int i = 0; i < number_of_siblings; i++) { + adjacency_list[i] = -1; + in_degree[i] = 0; + } + + // Build dependency graph with a linear search + for (int i = 0; i < number_of_siblings; i++) { + const RID used_by = p_siblings[i]->used_by_viewport; + if (!used_by.is_valid()) { + continue; + } + for (int j = 0; j < number_of_siblings; j++) { + if (p_siblings[j]->self == used_by) { + // p_siblings[i] must render before p_siblings[j] + adjacency_list[i] = j; + in_degree[j]++; + break; + } + } + } + + // pre-allocated array used as a queue (no per-element allocation). + LocalVector queue; + queue.resize(number_of_siblings); + int queue_head = 0; + int queue_tail = 0; + + for (int i = 0; i < number_of_siblings; i++) { + if (in_degree[i] == 0) { + queue[queue_tail++] = i; + } + } + Vector result; + while (queue_head < queue_tail) { + int idx = queue[queue_head++]; + result.push_back(p_siblings[idx]); + if (adjacency_list[idx] != -1) { + int next_idx = adjacency_list[idx]; + if (--in_degree[next_idx] == 0) { + queue[queue_tail++] = next_idx; + } + } + } + + if (result.size() != number_of_siblings) { + WARN_PRINT_ONCE("Viewport sibling dependency cycle detected; falling back to original sort order."); + return p_siblings; + } + + return result; +} + +Vector RendererViewport::_sort_active_viewports() { Vector result; List nodes; + // Collect root viewports (no parent) in active_viewports order. for (int i = active_viewports.size() - 1; i >= 0; --i) { Viewport *viewport = active_viewports[i]; if (viewport->parent.is_valid()) { continue; } - nodes.push_back(viewport); result.insert(0, viewport); } @@ -118,12 +192,23 @@ Vector RendererViewport::_sort_active_viewports() const Viewport *node = nodes.front()->get(); nodes.pop_front(); - for (int i = active_viewports.size() - 1; i >= 0; --i) { + // Collect children of this node in active_viewports order. + // Note that all of these children are "siblings" because they're all 1 level down from this parent node + Vector children; + for (int i = 0; i < active_viewports.size(); ++i) { Viewport *child = active_viewports[i]; - if (child->parent != node->self) { - continue; + if (child->parent == node->self) { + children.push_back(child); } + } + // Topologically sort siblings so that a viewport whose texture is consumed by a sibling is drawn first. + Vector sorted_children = _topological_sort_siblings(children); + + // Insert sorted children before the parent (children-first order). + // Iterate in reverse so we start at the first child (at the "bottom", so to speak) + for (int i = sorted_children.size() - 1; i >= 0; --i) { + Viewport *child = sorted_children[i]; if (!nodes.find(child)) { nodes.push_back(child); result.insert(0, child); @@ -827,15 +912,33 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) { int objects_drawn = 0; int draw_calls_used = 0; + // Clear stale used_by_viewport data from the previous frame so that + // viewports that are no longer consuming a sibling's texture don't + // keep influencing the sort order. + for (Viewport *vp : active_viewports) { + vp->used_by_viewport = RID(); + } + + // Pre-allocate snapshot storage (avoids per-frame heap allocation). + LocalVector rt_was_used_snapshot; + rt_was_used_snapshot.resize(active_viewports.size()); + for (int i = 0; i < sorted_active_viewports.size(); i++) { Viewport *vp = sorted_active_viewports[i]; if (vp->last_pass != draw_viewports_pass) { - continue; //should not draw + continue; // should not draw } RENDER_TIMESTAMP("> Render Viewport " + itos(i)); + // Snapshot was_used for every active viewport BEFORE rendering vp. + // Anything that flips false→true during _draw_viewport(vp) was + // consumed by vp, establishing a dependency for next frame's sort. + for (int j = 0; j < (int)active_viewports.size(); j++) { + rt_was_used_snapshot[j] = RSG::texture_storage->render_target_was_used(active_viewports[j]->render_target); + } + RSG::texture_storage->render_target_set_as_unused(vp->render_target); #ifndef XR_DISABLED if (vp->use_xr && xr_interface.is_valid()) { @@ -915,6 +1018,23 @@ void RendererViewport::draw_viewports(bool p_swap_buffers) { blits->push_back(blit); } } + + // After rendering vp, detect which sibling viewports had their + // render target's was_used flag set for the first time this call. + // This is critical info for the topological search to resolve sibling viewport order + for (int j = 0; j < (int)active_viewports.size(); j++) { + Viewport *other_vp = active_viewports[j]; + if (other_vp == vp) { + continue; + } + if (!rt_was_used_snapshot[j] && + RSG::texture_storage->render_target_was_used(other_vp->render_target)) { + if (other_vp->used_by_viewport != vp->self) { + other_vp->used_by_viewport = vp->self; + sorted_active_viewports_dirty = true; + } + } + } } if (vp->update_mode == RS::VIEWPORT_UPDATE_ONCE) { @@ -1096,6 +1216,7 @@ void RendererViewport::viewport_set_parent_viewport(RID p_viewport, RID p_parent ERR_FAIL_NULL(viewport); viewport->parent = p_parent_viewport; + sorted_active_viewports_dirty = true; } void RendererViewport::viewport_set_clear_mode(RID p_viewport, RS::ViewportClearMode p_clear_mode) { diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 7585f2d1bf..9995aae50c 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -52,6 +52,7 @@ class RendererViewport { struct Viewport { RID self; RID parent; + RID used_by_viewport; // RID of the sibling viewport that consumed this viewport's texture last frame /// Use xr interface to override camera positioning and projection matrices and control output bool use_xr = false; @@ -208,7 +209,15 @@ class RendererViewport { int num_viewports_with_motion_vectors = 0; private: + // We need to sort the viewports in a "topological order", children first and parents last. + // Among siblings sharing the same parent, use used_by_viewport dependency + // information to ensure a viewport whose texture is consumed by a sibling + // is rendered before that sibling. Vector _sort_active_viewports(); + /// Topologically sort a group of sibling viewports using Kahn's algorithm. + /// If sibling A has used_by_viewport == sibling B's self RID, then A must + /// be rendered before B (A's texture is consumed by B). + Vector _topological_sort_siblings(const Vector &p_siblings); void _viewport_set_size(Viewport *p_viewport, int p_width, int p_height, uint32_t p_view_count); bool _viewport_requires_motion_vectors(Viewport *p_viewport); void _viewport_set_force_motion_vectors(Viewport *p_viewport, bool p_force_motion_vectors);