diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index bafca16d649b..3b6d416eee9a 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -782,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] or [Viewport].
+ 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.
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index a50214c9c56e..f26b4e04c75f 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,23 @@ 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 && 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);
+ 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 +4126,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 741277ec5b75..c61c828135d7 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 7ad2ceaccf82..ede739f86100 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);