diff --git a/metadata/workarounds.xml b/metadata/workarounds.xml index 5a4c1b95f..036c245f3 100644 --- a/metadata/workarounds.xml +++ b/metadata/workarounds.xml @@ -57,6 +57,10 @@ + diff --git a/src/api/wayfire/debug.hpp b/src/api/wayfire/debug.hpp index 5bdf1ac7e..d1abe6f76 100644 --- a/src/api/wayfire/debug.hpp +++ b/src/api/wayfire/debug.hpp @@ -63,6 +63,8 @@ enum class logging_category : size_t LSHELL = 9, // Input-Method-related events IM = 10, + // Rendering-related events + RENDER = 11, TOTAL, }; diff --git a/src/api/wayfire/plugin.hpp b/src/api/wayfire/plugin.hpp index a113cb35e..c5441cf36 100644 --- a/src/api/wayfire/plugin.hpp +++ b/src/api/wayfire/plugin.hpp @@ -105,7 +105,7 @@ class plugin_interface_t using wayfire_plugin_load_func = wf::plugin_interface_t * (*)(); /** The version of Wayfire's API/ABI */ -constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2024'03'24'2; +constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2024'03'27; /** * Each plugin must also provide a function which returns the Wayfire API/ABI diff --git a/src/api/wayfire/scene.hpp b/src/api/wayfire/scene.hpp index 9ffcb0ba3..96cdd6207 100644 --- a/src/api/wayfire/scene.hpp +++ b/src/api/wayfire/scene.hpp @@ -94,6 +94,41 @@ enum class node_flags : int using node_flags_bitmask_t = uint64_t; +/** + * A list of bitmask flags which indicate what parts of the node state have + * changed. The information is useful when updating the scenegraph's state + * with wf::scene::update(). + */ +namespace update_flag +{ +enum update_flag +{ + /** + * The list of the node's children changed. + */ + CHILDREN_LIST = (1 << 0), + /** + * The node's enabled or disabled state changed. + */ + ENABLED = (1 << 1), + /** + * The node's input state changed, that is, the result of find_node_at() + * may have changed. Typically, this is triggered when a surface is mapped, + * unmapped or moved. + */ + INPUT_STATE = (1 << 2), + /** + * The node's geometry changed. Changes include not just the bounding box + * of the view, but also things like opaque regions. + */ + GEOMETRY = (1 << 3), + /** + * A keyboard refocus might be necessary (for example, node removed, keyboard input state changed, etc.). + */ + REFOCUS = (1 << 4), +}; +} + /** * Used as a result of an intersection of the scenegraph with the user input. */ @@ -298,6 +333,22 @@ class node_t : public std::enable_shared_from_this, return children; } + /** + * When a scenegraph change happens, core or the plugin which modifies the scenegraph is supposed to call + * the @scene::update() function defined below, so that the scene graph can be updated properly, render + * instances regenerated, etc. + * + * However, in many cases a full update is not necessary. For example, when subsurfaces are being + * reordered, locally regenerating the render instances within the view render instances is enough. + * For such cases, nodes can override this function and change the information which is propagated for + * the update to their parent nodes. In the above example of subsurface reordering, the subsurface root + * will update all of its render instances manually and not propagate CHILDREN_LIST updates to its parent. + */ + virtual uint32_t optimize_update(uint32_t update_flags) + { + return update_flags; + } + public: node_t(const node_t&) = delete; node_t(node_t&&) = delete; @@ -421,41 +472,6 @@ enum class layer : size_t ALL_LAYERS, }; -/** - * A list of bitmask flags which indicate what parts of the node state have - * changed. The information is useful when updating the scenegraph's state - * with wf::scene::update(). - */ -namespace update_flag -{ -enum update_flag -{ - /** - * The list of the node's children changed. - */ - CHILDREN_LIST = (1 << 0), - /** - * The node's enabled or disabled state changed. - */ - ENABLED = (1 << 1), - /** - * The node's input state changed, that is, the result of find_node_at() - * may have changed. Typically, this is triggered when a surface is mapped, - * unmapped or moved. - */ - INPUT_STATE = (1 << 2), - /** - * The node's geometry changed. Changes include not just the bounding box - * of the view, but also things like opaque regions. - */ - GEOMETRY = (1 << 3), - /** - * A keyboard refocus might be necessary (for example, node removed, keyboard input state changed, etc.). - */ - REFOCUS = (1 << 4), -}; -} - /** * A signal that the root node has been updated. * diff --git a/src/api/wayfire/unstable/translation-node.hpp b/src/api/wayfire/unstable/translation-node.hpp index 6c1e8ec97..1e1a86524 100644 --- a/src/api/wayfire/unstable/translation-node.hpp +++ b/src/api/wayfire/unstable/translation-node.hpp @@ -7,6 +7,14 @@ namespace wf { namespace scene { +/** + * Emitted on: translation node + * The signal means that the translation node wishes to optimize a scenegraph update, and render instances + * should update their internal state to match. + */ +struct translation_node_regen_instances_signal +{}; + /** * A node which simply applies an offset to its children. */ @@ -34,6 +42,7 @@ class translation_node_t : public wf::scene::floating_inner_node_t void gen_render_instances(std::vector& instances, scene::damage_callback damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; + uint32_t optimize_update(uint32_t flags) override; protected: wf::point_t offset = {0, 0}; @@ -46,6 +55,9 @@ class translation_node_instance_t : public render_instance_t damage_callback push_damage; translation_node_t *self; wf::signal::connection_t on_node_damage; + wf::signal::connection_t on_regen_instances; + wf::output_t *shown_on; + void regen_instances(); public: translation_node_instance_t(translation_node_t *self, diff --git a/src/api/wayfire/unstable/wlr-subsurface-controller.hpp b/src/api/wayfire/unstable/wlr-subsurface-controller.hpp index 462043e76..e2ba05efd 100644 --- a/src/api/wayfire/unstable/wlr-subsurface-controller.hpp +++ b/src/api/wayfire/unstable/wlr-subsurface-controller.hpp @@ -16,7 +16,7 @@ class wlr_subsurface_root_node_t : public wf::scene::translation_node_t public: wlr_subsurface_root_node_t(wlr_subsurface *subsurface); std::string stringify() const override; - void update_offset(); + bool update_offset(bool damage = true); private: wlr_subsurface *subsurface; diff --git a/src/api/wayfire/unstable/wlr-surface-controller.hpp b/src/api/wayfire/unstable/wlr-surface-controller.hpp index 046aa5b43..a3b226384 100644 --- a/src/api/wayfire/unstable/wlr-surface-controller.hpp +++ b/src/api/wayfire/unstable/wlr-surface-controller.hpp @@ -22,6 +22,8 @@ class wlr_surface_controller_t wlr_surface_controller_t(wlr_surface *surface, scene::floating_inner_ptr root_node); ~wlr_surface_controller_t(); + void update_subsurface_order_and_position(); + scene::floating_inner_ptr root; wlr_surface *surface; diff --git a/src/api/wayfire/unstable/wlr-surface-node.hpp b/src/api/wayfire/unstable/wlr-surface-node.hpp index bcaafc357..af65f9424 100644 --- a/src/api/wayfire/unstable/wlr-surface-node.hpp +++ b/src/api/wayfire/unstable/wlr-surface-node.hpp @@ -1,6 +1,7 @@ #pragma once #include "wayfire/geometry.hpp" +#include "wayfire/signal-definitions.hpp" #include "wayfire/util.hpp" #include "wayfire/view-transform.hpp" #include @@ -72,8 +73,17 @@ class wlr_surface_node_t : public node_t, public zero_copy_texturable_node_t std::unique_ptr ptr_interaction; std::unique_ptr tch_interaction; wlr_surface *surface; + std::map visibility; + std::map pending_visibility_delta; + wf::signal::connection_t on_output_remove; + class wlr_surface_render_instance_t; + void handle_enter(wf::output_t *output); + void handle_leave(wf::output_t *output); + void update_pending_outputs(); + wf::wl_idle_call idle_update_outputs; + wf::wl_listener_wrapper on_surface_destroyed; wf::wl_listener_wrapper on_surface_commit; diff --git a/src/core/scene.cpp b/src/core/scene.cpp index 3f935e229..fb5b6922e 100644 --- a/src/core/scene.cpp +++ b/src/core/scene.cpp @@ -3,18 +3,15 @@ #include #include #include -#include #include #include "scene-priv.hpp" -#include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/signal-provider.hpp" -#include "wayfire/util.hpp" #include namespace wf @@ -503,6 +500,7 @@ void update(node_ptr changed_node, uint32_t flags) if (changed_node->parent()) { + flags = changed_node->parent()->optimize_update(flags); update(changed_node->parent()->shared_from_this(), flags); } } diff --git a/src/main.cpp b/src/main.cpp index 6a764f121..1bfe8e56e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -227,6 +227,10 @@ void parse_extended_debugging(const std::vector& categories) { LOGD("Enabling extended debugging for input method events"); wf::log::enabled_categories.set((size_t)wf::log::logging_category::IM, 1); + } else if (cat == "render") + { + LOGD("Enabling extended debugging for render events"); + wf::log::enabled_categories.set((size_t)wf::log::logging_category::RENDER, 1); } else { LOGE("Unrecognized debugging category \"", cat, "\""); diff --git a/src/output/render-manager.cpp b/src/output/render-manager.cpp index b2a19e013..8c52da6b3 100644 --- a/src/output/render-manager.cpp +++ b/src/output/render-manager.cpp @@ -42,6 +42,7 @@ struct swapchain_damage_manager_t output_t *wo; bool pending_gamma_lut = false; + wf::wl_idle_call idle_recompute_visibility; void update_scenegraph(uint32_t update_mask) { @@ -51,6 +52,7 @@ struct swapchain_damage_manager_t if (update_mask & recompute_instances_on) { + LOGC(RENDER, "Output ", wo->to_string(), ": regenerating instances."); auto root = wf::get_core().scene(); scene::damage_callback push_damage = [=] (wf::region_t region) { @@ -66,11 +68,15 @@ struct swapchain_damage_manager_t if (update_mask & recompute_visibility_on) { - wf::region_t region = this->wo->get_layout_geometry(); - for (auto& inst : render_instances) + idle_recompute_visibility.run_once([=] () { - inst->compute_visibility(wo, region); - } + LOGC(RENDER, "Output ", wo->to_string(), ": recomputing visibility."); + wf::region_t region = this->wo->get_layout_geometry(); + for (auto& inst : render_instances) + { + inst->compute_visibility(wo, region); + } + }); } } diff --git a/src/view/translation-node.cpp b/src/view/translation-node.cpp index 44cfdd78b..e20e0f22d 100644 --- a/src/view/translation-node.cpp +++ b/src/view/translation-node.cpp @@ -46,12 +46,29 @@ void wf::scene::translation_node_t::set_offset(wf::point_t offset) this->offset = offset; } +uint32_t wf::scene::translation_node_t::optimize_update(uint32_t flags) +{ + if (flags & (update_flag::CHILDREN_LIST | update_flag::ENABLED)) + { + // If we update the list of children, there is no need to notify the whole scenegraph. + // Instead, we can do a local update, and only update visibility. + flags &= ~update_flag::CHILDREN_LIST; + flags &= ~update_flag::ENABLED; + flags |= update_flag::GEOMETRY | update_flag::INPUT_STATE; + translation_node_regen_instances_signal data; + emit(&data); + } + + return flags; +} + // ----------------------------------------- Render instance ------------------------------------------------- wf::scene::translation_node_instance_t::translation_node_instance_t( translation_node_t *self, damage_callback push_damage, wf::output_t *shown_on) { this->self = self; this->push_damage = push_damage; + this->shown_on = shown_on; on_node_damage = [=] (wf::scene::node_damage_signal *data) { @@ -59,6 +76,17 @@ wf::scene::translation_node_instance_t::translation_node_instance_t( }; self->connect(&on_node_damage); + on_regen_instances = [=] (auto) + { + regen_instances(); + }; + self->connect(&on_regen_instances); + regen_instances(); +} + +void wf::scene::translation_node_instance_t::regen_instances() +{ + children.clear(); auto push_damage_child = [=] (wf::region_t child_damage) { child_damage += self->get_offset(); diff --git a/src/view/view-impl.cpp b/src/view/view-impl.cpp index 86062850f..6979255db 100644 --- a/src/view/view-impl.cpp +++ b/src/view/view-impl.cpp @@ -1,5 +1,4 @@ #include "wayfire/core.hpp" -#include "../core/core-impl.hpp" #include "view-impl.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-render.hpp" @@ -8,15 +7,12 @@ #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/view.hpp" -#include "wayfire/workspace-set.hpp" #include "wayfire/output-layout.hpp" #include #include #include #include -#include "xdg-shell.hpp" - void wf::view_implementation::emit_view_map_signal(wayfire_view view, bool has_position) { wf::view_mapped_signal data; diff --git a/src/view/wlr-subsurface-controller.cpp b/src/view/wlr-subsurface-controller.cpp index 120884a5b..b5ba23b24 100644 --- a/src/view/wlr-subsurface-controller.cpp +++ b/src/view/wlr-subsurface-controller.cpp @@ -1,14 +1,10 @@ -#include "view/view-impl.hpp" #include "wayfire/geometry.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" -#include "wayfire/signal-definitions.hpp" -#include "wayfire/unstable/translation-node.hpp" #include "wayfire/unstable/wlr-subsurface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include #include -#include wf::wlr_subsurface_controller_t::wlr_subsurface_controller_t(wlr_subsurface *sub) { @@ -81,13 +77,20 @@ std::string wf::wlr_subsurface_root_node_t::stringify() const return "subsurface root node"; } -void wf::wlr_subsurface_root_node_t::update_offset() +bool wf::wlr_subsurface_root_node_t::update_offset(bool apply_damage) { wf::point_t offset = {subsurface->current.x, subsurface->current.y}; - if (offset != get_offset()) + const bool changed = offset != get_offset(); + + if (changed && apply_damage) { scene::damage_node(this, get_bounding_box()); set_offset(offset); scene::damage_node(this, get_bounding_box()); + } else if (changed) + { + set_offset(offset); } + + return changed; } diff --git a/src/view/wlr-surface-controller.cpp b/src/view/wlr-surface-controller.cpp index 44e2de753..ec4744129 100644 --- a/src/view/wlr-surface-controller.cpp +++ b/src/view/wlr-surface-controller.cpp @@ -1,16 +1,9 @@ -#include -#include #include -#include "wayfire/geometry.hpp" -#include "wayfire/opengl.hpp" -#include "../core/core-impl.hpp" -#include "wayfire/output.hpp" #include -#include "wayfire/render-manager.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" -#include "wayfire/signal-definitions.hpp" #include "wayfire/unstable/wlr-surface-controller.hpp" +#include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/unstable/wlr-subsurface-controller.hpp" #include @@ -20,7 +13,8 @@ static void update_subsurface_position(wlr_surface *surface, int, int, void*) { if (sub->data) { - ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root()->update_offset(); + auto sub_root = ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root(); + sub_root->update_offset(); } } } @@ -72,15 +66,17 @@ wf::wlr_surface_controller_t::wlr_surface_controller_t(wlr_surface *surface, on_new_subsurface.emit(sub); } - if (!wlr_subsurface_try_from_wlr_surface(surface)) + on_commit.set_callback([=] (void*) { - on_commit.set_callback([=] (void*) + if (!wlr_subsurface_try_from_wlr_surface(surface)) { wlr_surface_for_each_surface(surface, update_subsurface_position, nullptr); - }); + } - on_commit.connect(&surface->events.commit); - } + update_subsurface_order_and_position(); + }); + + on_commit.connect(&surface->events.commit); } wf::wlr_surface_controller_t::~wlr_surface_controller_t() @@ -101,3 +97,81 @@ void wf::wlr_surface_controller_t::try_free_controller(wlr_surface *surface) delete (wlr_surface_controller_t*)surface->data; } } + +void wf::wlr_surface_controller_t::update_subsurface_order_and_position() +{ + auto old_bbox = root->get_bounding_box(); + + // Calculate whether we need to reorder the surfaces. + auto all_subsurfaces = this->root->get_children(); + + // Go until we find the main node, it should be a wlr_surface node. + auto it = std::find_if(all_subsurfaces.begin(), all_subsurfaces.end(), + [=] (const scene::node_ptr& node) + { + if (auto wlr_node = dynamic_cast(node.get())) + { + return wlr_node->get_surface() == surface; + } + + return false; + }); + + if (it == all_subsurfaces.end()) + { + // Maybe unmapped? Can't do anything at the moment anyway. + return; + } + + // Now, put all subsurfaces above in the right order, then main view, then subsurfaces below. + // For nodes that we have not copied, we put them at the start/end depending on their original + // position. + std::vector new_subsurface_order; + + // Update subsurface order + bool subsurface_repositioned = false; + + wlr_subsurface *sub; + wl_list_for_each_reverse(sub, &surface->current.subsurfaces_above, current.link) + { + auto sub_root = ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root(); + new_subsurface_order.push_back(sub_root); + subsurface_repositioned |= sub_root->update_offset(false); + } + new_subsurface_order.push_back(*it); + wl_list_for_each_reverse(sub, &surface->current.subsurfaces_below, current.link) + { + auto sub_root = ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root(); + new_subsurface_order.push_back(sub_root); + subsurface_repositioned |= sub_root->update_offset(false); + } + + // Place compositor subsurfaces correctly: either on top or below. + for (auto iter = all_subsurfaces.begin(); iter != all_subsurfaces.end(); ++iter) + { + if (!dynamic_cast(iter->get()) && + !dynamic_cast(iter->get())) + { + if (iter < it) + { + new_subsurface_order.insert(new_subsurface_order.begin(), *iter); + } else + { + new_subsurface_order.push_back(*iter); + } + } + } + + const bool order_changed = new_subsurface_order != all_subsurfaces; + if (order_changed || subsurface_repositioned) + { + wf::scene::damage_node(root, old_bbox); + if (order_changed) + { + root->set_children_list(new_subsurface_order); + wf::scene::update(root, wf::scene::update_flag::CHILDREN_LIST); + } + + wf::scene::damage_node(root, root->get_bounding_box()); + } +} diff --git a/src/view/wlr-surface-node.cpp b/src/view/wlr-surface-node.cpp index 2b6caf16f..731980964 100644 --- a/src/view/wlr-surface-node.cpp +++ b/src/view/wlr-surface-node.cpp @@ -6,6 +6,7 @@ #include "wayfire/scene.hpp" #include "wlr-surface-pointer-interaction.hpp" #include "wlr-surface-touch-interaction.cpp" +#include "wayfire/output-layout.hpp" #include #include #include @@ -131,6 +132,13 @@ wf::scene::wlr_surface_node_t::wlr_surface_node_t(wlr_surface *surface, bool aut send_frame_done(false); current_state.merge_state(surface); + + on_output_remove.set_callback([&] (wf::output_removed_signal *ev) + { + visibility.erase(ev->output); + pending_visibility_delta.erase(ev->output); + }); + wf::get_core().output_layout->connect(&on_output_remove); } void wf::scene::wlr_surface_node_t::apply_state(surface_state_t&& state) @@ -226,6 +234,7 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend wf::output_t *visible_on; damage_callback push_damage; + wf::region_t last_visibility; wf::signal::connection_t on_surface_damage = [=] (node_damage_signal *data) @@ -241,7 +250,17 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend } } - push_damage(data->region); + static wf::option_wrapper_t use_opaque_optimizations{ + "workarounds/enable_opaque_region_damage_optimizations" + }; + + if (use_opaque_optimizations) + { + push_damage(data->region & last_visibility); + } else + { + push_damage(data->region); + } }; public: @@ -250,12 +269,7 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend { if (visible_on) { - self->visibility[visible_on]++; - if (self->surface) - { - wlr_surface_send_enter(self->surface, visible_on->handle); - wlr_fractional_scale_v1_notify_scale(self->surface, visible_on->handle->scale); - } + self->handle_enter(visible_on); } this->self = self; @@ -268,12 +282,7 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend { if (visible_on) { - self->visibility[visible_on]--; - if ((self->visibility[visible_on] == 0) && self->surface) - { - self->visibility.erase(visible_on); - wlr_surface_send_leave(self->surface, visible_on->handle); - } + self->handle_leave(visible_on); } } @@ -402,13 +411,23 @@ class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public rend { auto our_box = self->get_bounding_box(); on_frame_done.disconnect(); + last_visibility = visible & our_box; - if (!(visible & our_box).empty()) + static wf::option_wrapper_t use_opaque_optimizations{ + "workarounds/enable_opaque_region_damage_optimizations" + }; + + if (!last_visibility.empty()) { // We are visible on the given output => send wl_surface.frame on output frame, so that clients // can draw the next frame. output->connect(&on_frame_done); - // TODO: compute actually visible region and disable damage reporting for that region. + + if (use_opaque_optimizations && self->surface) + { + pixman_region32_subtract(visible.to_pixman(), visible.to_pixman(), + &self->surface->opaque_region); + } } } }; @@ -433,10 +452,50 @@ wlr_surface*wf::scene::wlr_surface_node_t::get_surface() const std::optional wf::scene::wlr_surface_node_t::to_texture() const { - if (this->current_state.current_buffer) + if (this->current_state.current_buffer && (this->current_state.transform == WL_OUTPUT_TRANSFORM_NORMAL)) { return wf::texture_t{current_state.texture, current_state.src_viewport}; } return {}; } + +// Idea of handling output enter/leave events: when the event comes, we store the number of enters/leaves +// for outputs and update them on the next idle. The idea is to cache together multiple events, which may +// be triggered especially when visibility recomputation happens. +void wf::scene::wlr_surface_node_t::handle_enter(wf::output_t *output) +{ + pending_visibility_delta[output]++; + idle_update_outputs.run_once([&] () { update_pending_outputs(); }); +} + +void wf::scene::wlr_surface_node_t::handle_leave(wf::output_t *output) +{ + pending_visibility_delta[output]--; + idle_update_outputs.run_once([&] () { update_pending_outputs(); }); +} + +void wf::scene::wlr_surface_node_t::update_pending_outputs() +{ + for (auto& [wo, delta] : pending_visibility_delta) + { + if ((visibility[wo] == 0) && (delta > 0) && surface) + { + wlr_surface_send_enter(surface, wo->handle); + wlr_fractional_scale_v1_notify_scale(surface, wo->handle->scale); + } + + visibility[wo] += delta; + if ((visibility[wo] == 0) && (delta < 0) && surface) + { + wlr_surface_send_leave(surface, wo->handle); + } + + if (visibility[wo] == 0) + { + visibility.erase(wo); + } + } + + pending_visibility_delta.clear(); +}