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);