From 77f856714d5ebcd637461d0547f142239c987ebf Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Tue, 18 Aug 2026 12:15:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=9C=8D=E5=8A=A1=E5=8D=8F?= =?UTF-8?q?=E7=A8=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/renderive/scene/base/Scene_Base.cpp | 16 ++++++- .../src/renderive/scene/base/Scene_Base.hpp | 2 + .../src/renderive/scene/base/Scene_Base.inl | 3 +- .../frame_control/Plot_Frame_Control.cpp | 7 ++- .../frame_control/Plot_Frame_Control.h | 1 + render_2D/render_2D/scene/Render_Scene_2D.cpp | 5 +- render_2D/render_2D/scene/Render_Scene_2D.h | 1 + render_3D/render_3D/Render_Scene_3D.cpp | 5 +- render_3D/render_3D/Render_Scene_3D.h | 1 + .../frame_control/Point_Frame_Control.cpp | 7 ++- .../frame_control/Point_Frame_Control.h | 1 + web_server/app/Asio_WebSocket_Scene_Loop.h | 14 ++++-- web_server/app/Gallery_Plot_Session.cpp | 45 ++++++++++++++++++ web_server/app/Gallery_Plot_Session.h | 2 +- web_server/app/Web_Plot_Session.cpp | 46 +++++++++++++++---- web_server/app/Web_Plot_Session.h | 10 +++- .../app/common/Gallery_Scene_Interface.h | 2 + web_server/app/render_2D/Gallery_Scene2D.cpp | 11 ++++- web_server/app/render_3D/Gallery_Scene3D.cpp | 10 +++- 19 files changed, 164 insertions(+), 25 deletions(-) diff --git a/Kernel/src/renderive/scene/base/Scene_Base.cpp b/Kernel/src/renderive/scene/base/Scene_Base.cpp index 7ac5acf..c705789 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.cpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.cpp @@ -762,7 +762,10 @@ Scene_Render_Result Scene_Base::render() { Scene_Render_Result Scene_Base::render(Abstract_Frame& frame) { return d_func().submit_render(&frame); } -Scene_Render_Result Scene_Base::Impl::submit_render(Abstract_Frame* frame) { +Scene_Render_Result Scene_Base::render(Abstract_Frame& frame, Scene_Render_Completion completion) { + return d_func().submit_render(&frame, std::move(completion)); +} +Scene_Render_Result Scene_Base::Impl::submit_render(Abstract_Frame* frame, Scene_Render_Completion completion) { if (active_submitted_observer_scene_ == owner_) { std::lock_guard lock(task_mutex_); ++deferred_render_count_; @@ -802,6 +805,7 @@ Scene_Render_Result Scene_Base::Impl::submit_render(Abstract_Frame* frame) { task->topology = std::make_shared(scene().topology_snapshot()); task->completion = std::make_shared(); + task->completion_callback = std::move(completion); const Capture_Frame_Ticket capture_ticket = capture_controller_.begin_frame(); snapshot->capture_ticket_ = capture_ticket; task->snapshot = std::move(snapshot); @@ -1526,6 +1530,16 @@ void Scene_Base::Impl::execute_render_task(std::shared_ptr task) { << " pending_operations=" << pending_operations; }); render_completed_.notify_all(); + if (task->completion_callback) { + try { + task->completion_callback(error, exception); + } + catch (...) { + record_pending_exception(::renderive::error::capture( + "executing scene render completion callback", + std::current_exception())); + } + } } std::shared_ptr Scene_Base::Impl::compile_render_plan( const Frame_Render_Snapshot& snapshot) { diff --git a/Kernel/src/renderive/scene/base/Scene_Base.hpp b/Kernel/src/renderive/scene/base/Scene_Base.hpp index f89d88c..4575976 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.hpp +++ b/Kernel/src/renderive/scene/base/Scene_Base.hpp @@ -48,6 +48,7 @@ enum class Scene_Render_Result : std::uint8_t { external_failure, shutting_down }; +using Scene_Render_Completion = std::function; enum class Scene_Edit_Error : std::uint8_t { none, empty_edit, @@ -160,6 +161,7 @@ public: }; Scene_Render_Result render(); Scene_Render_Result render(Abstract_Frame& frame); + Scene_Render_Result render(Abstract_Frame& frame, Scene_Render_Completion completion); Scene_Render_Result wait_for_render(); void publish_frame_state(); void publish_frame_state(Abstract_Frame& frame); diff --git a/Kernel/src/renderive/scene/base/Scene_Base.inl b/Kernel/src/renderive/scene/base/Scene_Base.inl index 37bf647..5b022d7 100644 --- a/Kernel/src/renderive/scene/base/Scene_Base.inl +++ b/Kernel/src/renderive/scene/base/Scene_Base.inl @@ -75,6 +75,7 @@ public: std::shared_ptr topology; std::shared_ptr frame; std::shared_ptr completion; + Scene_Render_Completion completion_callback; }; class Execution_Context; class Render_Execution_Scope; @@ -95,7 +96,7 @@ public: [[nodiscard]] const Composition_Relationships* composition() const noexcept; [[nodiscard]] Raster_Capabilities* raster() noexcept; [[nodiscard]] const Raster_Capabilities* raster() const noexcept; - [[nodiscard]] Scene_Render_Result submit_render(Abstract_Frame* frame); + [[nodiscard]] Scene_Render_Result submit_render(Abstract_Frame* frame, Scene_Render_Completion completion = {}); void complete_pending_operation(); void record_pending_exception(std::exception_ptr exception); void capture_edit_renderable(const Renderable& renderable); diff --git a/render_2D/render_2D/frame_control/Plot_Frame_Control.cpp b/render_2D/render_2D/frame_control/Plot_Frame_Control.cpp index cadd93c..232e8b7 100644 --- a/render_2D/render_2D/frame_control/Plot_Frame_Control.cpp +++ b/render_2D/render_2D/frame_control/Plot_Frame_Control.cpp @@ -47,6 +47,9 @@ std::shared_ptr Plot_Frame_Control::runtime() return impl_->strategy; } auto Plot_Frame_Control::request(Render_Scene_2D& scene) -> Expected { + return request(scene, {}); +} +auto Plot_Frame_Control::request(Render_Scene_2D& scene, Scene_Render_Completion completion) -> Expected { const bool prepared = visit(*impl_->strategy, [&scene](auto& strategy) { auto frame = strategy.acquire_painter(); if (!frame) return false; @@ -58,13 +61,13 @@ auto Plot_Frame_Control::request(Render_Scene_2D& scene) -> Expected(impl_->strategy.get())) { if (strategy->refresh() != Manual_Refresh_Result::none) return unexpected(Plot_Render_Frame_Error::no_pending_frame); } - return visit(*impl_->strategy, [&scene](auto& strategy) + return visit(*impl_->strategy, [&scene, completion = std::move(completion)](auto& strategy) mutable -> Expected { auto frame = strategy.acquire_renderer(); if (!frame) return unexpected( Plot_Render_Frame_Error::renderer_unavailable); - const auto result = scene.Scene_Base::render(*frame); + const auto result = scene.Scene_Base::render(*frame, std::move(completion)); if (result == Scene_Render_Result::none) return {}; const auto converted = adapter::enum_cast(result); diff --git a/render_2D/render_2D/frame_control/Plot_Frame_Control.h b/render_2D/render_2D/frame_control/Plot_Frame_Control.h index 4c05d95..4c398f5 100644 --- a/render_2D/render_2D/frame_control/Plot_Frame_Control.h +++ b/render_2D/render_2D/frame_control/Plot_Frame_Control.h @@ -14,6 +14,7 @@ public: [[nodiscard]] std::shared_ptr runtime(); [[nodiscard]] std::shared_ptr runtime() const; [[nodiscard]] auto request(Render_Scene_2D& scene) -> Expected; + [[nodiscard]] auto request(Render_Scene_2D& scene, Scene_Render_Completion completion) -> Expected; void replace(std::shared_ptr strategy); private: struct Impl; diff --git a/render_2D/render_2D/scene/Render_Scene_2D.cpp b/render_2D/render_2D/scene/Render_Scene_2D.cpp index ef15936..69a6f10 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.cpp +++ b/render_2D/render_2D/scene/Render_Scene_2D.cpp @@ -148,12 +148,15 @@ void detail::Render_Scene_2D::dispatch_event(const Event& event) { } } auto detail::Render_Scene_2D::render_frame() -> Expected { + return render_frame({}); +} +auto detail::Render_Scene_2D::render_frame(Scene_Render_Completion completion) -> Expected { if (!view_active()) return Plot_Render_Status::view_inactive; const Size viewport = get<&State::viewport>(); if (viewport.empty()) return Plot_Render_Status::empty_viewport; const auto started = std::chrono::steady_clock::now(); try { - if (const auto result = d_func().frame_control->request(*this); !result) return unexpected(result.error()); + if (const auto result = d_func().frame_control->request(*this, std::move(completion)); !result) return unexpected(result.error()); } catch (...) { ::renderive::error::unexpected( diff --git a/render_2D/render_2D/scene/Render_Scene_2D.h b/render_2D/render_2D/scene/Render_Scene_2D.h index c69dec6..57346a0 100644 --- a/render_2D/render_2D/scene/Render_Scene_2D.h +++ b/render_2D/render_2D/scene/Render_Scene_2D.h @@ -174,6 +174,7 @@ public: [[nodiscard]] ::renderive_Owner<::renderive::Renderable> root_renderable() const; void dispatch_event(const Event& event); [[nodiscard]] auto render_frame() -> Expected; + [[nodiscard]] auto render_frame(Scene_Render_Completion completion) -> Expected; void with_frame(const std::function& consumer); void activate_view(); void deactivate_view() noexcept; diff --git a/render_3D/render_3D/Render_Scene_3D.cpp b/render_3D/render_3D/Render_Scene_3D.cpp index 4f1d8f3..ee0cf1c 100644 --- a/render_3D/render_3D/Render_Scene_3D.cpp +++ b/render_3D/render_3D/Render_Scene_3D.cpp @@ -507,7 +507,7 @@ Scene_Control_Error detail::Render_Scene_3D::dispatch( return Scene_Control_Error::none; } Frame_Request_Result detail::Render_Scene_3D::request_frame() { - const auto requested = d_func().frame_control->request(*this); + const auto requested = request_frame({}); if (requested != Frame_Request_Result::none) return requested; switch (wait_for_render()) { case Scene_Render_Result::none: return Frame_Request_Result::none; @@ -521,6 +521,9 @@ Frame_Request_Result detail::Render_Scene_3D::request_frame() { } return Frame_Request_Result::external_failure; } +Frame_Request_Result detail::Render_Scene_3D::request_frame(Scene_Render_Completion completion) { + return d_func().frame_control->request(*this, std::move(completion)); +} std::shared_ptr detail::Render_Scene_3D::latest_frame() const { return d_func().latest_frame(); } diff --git a/render_3D/render_3D/Render_Scene_3D.h b/render_3D/render_3D/Render_Scene_3D.h index a562049..e543aa5 100644 --- a/render_3D/render_3D/Render_Scene_3D.h +++ b/render_3D/render_3D/Render_Scene_3D.h @@ -149,6 +149,7 @@ public: [[nodiscard]] Scene_Control_Error dispatch( const ::renderive::Event& event); [[nodiscard]] Frame_Request_Result request_frame(); + [[nodiscard]] Frame_Request_Result request_frame(Scene_Render_Completion completion); [[nodiscard]] std::shared_ptr latest_frame() const; ~Render_Scene_3D() override; private: diff --git a/render_3D/render_3D/frame_control/Point_Frame_Control.cpp b/render_3D/render_3D/frame_control/Point_Frame_Control.cpp index 9672c8e..8a10127 100644 --- a/render_3D/render_3D/frame_control/Point_Frame_Control.cpp +++ b/render_3D/render_3D/frame_control/Point_Frame_Control.cpp @@ -45,6 +45,9 @@ std::shared_ptr Point_Frame_Control::runtime( return impl_->strategy; } Frame_Request_Result Point_Frame_Control::request(Render_Scene_3D& scene) { + return request(scene, {}); +} +Frame_Request_Result Point_Frame_Control::request(Render_Scene_3D& scene, Scene_Render_Completion completion) { const auto prepared = visit(*impl_->strategy, [&scene](auto& strategy) { auto frame = strategy.acquire_painter(); if (!frame) return false; @@ -53,10 +56,10 @@ Frame_Request_Result Point_Frame_Control::request(Render_Scene_3D& scene) { }); if (!prepared) return Frame_Request_Result::painter_unavailable; if (auto* strategy = dynamic_cast(impl_->strategy.get()); strategy && strategy->refresh() != Manual_Refresh_Result::none) return Frame_Request_Result::no_pending_frame; - return visit(*impl_->strategy, [&scene](auto& strategy) { + return visit(*impl_->strategy, [&scene, completion = std::move(completion)](auto& strategy) mutable { auto frame = strategy.acquire_renderer(); if (!frame) return Frame_Request_Result::renderer_unavailable; - const auto result = scene.Scene_Base::render(*frame); + const auto result = scene.Scene_Base::render(*frame, std::move(completion)); const auto converted = adapter::enum_cast(result); if (!converted) diff --git a/render_3D/render_3D/frame_control/Point_Frame_Control.h b/render_3D/render_3D/frame_control/Point_Frame_Control.h index 4717f4d..2086d7d 100644 --- a/render_3D/render_3D/frame_control/Point_Frame_Control.h +++ b/render_3D/render_3D/frame_control/Point_Frame_Control.h @@ -14,6 +14,7 @@ public: [[nodiscard]] std::shared_ptr runtime(); [[nodiscard]] std::shared_ptr runtime() const; [[nodiscard]] Frame_Request_Result request(Render_Scene_3D& scene); + [[nodiscard]] Frame_Request_Result request(Render_Scene_3D& scene, Scene_Render_Completion completion); void replace(std::shared_ptr strategy); private: struct Impl; diff --git a/web_server/app/Asio_WebSocket_Scene_Loop.h b/web_server/app/Asio_WebSocket_Scene_Loop.h index 57760ce..8251a09 100644 --- a/web_server/app/Asio_WebSocket_Scene_Loop.h +++ b/web_server/app/Asio_WebSocket_Scene_Loop.h @@ -67,11 +67,19 @@ private: co_return; throw asio::system_error(error); } - auto response = session.handle(event); + using Completion_Channel = asio::experimental::concurrent_channel; + auto completion_channel = std::make_shared(events_.get_executor(), 1); + session.async_handle(event, [completion_channel](Web_Async_Response response) { + completion_channel->try_send(asio::error_code{}, std::move(response)); + }); + asio::error_code completion_error; + auto completed = co_await completion_channel->async_receive(asio::redirect_error(asio::use_awaitable, completion_error)); + if (completion_error) throw asio::system_error(completion_error); + if (completed.exception) std::rethrow_exception(completed.exception); if (stopped_.load(std::memory_order_acquire)) co_return; - if (response) - response_handler_(std::move(*response)); + if (completed.response) + response_handler_(std::move(*completed.response)); } } asio::experimental::concurrent_channel events_; diff --git a/web_server/app/Gallery_Plot_Session.cpp b/web_server/app/Gallery_Plot_Session.cpp index 928c7e2..c42f0f8 100644 --- a/web_server/app/Gallery_Plot_Session.cpp +++ b/web_server/app/Gallery_Plot_Session.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -308,6 +309,40 @@ struct Gallery_Plot_Session::Impl { return std::nullopt; return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)}; } + void async_handle_frame_request(Web_Response_Completion completion) { + const auto request_started = std::chrono::steady_clock::now(); + if (!scene) { + completion({}); + return; + } + const bool submitted = scene->async_request_frame( + [this, request_started, completion](Scene_Render_Result render_result, std::exception_ptr exception) mutable { + if (exception) { + completion({{}, std::move(exception)}); + return; + } + if (render_result != Scene_Render_Result::none) { + completion({}); + return; + } + try { + const auto encode_started = std::chrono::steady_clock::now(); + auto pixels = scene->encode_latest_pixels(); + const auto encode_finished = std::chrono::steady_clock::now(); + if (pixels) + scene->record_pixel_response(request_started, encode_started, encode_finished, pixels->size()); + if (!pixels) { + completion({}); + return; + } + completion({Web_Response{Web_Response_Type::Pixels, std::move(*pixels)}, {}}); + } + catch (...) { + completion({{}, std::current_exception()}); + } + }); + if (!submitted) completion({}); + } std::optional handle(const Web_Event& event) { if (std::holds_alternative(event)) return handle_frame_request(); @@ -336,10 +371,20 @@ struct Gallery_Plot_Session::Impl { }, event); } + void async_handle(const Web_Event& event, Web_Response_Completion completion) { + if (std::holds_alternative(event)) { + async_handle_frame_request(std::move(completion)); + return; + } + completion({handle(event), {}}); + } }; Gallery_Plot_Session::Gallery_Plot_Session() : impl_(std::make_unique()) {} Gallery_Plot_Session::~Gallery_Plot_Session() = default; std::optional Gallery_Plot_Session::handle(const Web_Event& event) { return impl_->handle(event); } +void Gallery_Plot_Session::async_handle(const Web_Event& event, Web_Response_Completion completion) { + impl_->async_handle(event, std::move(completion)); +} } // namespace renderive::web diff --git a/web_server/app/Gallery_Plot_Session.h b/web_server/app/Gallery_Plot_Session.h index 11cc377..4c093f9 100644 --- a/web_server/app/Gallery_Plot_Session.h +++ b/web_server/app/Gallery_Plot_Session.h @@ -15,7 +15,7 @@ public: Gallery_Plot_Session& operator=(const Gallery_Plot_Session&) = delete; [[nodiscard]] std::optional handle(const Web_Event& event); - + void async_handle(const Web_Event& event, Web_Response_Completion completion); private: struct Impl; std::unique_ptr impl_; diff --git a/web_server/app/Web_Plot_Session.cpp b/web_server/app/Web_Plot_Session.cpp index 2a871dd..1c2731c 100644 --- a/web_server/app/Web_Plot_Session.cpp +++ b/web_server/app/Web_Plot_Session.cpp @@ -25,20 +25,21 @@ struct Web_Plot_Session::Impl { scene = std::move(*built_scene); scene->activate_view(); } + std::optional encode_frame() { + std::string pixels; + scene->with_frame([&](Image_View image) { + pixels = encode_pixel_frame(image, ++sequence); + }); + if (pixels.empty()) return std::nullopt; + return Web_Response{Web_Response_Type::Pixels, std::move(pixels)}; + } std::optional handle(const Web_Event& event) { return std::visit([this](const auto& value) -> std::optional { using T = std::decay_t; if constexpr (std::same_as) { const auto result = scene->render_frame(); - if (!result || scene->wait_for_render() != Scene_Render_Result::none) - return std::nullopt; - std::string pixels; - scene->with_frame([&](Image_View image) { - pixels = encode_pixel_frame(image, ++sequence); - }); - if (pixels.empty()) return std::nullopt; - return Web_Response{Web_Response_Type::Pixels, - std::move(pixels)}; + if (!result || scene->wait_for_render() != Scene_Render_Result::none) return std::nullopt; + return encode_frame(); } else if constexpr (std::same_as) { viewport = value.size; rebuild(); @@ -48,10 +49,37 @@ struct Web_Plot_Session::Impl { return std::nullopt; }, event); } + void async_handle(const Web_Event& event, Web_Response_Completion completion) { + if (!std::holds_alternative(event)) { + completion({handle(event), {}}); + return; + } + const auto result = scene->render_frame( + [this, completion](Scene_Render_Result render_result, std::exception_ptr exception) mutable { + if (exception) { + completion({{}, std::move(exception)}); + return; + } + if (render_result != Scene_Render_Result::none) { + completion({}); + return; + } + try { + completion({encode_frame(), {}}); + } + catch (...) { + completion({{}, std::current_exception()}); + } + }); + if (!result || *result != Plot_Render_Status::rendered) completion({}); + } }; Web_Plot_Session::Web_Plot_Session() : impl_(std::make_unique()) {} Web_Plot_Session::~Web_Plot_Session() = default; std::optional Web_Plot_Session::handle(const Web_Event& event) { return impl_->handle(event); } +void Web_Plot_Session::async_handle(const Web_Event& event, Web_Response_Completion completion) { + impl_->async_handle(event, std::move(completion)); +} } // namespace renderive::web diff --git a/web_server/app/Web_Plot_Session.h b/web_server/app/Web_Plot_Session.h index d76fef1..60d0a8f 100644 --- a/web_server/app/Web_Plot_Session.h +++ b/web_server/app/Web_Plot_Session.h @@ -2,6 +2,8 @@ #include "Web_Event.h" +#include +#include #include #include #include @@ -14,7 +16,11 @@ struct Web_Response { Web_Response_Type type = Web_Response_Type::Pixels; std::string payload; }; - +struct Web_Async_Response { + std::optional response; + std::exception_ptr exception; +}; +using Web_Response_Completion = std::function; class Web_Plot_Session final { public: Web_Plot_Session(); @@ -23,7 +29,7 @@ public: Web_Plot_Session& operator=(const Web_Plot_Session&) = delete; [[nodiscard]] std::optional handle(const Web_Event& event); - + void async_handle(const Web_Event& event, Web_Response_Completion completion); private: struct Impl; std::unique_ptr impl_; diff --git a/web_server/app/common/Gallery_Scene_Interface.h b/web_server/app/common/Gallery_Scene_Interface.h index ebc1baa..5a933c9 100644 --- a/web_server/app/common/Gallery_Scene_Interface.h +++ b/web_server/app/common/Gallery_Scene_Interface.h @@ -3,6 +3,7 @@ #include "Gallery_Performance_Types.h" #include "Gallery_Types.h" #include "adminive/adminive.hpp" +#include #include #include #include @@ -22,6 +23,7 @@ public: virtual void resize(int width, int height) = 0; virtual void dispatch(const Gallery_Input_Event& event) = 0; [[nodiscard]] virtual bool request_frame() = 0; + [[nodiscard]] virtual bool async_request_frame(Scene_Render_Completion completion) = 0; [[nodiscard]] virtual std::optional encode_latest_pixels() = 0; virtual void record_pixel_response(std::chrono::steady_clock::time_point request_started, std::chrono::steady_clock::time_point encode_started, std::chrono::steady_clock::time_point encode_finished, std::size_t pixel_bytes) = 0; [[nodiscard]] virtual std::string action(const Gallery_Action_Request& request, bool& recognized) = 0; diff --git a/web_server/app/render_2D/Gallery_Scene2D.cpp b/web_server/app/render_2D/Gallery_Scene2D.cpp index 0591611..6ee3c08 100644 --- a/web_server/app/render_2D/Gallery_Scene2D.cpp +++ b/web_server/app/render_2D/Gallery_Scene2D.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace renderive::web { @@ -159,7 +160,15 @@ public: ++render_count_; return true; } - + bool async_request_frame(Scene_Render_Completion completion) override { + update_samples(); + const auto result = scene_->render_frame( + [this, completion = std::move(completion)](Scene_Render_Result render_result, std::exception_ptr exception) mutable { + if (!exception && render_result == Scene_Render_Result::none) ++render_count_; + completion(render_result, std::move(exception)); + }); + return result && *result == Plot_Render_Status::rendered; + } std::optional encode_latest_pixels() override { std::string result; scene_->with_frame([&](Image_View image) { diff --git a/web_server/app/render_3D/Gallery_Scene3D.cpp b/web_server/app/render_3D/Gallery_Scene3D.cpp index 2f76af2..74ae953 100644 --- a/web_server/app/render_3D/Gallery_Scene3D.cpp +++ b/web_server/app/render_3D/Gallery_Scene3D.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace renderive::web { @@ -137,7 +138,14 @@ public: ++render_count_; return true; } - + bool async_request_frame(Scene_Render_Completion completion) override { + const auto result = scene_->request_frame( + [this, completion = std::move(completion)](Scene_Render_Result render_result, std::exception_ptr exception) mutable { + if (!exception && render_result == Scene_Render_Result::none) ++render_count_; + completion(render_result, std::move(exception)); + }); + return result == Frame_Request_Result::none; + } std::optional encode_latest_pixels() override { const auto frame = scene_->latest_frame(); if (!frame || frame->rgba8.empty()) return std::nullopt;