diff --git a/README.md b/README.md index 7cddb47..2bdf8b2 100644 --- a/README.md +++ b/README.md @@ -203,3 +203,19 @@ stays. No dependency on the shell is introduced. `wayfire/exit` schedules shutdown of this compositor after its response, with core/exit's inhibited-output guard. Config monitoring follows atomically replaced symlink targets again. + +## 0.11.0-r79 (Hedron Sprint 3 follow-up) + +Closing an overlay no longer starts a snapshot render pass after auxiliary +buffer allocation fails. The animate plugin tears down the closing animation +and transformer, including when close interrupts an opening animation. Failed +snapshots generate no draw instances; a missing texture is skipped safely. +The public take_snapshot signature and compositor/plugin ABI are unchanged. + +A test-only allocator shim reproduces the old fatal assertion on installed r77. +The new build survives four forced snapshot failures and then reopens/closes the +menu normally. See ../../logs/s4/snapshot-final.log and ../../tools/snapshot-close-probe.py. +The valid wlroots software mode (WLR_RENDERER_FORCE_SOFTWARE=1 with udmabuf access) +still has a separate first-frame crash on this host; full software/VM acceptance +remains pending. Do not use MESA_LOADER_DRIVER_OVERRIDE=kms_swrast on a DRM render +node as an acceptance setup: it cannot allocate the required dumb buffers here. diff --git a/plugins/animate/animate.cpp b/plugins/animate/animate.cpp index a5b38fa..6da9aa7 100644 --- a/plugins/animate/animate.cpp +++ b/plugins/animate/animate.cpp @@ -424,12 +424,27 @@ class wayfire_animation : public wf::plugin_interface_t, private wf::per_output_ auto& effect = effects_registry->effects[animation_name]; if (view->has_data(cdata_name)) { - view->get_data(cdata_name)->set_animation_type(type); + auto hook = view->get_data(cdata_name); + hook->set_animation_type(type); + if ((type == wf::animate::ANIMATION_TYPE_UNMAP) && + !hook->unmapped_contents->has_snapshot()) + { + view->erase_data(cdata_name); + } + return; } auto hook = std::make_unique(view, effect.generator(), duration, type, cdata_name, animation_name); + if ((type == wf::animate::ANIMATION_TYPE_UNMAP) && + !hook->unmapped_contents->has_snapshot()) + { + // Tear down the transformer and render hook together. Keeping an + // animation with empty contents would require another aux buffer. + return; + } + used_hook_names.insert(cdata_name); view->store_data(std::move(hook), cdata_name); } diff --git a/plugins/animate/unmapped-view-node.hpp b/plugins/animate/unmapped-view-node.hpp index 3ae9968..8330648 100644 --- a/plugins/animate/unmapped-view-node.hpp +++ b/plugins/animate/unmapped-view-node.hpp @@ -21,8 +21,18 @@ class unmapped_view_snapshot_node : public wf::scene::node_t _view = view->weak_from_this(); } + bool has_snapshot() const + { + return snapshot.get_buffer() != nullptr; + } + wf::geometry_t get_bounding_box() override { + if (!snapshot.get_buffer()) + { + return {0, 0, 0, 0}; + } + if (auto view = _view.lock()) { auto current_bbox = view->get_surface_root_node()->get_bounding_box(); @@ -35,6 +45,11 @@ class unmapped_view_snapshot_node : public wf::scene::node_t void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { + if (!snapshot.get_buffer()) + { + return; + } + instances.push_back(std::make_unique(this, push_damage, shown_on)); } @@ -50,6 +65,11 @@ class unmapped_view_snapshot_node : public wf::scene::node_t using simple_render_instance_t::simple_render_instance_t; void render(const wf::scene::render_instruction_t& data) { + if (!self->snapshot.get_texture()) + { + return; + } + auto texture = wf::texture_t::from_aux(self->snapshot); data.pass->add_texture(texture, data.target, self->get_bounding_box(), data.damage); } diff --git a/src/api/wayfire/view.hpp b/src/api/wayfire/view.hpp index b70f6c5..a813ad1 100644 --- a/src/api/wayfire/view.hpp +++ b/src/api/wayfire/view.hpp @@ -179,6 +179,7 @@ class view_interface_t : public wf::signal::provider_t, public wf::object_base_t /** * A snapshot of the view is a copy of the view's contents into a framebuffer. + * If allocation fails, the buffer is empty and no render pass is started. */ virtual void take_snapshot(wf::auxilliary_buffer_t& buffer); diff --git a/src/view/view.cpp b/src/view/view.cpp index daa50db..41c7e5a 100644 --- a/src/view/view.cpp +++ b/src/view/view.cpp @@ -96,8 +96,14 @@ void wf::view_interface_t::take_snapshot(wf::auxilliary_buffer_t& buffer) auto root_node = get_surface_root_node(); const wf::geometry_t bbox = root_node->get_bounding_box(); float scale = get_output()->handle->scale; - buffer.allocate(wf::dimensions(bbox), scale, - wf::buffer_allocation_hints_t{.hdr_linear = get_output() && get_output()->is_hdr()}); + if (buffer.allocate(wf::dimensions(bbox), scale, + wf::buffer_allocation_hints_t{.hdr_linear = get_output() && get_output()->is_hdr()}) == + wf::buffer_reallocation_result_t::FAILED) + { + // A closing view may disappear without an animation if memory or the + // allocator is unavailable. A render pass requires a valid buffer. + return; + } wf::render_target_t target{buffer}; target.geometry = bbox;