From 513a888dc6336377a204de5e4ba86ec30e7da5c1 Mon Sep 17 00:00:00 2001
From: fsfgrafb <2113147846@qq.com>
Date: Wed, 23 Sep 2026 14:56:09 +0800
Subject: [PATCH 1/3] Fix Viewport texture filter inheritance
---
doc/classes/Viewport.xml | 3 +-
scene/main/viewport.cpp | 45 ++++++----
scene/main/viewport.h | 1 +
tests/scene/test_viewport.cpp | 159 ++++++++++++++++++++++++++++++++++
4 files changed, 188 insertions(+), 20 deletions(-)
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index bafca16d649b5..f3d138a3fdb5d 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -331,6 +331,7 @@
The default filter mode used by [CanvasItem] nodes in this viewport.
+ The root viewport's default filter is set by [member ProjectSettings.rendering/textures/canvas_textures/default_texture_filter]. To inherit filtering from the parent node or viewport in another viewport, use [constant DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE].
The default repeat mode used by [CanvasItem] nodes in this viewport.
@@ -782,7 +783,7 @@
Use this for non-pixel art textures that may be viewed at a low scale (e.g. due to [Camera2D] zoom or sprite scaling), as mipmaps are important to smooth out pixels that are smaller than on-screen pixels.
- The [Viewport] will inherit the filter from its parent [CanvasItem] or [Viewport].
+ The [Viewport] will inherit the filter from its parent [CanvasItem]. If the parent is not a [CanvasItem], or its filter inherits the viewport default, the filter is inherited from the nearest ancestor [Viewport]. If there is no ancestor [Viewport], [constant DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR] is used.
Represents the size of the [enum DefaultCanvasItemTextureFilter] enum.
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index a50214c9c56e4..b72bd88a214d1 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -4081,16 +4081,15 @@ void Viewport::_refresh_texture_filter_cache() const {
default_canvas_item_texture_filter_cache = RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;
} break;
case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE: {
- Node *p = get_parent();
- CanvasItem *parent_ci = Object::cast_to(p);
+ CanvasItem *parent_ci = Object::cast_to(get_parent());
if (parent_ci) {
default_canvas_item_texture_filter_cache = (RenderingServerEnums::CanvasItemTextureFilter)parent_ci->get_texture_filter_in_tree();
- if (default_canvas_item_texture_filter_cache == RSE::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT) {
- default_canvas_item_texture_filter_cache = RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR;
+ if (default_canvas_item_texture_filter_cache != RSE::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT) {
+ break;
}
- break;
}
- Viewport *parent_vp = Object::cast_to(p);
+ // DEFAULT uses the enclosing viewport's filter.
+ Viewport *parent_vp = get_parent_viewport();
if (parent_vp) {
default_canvas_item_texture_filter_cache = (RenderingServerEnums::CanvasItemTextureFilter)parent_vp->get_texture_filter_in_tree();
break;
@@ -4102,6 +4101,26 @@ void Viewport::_refresh_texture_filter_cache() const {
}
}
+void Viewport::_propagate_texture_filter_changed(Node *p_node) {
+ for (Node *child : p_node->iterate_children()) {
+ Viewport *child_vp = Object::cast_to(child);
+ if (child_vp) {
+ if (child_vp->default_canvas_item_texture_filter == DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE) {
+ child_vp->_update_texture_filter_changed(true);
+ }
+ continue;
+ }
+
+ CanvasItem *child_ci = Object::cast_to(child);
+ if (child_ci && child_ci->texture_filter == CanvasItem::TEXTURE_FILTER_PARENT_NODE) {
+ child_ci->_update_texture_filter_changed(false);
+ }
+
+ // Descendants may still inherit this viewport's filter, even past a CanvasItem.
+ _propagate_texture_filter_changed(child);
+ }
+}
+
void Viewport::_update_texture_filter_changed(bool p_propagate) {
if (!is_inside_tree()) {
return;
@@ -4110,19 +4129,7 @@ void Viewport::_update_texture_filter_changed(bool p_propagate) {
RS::get_singleton()->viewport_set_default_canvas_item_texture_filter(viewport, default_canvas_item_texture_filter_cache);
if (p_propagate) {
- for (Node *c : iterate_children()) {
- CanvasItem *child_ci = Object::cast_to(c);
- if (child_ci) {
- if (child_ci->texture_filter == CanvasItem::TEXTURE_FILTER_PARENT_NODE) {
- child_ci->_update_texture_filter_changed(true);
- }
- continue;
- }
- Viewport *child_vp = Object::cast_to(c);
- if (child_vp && child_vp->default_canvas_item_texture_filter == Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE) {
- child_vp->_update_texture_filter_changed(true);
- }
- }
+ _propagate_texture_filter_changed(this);
}
}
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 741277ec5b751..c61c828135d7a 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -430,6 +430,7 @@ class Viewport : public Node {
DefaultCanvasItemTextureFilter default_canvas_item_texture_filter = DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR;
mutable RenderingServerEnums::CanvasItemTextureFilter default_canvas_item_texture_filter_cache = RenderingServerEnums::CANVAS_ITEM_TEXTURE_FILTER_LINEAR;
void _refresh_texture_filter_cache() const;
+ void _propagate_texture_filter_changed(Node *p_node);
DefaultCanvasItemTextureRepeat default_canvas_item_texture_repeat = DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED;
mutable RenderingServerEnums::CanvasItemTextureRepeat default_canvas_item_texture_repeat_cache = RenderingServerEnums::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED;
diff --git a/tests/scene/test_viewport.cpp b/tests/scene/test_viewport.cpp
index 7ad2ceaccf82f..ede739f861008 100644
--- a/tests/scene/test_viewport.cpp
+++ b/tests/scene/test_viewport.cpp
@@ -35,10 +35,17 @@ TEST_FORCE_LINK(test_viewport)
#include "core/object/callable_mp.h"
#include "scene/2d/node_2d.h"
#include "scene/gui/control.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/option_button.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/popup_menu.h"
#include "scene/gui/subviewport_container.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
+#include "servers/rendering/renderer_viewport.h"
+#include "servers/rendering/rendering_server.h"
+#include "servers/rendering/rendering_server_globals.h"
#include "tests/display_server_mock.h"
#include "tests/signal_watcher.h"
@@ -51,6 +58,158 @@ TEST_FORCE_LINK(test_viewport)
namespace TestViewport {
+static void check_texture_filter(Viewport *p_viewport, RSE::CanvasItemTextureFilter p_expected) {
+ INFO("Viewport: ", p_viewport->get_path());
+ RenderingServer::get_singleton()->sync();
+ const RendererViewport::Viewport *render_viewport = RSG::viewport->viewport_owner.get_or_null(p_viewport->get_viewport_rid());
+ REQUIRE(render_viewport != nullptr);
+ // Check the renderer before calling the getter, which refreshes the scene-side cache.
+ CHECK(render_viewport->texture_filter == p_expected);
+ CHECK(p_viewport->get_texture_filter_in_tree() == p_expected);
+}
+
+TEST_CASE("[SceneTree][Viewport] Texture filter inheritance") {
+ SubViewport *outer = memnew(SubViewport);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ SceneTree::get_singleton()->get_root()->add_child(outer);
+
+ SubViewport *inner = memnew(SubViewport);
+ inner->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
+
+ SUBCASE("Direct viewport parent") {
+ outer->add_child(inner);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS);
+ }
+
+ SUBCASE("CanvasItem parent inherits the enclosing viewport") {
+ Control *control = memnew(Control);
+ outer->add_child(control);
+ control->add_child(inner);
+ CHECK(control->get_texture_filter_in_tree() == CanvasItem::TEXTURE_FILTER_PARENT_NODE);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ }
+
+ SUBCASE("Explicit CanvasItem filters take precedence") {
+ Control *control = memnew(Control);
+ Control *child = memnew(Control);
+ outer->add_child(control);
+ control->add_child(child);
+ child->add_child(inner);
+ for (int filter = CanvasItem::TEXTURE_FILTER_NEAREST; filter < CanvasItem::TEXTURE_FILTER_MAX; filter++) {
+ control->set_texture_filter(CanvasItem::TextureFilter(filter));
+ check_texture_filter(inner, RSE::CanvasItemTextureFilter(filter));
+ }
+ control->set_texture_filter(CanvasItem::TEXTURE_FILTER_PARENT_NODE);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ }
+
+ SUBCASE("Plain Node parent and reparenting") {
+ Node *node = memnew(Node);
+ outer->add_child(node);
+ node->add_child(inner);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+
+ SubViewport *other = memnew(SubViewport);
+ other->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
+ outer->add_child(other);
+ node->reparent(other);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
+ }
+
+ SUBCASE("Explicit viewport defaults are preserved") {
+ outer->add_child(inner);
+ SubViewport *explicit_viewport = memnew(SubViewport);
+ inner->add_child(explicit_viewport);
+ CHECK(explicit_viewport->get_default_canvas_item_texture_filter() == Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ Control *control = memnew(Control);
+ explicit_viewport->add_child(control);
+ SubViewport *nested = memnew(SubViewport);
+ nested->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
+ control->add_child(nested);
+ check_texture_filter(nested, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
+ check_texture_filter(explicit_viewport, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ check_texture_filter(nested, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ explicit_viewport->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
+ check_texture_filter(nested, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
+ }
+
+ SUBCASE("Viewport inheritance crosses CanvasItem inheritance boundaries") {
+ Control *explicit_control = memnew(Control);
+ explicit_control->set_texture_filter(CanvasItem::TEXTURE_FILTER_LINEAR);
+ outer->add_child(explicit_control);
+ Node *boundary = nullptr;
+ SUBCASE("Node") {
+ boundary = memnew(Node);
+ }
+ SUBCASE("CanvasLayer") {
+ boundary = memnew(CanvasLayer);
+ }
+ SUBCASE("Top-level CanvasItem") {
+ Control *top_level = memnew(Control);
+ top_level->set_as_top_level(true);
+ boundary = top_level;
+ }
+ explicit_control->add_child(boundary);
+ Control *control = memnew(Control);
+ boundary->add_child(control);
+ control->add_child(inner);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ outer->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS);
+ check_texture_filter(inner, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS);
+ CHECK(explicit_control->get_texture_filter_in_tree() == CanvasItem::TEXTURE_FILTER_LINEAR);
+ }
+
+ memdelete(outer);
+}
+
+TEST_CASE("[SceneTree][Viewport] Window and popup texture filter inheritance") {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Viewport::DefaultCanvasItemTextureFilter original_filter = root->get_default_canvas_item_texture_filter();
+ root->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ Node *scene = memnew(Node);
+ root->add_child(scene);
+
+ Window *window = memnew(Window);
+ window->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
+ scene->add_child(window);
+ Popup *popup = memnew(Popup);
+ CHECK(popup->get_default_canvas_item_texture_filter() == Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ popup->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
+ scene->add_child(popup);
+ PopupPanel *popup_panel = memnew(PopupPanel);
+ scene->add_child(popup_panel);
+ OptionButton *option_button = memnew(OptionButton);
+ scene->add_child(option_button);
+ MenuButton *menu_button = memnew(MenuButton);
+ scene->add_child(menu_button);
+
+ Viewport *viewports[] = { window, popup, popup_panel, option_button->get_popup(), menu_button->get_popup() };
+ for (Viewport *viewport : viewports) {
+ check_texture_filter(viewport, RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+ }
+ root->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ for (Viewport *viewport : viewports) {
+ check_texture_filter(viewport, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ }
+ option_button->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST);
+ check_texture_filter(option_button->get_popup(), RSE::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
+
+ memdelete(scene);
+ root->set_default_canvas_item_texture_filter(Viewport::DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE);
+ check_texture_filter(root, RSE::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
+ root->set_default_canvas_item_texture_filter(original_filter);
+}
+
class NotificationControlViewport : public Control {
GDCLASS(NotificationControlViewport, Control);
From 056312b4728a8dc1a8dca54b07f5b287955c4522 Mon Sep 17 00:00:00 2001
From: fsfgrafb <2113147846@qq.com>
Date: Thu, 24 Sep 2026 10:38:10 +0800
Subject: [PATCH 2/3] Update scene/main/viewport.cpp
Co-authored-by: Jack Moody <74024384+radioactivejackal1414@users.noreply.github.com>
---
scene/main/viewport.cpp | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index b72bd88a214d1..f26b4e04c75f8 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -4104,11 +4104,8 @@ void Viewport::_refresh_texture_filter_cache() const {
void Viewport::_propagate_texture_filter_changed(Node *p_node) {
for (Node *child : p_node->iterate_children()) {
Viewport *child_vp = Object::cast_to(child);
- if (child_vp) {
- if (child_vp->default_canvas_item_texture_filter == DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE) {
- child_vp->_update_texture_filter_changed(true);
- }
- continue;
+ if (child_vp && child_vp->default_canvas_item_texture_filter == DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE) {
+ child_vp->_update_texture_filter_changed(false);
}
CanvasItem *child_ci = Object::cast_to(child);
From 128a9e70f5b0229a3cdfdda43612b9a332a0ff02 Mon Sep 17 00:00:00 2001
From: fsfgrafb <2113147846@qq.com>
Date: Thu, 24 Sep 2026 17:37:36 +0800
Subject: [PATCH 3/3] Clarify Viewport texture filter documentation
---
doc/classes/Viewport.xml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index f3d138a3fdb5d..3b6d416eee9a7 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -331,7 +331,6 @@
The default filter mode used by [CanvasItem] nodes in this viewport.
- The root viewport's default filter is set by [member ProjectSettings.rendering/textures/canvas_textures/default_texture_filter]. To inherit filtering from the parent node or viewport in another viewport, use [constant DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_PARENT_NODE].
The default repeat mode used by [CanvasItem] nodes in this viewport.
@@ -783,7 +782,7 @@
Use this for non-pixel art textures that may be viewed at a low scale (e.g. due to [Camera2D] zoom or sprite scaling), as mipmaps are important to smooth out pixels that are smaller than on-screen pixels.
- The [Viewport] will inherit the filter from its parent [CanvasItem]. If the parent is not a [CanvasItem], or its filter inherits the viewport default, the filter is inherited from the nearest ancestor [Viewport]. If there is no ancestor [Viewport], [constant DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR] is used.
+ The [Viewport] will inherit the filter from its parent [CanvasItem]. If the parent is not a [CanvasItem], or the parent's effective filter uses the viewport default, the filter is inherited from the nearest ancestor [Viewport].
Represents the size of the [enum DefaultCanvasItemTextureFilter] enum.