471 lines
22 KiB
C++
471 lines
22 KiB
C++
#pragma once
|
|
#include "../base/Types.h"
|
|
#include "../event/Event.h"
|
|
#include "../render/Blend2D_Cache.h"
|
|
#include "../renderable/Renderable.h"
|
|
#include <renderive/base/observer/Observer.hpp>
|
|
#include <renderive/frame_control/Frame_Consumer_Feedback.hpp>
|
|
#include <renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp>
|
|
#include <renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp>
|
|
#include <renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp>
|
|
#include <renderive/scene/Scene2D_Context.hpp>
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <concepts>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
namespace renderive {
|
|
class Performance_Overlay;
|
|
class Renderable;
|
|
class Event_Handler;
|
|
enum class Frame_Control_Mode : std::uint8_t {
|
|
Manual,
|
|
Low_Latency,
|
|
Playback
|
|
};
|
|
struct Refresh_Control_Snapshot {
|
|
double frequency_hz{};
|
|
std::uint64_t frame_count{};
|
|
std::uint64_t next_refresh_interval_ns{};
|
|
};
|
|
struct Low_Latency_Diagnostics {
|
|
bool active{};
|
|
Refresh_Control_Snapshot refresh;
|
|
};
|
|
struct Frame_Observer_Snapshot {
|
|
Frame_Control_Mode mode = Frame_Control_Mode::Low_Latency;
|
|
std::string last_event;
|
|
std::string limit_state;
|
|
double frequency_hz{};
|
|
std::uint64_t observation_count{};
|
|
std::uint64_t produced_frame_count{};
|
|
std::uint64_t consumed_frame_count{};
|
|
std::uint64_t dropped_frame_count{};
|
|
std::uint64_t failed_operation_count{};
|
|
std::uint64_t pending_frame_count{};
|
|
std::uint64_t latest_sequence{};
|
|
std::uint64_t paint_duration_ns{};
|
|
std::uint64_t render_duration_ns{};
|
|
std::uint64_t target_interval_ns{};
|
|
bool frequency_limit_enabled{};
|
|
bool consumer_feedback_enabled{};
|
|
std::uint64_t bottleneck_duration_ns{};
|
|
std::uint64_t consumer_sample_interval_ns{};
|
|
std::uint64_t consumer_smoothed_interval_ns{};
|
|
std::uint64_t consumer_variation_ns{};
|
|
std::uint64_t consumer_safety_interval_ns{};
|
|
std::uint64_t consumer_interval_ns{};
|
|
std::uint64_t next_refresh_interval_ns{};
|
|
std::uint64_t paint_lease_wait_ns{};
|
|
std::uint64_t paint_state_wait_ns{};
|
|
std::uint64_t publish_state_wait_ns{};
|
|
std::uint64_t ready_wait_ns{};
|
|
std::uint64_t frame_age_at_render_ns{};
|
|
std::uint64_t render_lease_wait_ns{};
|
|
std::uint64_t render_state_wait_ns{};
|
|
std::uint64_t render_finish_state_wait_ns{};
|
|
std::uint64_t queue_wait_ns{};
|
|
std::uint64_t end_to_end_ns{};
|
|
};
|
|
class Presentation_Sink {
|
|
public:
|
|
virtual ~Presentation_Sink() = default;
|
|
virtual void request_present(Rect dirty_rect) = 0;
|
|
};
|
|
struct Scene_State : Scene2D_State {
|
|
Color background = Color::black();
|
|
};
|
|
namespace detail {
|
|
struct Frame_Observer_Data {
|
|
mutable std::mutex mutex;
|
|
std::uint32_t last_event{};
|
|
std::uint64_t observation_count{};
|
|
std::uint64_t produced_frame_count{};
|
|
std::uint64_t consumed_frame_count{};
|
|
std::uint64_t dropped_frame_count{};
|
|
std::uint64_t failed_operation_count{};
|
|
std::uint64_t pending_frame_count{};
|
|
std::uint64_t latest_sequence{};
|
|
std::uint64_t paint_duration_ns{};
|
|
std::uint64_t render_duration_ns{};
|
|
std::uint64_t paint_lease_wait_ns{};
|
|
std::uint64_t paint_state_wait_ns{};
|
|
std::uint64_t publish_state_wait_ns{};
|
|
std::uint64_t ready_wait_ns{};
|
|
std::uint64_t frame_age_at_render_ns{};
|
|
std::uint64_t render_lease_wait_ns{};
|
|
std::uint64_t render_state_wait_ns{};
|
|
std::uint64_t render_finish_state_wait_ns{};
|
|
std::uint64_t queue_wait_ns{};
|
|
};
|
|
struct Frame_Observer {
|
|
static constexpr bool enabled = true;
|
|
std::shared_ptr<Frame_Observer_Data> data;
|
|
template <class Observation>
|
|
void observe(const Observation& observation) noexcept {
|
|
std::lock_guard lock(data->mutex);
|
|
data->last_event = static_cast<std::uint32_t>(observation.event);
|
|
++data->observation_count;
|
|
if (observation.statistics.sequence != 0)
|
|
data->latest_sequence = observation.statistics.sequence;
|
|
if constexpr (requires { observation.statistics.timing.paint_duration_ns; }) {
|
|
if (observation.statistics.sequence != 0) {
|
|
const auto& timing = observation.statistics.timing;
|
|
data->paint_lease_wait_ns = timing.paint_lease_wait_ns;
|
|
data->paint_state_wait_ns = timing.paint_state_wait_ns;
|
|
data->publish_state_wait_ns = timing.publish_state_wait_ns;
|
|
data->ready_wait_ns = timing.ready_wait_ns;
|
|
data->frame_age_at_render_ns = timing.frame_age_at_render_ns;
|
|
data->render_lease_wait_ns = timing.render_lease_wait_ns;
|
|
data->render_state_wait_ns = timing.render_state_wait_ns;
|
|
data->render_finish_state_wait_ns = timing.render_finish_state_wait_ns;
|
|
}
|
|
} else if (observation.statistics.sequence != 0) {
|
|
data->paint_duration_ns = observation.statistics.paint_duration_ns;
|
|
data->render_duration_ns = observation.statistics.render_duration_ns;
|
|
}
|
|
if constexpr (requires { observation.statistics.queue_wait_ns; }) {
|
|
if (observation.statistics.sequence != 0)
|
|
data->queue_wait_ns = observation.statistics.queue_wait_ns;
|
|
}
|
|
if constexpr (requires { observation.state.prepared_frame_count; }) {
|
|
data->produced_frame_count = observation.state.prepared_frame_count;
|
|
data->consumed_frame_count = observation.state.render_count;
|
|
data->dropped_frame_count = observation.state.replaced_prepared_frame_count + observation.state.discarded_prepared_frame_count;
|
|
data->failed_operation_count = observation.state.failed_refresh_count;
|
|
data->pending_frame_count = observation.state.pending_frame ? 1U : 0U;
|
|
} else if constexpr (requires { observation.state.limit_state; }) {
|
|
data->produced_frame_count = observation.statistics.counters.published_frame_count;
|
|
data->consumed_frame_count = observation.state.completed_lifecycle_count;
|
|
data->dropped_frame_count = observation.statistics.counters.abandoned_frame_count + observation.statistics.counters.manually_discarded_frame_count;
|
|
data->failed_operation_count = observation.statistics.counters.swap_failure_count;
|
|
const auto retired = observation.state.completed_lifecycle_count + observation.statistics.counters.abandoned_frame_count + observation.statistics.counters.manually_discarded_frame_count;
|
|
data->pending_frame_count = observation.statistics.counters.published_frame_count > retired ? 1U : 0U;
|
|
} else {
|
|
data->produced_frame_count = observation.state.enqueued_frame_count;
|
|
data->consumed_frame_count = observation.state.rendered_frame_count;
|
|
data->failed_operation_count = observation.state.empty_acquire_count;
|
|
data->pending_frame_count = observation.state.pending_frame_count;
|
|
}
|
|
}
|
|
};
|
|
using Frame_Observer_State = ::Observer_State<Frame_Observer>;
|
|
using Manual_Frame_Control = ::Manual_Refresh_Strategy<::Scene2D_Frame_Data, std::mutex, Frame_Observer_State>;
|
|
using Low_Latency_Frame_Control = ::Low_Latency_Strategy<::Scene2D_Frame_Data, std::mutex, Frame_Observer_State>;
|
|
using Playback_Frame_Control = ::Flow_Refresh_Strategy<::Scene2D_Frame_Data, std::mutex, Frame_Observer_State>;
|
|
template <class Frame_Control>
|
|
concept Scene_Frame_Control = std::same_as<Frame_Control, Manual_Frame_Control> || std::same_as<Frame_Control, Low_Latency_Frame_Control> || std::same_as<Frame_Control, Playback_Frame_Control>;
|
|
template <Scene_Frame_Control Frame_Control>
|
|
constexpr Frame_Control_Mode frame_control_mode() noexcept {
|
|
if constexpr (std::same_as<Frame_Control, Manual_Frame_Control>)
|
|
return Frame_Control_Mode::Manual;
|
|
if constexpr (std::same_as<Frame_Control, Low_Latency_Frame_Control>)
|
|
return Frame_Control_Mode::Low_Latency;
|
|
return Frame_Control_Mode::Playback;
|
|
}
|
|
LIB_DECL renderive_Owner<Renderable> make_renderable_group(bool cache_enabled);
|
|
LIB_DECL void record_performance_frame(Performance_Overlay& overlay, double duration_ms, const Low_Latency_Diagnostics& diagnostics, std::size_t renderable_count, Size viewport);
|
|
inline std::string observer_event_name(Frame_Control_Mode mode, std::uint32_t event) {
|
|
if (mode == Frame_Control_Mode::Manual) {
|
|
static constexpr const char* names[]{"prepared", "prepared_replaced", "refresh_succeeded", "refresh_failed", "rendered", "real_time_data_updated", "manually_discarded"};
|
|
return event < std::size(names) ? names[event] : "none";
|
|
}
|
|
if (mode == Frame_Control_Mode::Playback) {
|
|
static constexpr const char* names[]{"enqueued", "dequeued", "queue_empty", "rendered", "real_time_data_updated"};
|
|
return event < std::size(names) ? names[event] : "none";
|
|
}
|
|
static constexpr const char* names[]{"published", "abandoned", "manually_discarded", "swap_failed", "rendered", "lifecycle_completed", "real_time_data_updated"};
|
|
return event < std::size(names) ? names[event] : "none";
|
|
}
|
|
inline std::string limit_state_name(std::uint32_t state) {
|
|
static constexpr const char* names[]{"frequency_limited", "paint_limited", "render_limited", "consumer_limited", "unlimited"};
|
|
return state < std::size(names) ? names[state] : "unknown";
|
|
}
|
|
inline Rect full_rect(Size size) {
|
|
return {0, 0, std::max(0, size.width), std::max(0, size.height)};
|
|
}
|
|
}
|
|
template <detail::Scene_Frame_Control Frame_Control>
|
|
class Basic_Scene2D final : public ::Scene2D_Context<Frame_Control, detail::Blend2D_Color_Cache, Scene_State> {
|
|
using Kernel_Scene = ::Scene2D_Context<Frame_Control, detail::Blend2D_Color_Cache, Scene_State>;
|
|
using Scene_State_Strategy = typename Kernel_Scene::Scene_State_Strategy;
|
|
static constexpr Frame_Control_Mode Mode = detail::frame_control_mode<Frame_Control>();
|
|
public:
|
|
Basic_Scene2D() : Basic_Scene2D(std::make_shared<detail::Frame_Observer_Data>()) {}
|
|
~Basic_Scene2D() override = default;
|
|
Basic_Scene2D(const Basic_Scene2D&) = delete;
|
|
Basic_Scene2D& operator=(const Basic_Scene2D&) = delete;
|
|
void init() {
|
|
std::lock_guard lock(initialization_mutex_);
|
|
if (root_renderable())
|
|
return;
|
|
auto root = detail::make_renderable_group(true);
|
|
root->set_object_name("root");
|
|
auto builder = this->attach_builder();
|
|
builder.attach(root);
|
|
}
|
|
[[nodiscard]] renderive_Owner<Renderable> root_renderable() const {
|
|
const auto topology = this->topology_snapshot();
|
|
for (const auto& relationship : topology.display) {
|
|
if (relationship.parent)
|
|
continue;
|
|
auto base = renderive_const_owner_cast<::Renderable_Base>(relationship.child);
|
|
if (auto renderable = renderive_dynamic_owner_cast<Renderable>(base))
|
|
return renderable;
|
|
}
|
|
return {};
|
|
}
|
|
void set_background_color(Color color) {
|
|
if (background_color() == color)
|
|
return;
|
|
static_cast<Scene_State_Strategy&>(*this).template set<&Scene_State::background>(color);
|
|
this->notify_model_dirty();
|
|
}
|
|
[[nodiscard]] Color background_color() const noexcept {
|
|
return static_cast<const Scene_State_Strategy&>(*this).template get<&Scene_State::background>();
|
|
}
|
|
void set_viewport_size(Size size) {
|
|
size.width = std::max(0, size.width);
|
|
size.height = std::max(0, size.height);
|
|
const Frame_Viewport viewport{size.width, size.height};
|
|
if (static_cast<const Scene_State_Strategy&>(*this).template get<&Scene_State::viewport>() == viewport)
|
|
return;
|
|
static_cast<Scene_State_Strategy&>(*this).template set<&Scene_State::viewport>(viewport);
|
|
const auto topology = this->topology_snapshot();
|
|
for (const auto& renderable : topology.renderables)
|
|
const_cast<::Renderable_Base&>(*renderable).invalidate_prepare();
|
|
this->notify_model_dirty();
|
|
}
|
|
[[nodiscard]] Size viewport_size() const noexcept {
|
|
const auto viewport = static_cast<const Scene_State_Strategy&>(*this).template get<&Scene_State::viewport>();
|
|
return {viewport.width, viewport.height};
|
|
}
|
|
void dispatch_event(const Event& event) {
|
|
const auto paint_order = this->paint_order_snapshot();
|
|
for (auto iterator = paint_order.rbegin(); iterator != paint_order.rend(); ++iterator) {
|
|
auto base = renderive_const_owner_cast<::Renderable_Base>(*iterator);
|
|
if (auto renderable = renderive_dynamic_owner_cast<Renderable>(base)) {
|
|
if (renderable->is_visible()) {
|
|
if (auto handler = renderive_dynamic_owner_cast<Event_Handler>(base))
|
|
handler->handle_event(event);
|
|
}
|
|
if (event.is_accepted())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
[[nodiscard]] bool prepare_frame() {
|
|
auto paint_frame = this->frame_control.acquire_painter();
|
|
if (!paint_frame)
|
|
return false;
|
|
this->publish_frame_state();
|
|
return true;
|
|
}
|
|
[[nodiscard]] bool refresh_manual_frame() {
|
|
if constexpr (Mode == Frame_Control_Mode::Manual)
|
|
return this->frame_control.refresh();
|
|
return false;
|
|
}
|
|
[[nodiscard]] bool render_prepared_frame() {
|
|
auto render_frame = this->frame_control.acquire_renderer();
|
|
if (!render_frame)
|
|
return false;
|
|
this->render(*render_frame);
|
|
this->wait_for_render();
|
|
return true;
|
|
}
|
|
[[nodiscard]] bool discard_pending_frame() {
|
|
if constexpr (Mode == Frame_Control_Mode::Playback)
|
|
return false;
|
|
else
|
|
return this->frame_control.discard_pending_frame();
|
|
}
|
|
[[nodiscard]] bool render_frame(bool force = false) {
|
|
if (!view_active() && !force)
|
|
return false;
|
|
if (viewport_size().empty())
|
|
return false;
|
|
if (!this->consume_model_dirty() && !force)
|
|
return false;
|
|
const auto started = std::chrono::steady_clock::now();
|
|
Size viewport;
|
|
try {
|
|
if (!prepare_frame())
|
|
return false;
|
|
const auto frame_viewport = this->Scene_State_Strategy::render_state_value().viewport;
|
|
viewport = {frame_viewport.width, frame_viewport.height};
|
|
if constexpr (Mode == Frame_Control_Mode::Manual) {
|
|
if (!refresh_manual_frame())
|
|
return false;
|
|
}
|
|
if (!render_prepared_frame())
|
|
return false;
|
|
} catch (...) {
|
|
this->notify_model_dirty();
|
|
throw;
|
|
}
|
|
const auto finished = std::chrono::steady_clock::now();
|
|
const double duration_ms = std::chrono::duration<double, std::milli>(finished - started).count();
|
|
std::shared_ptr<Presentation_Sink> sink;
|
|
std::shared_ptr<Performance_Overlay> overlay;
|
|
{
|
|
std::lock_guard lock(control_mutex_);
|
|
sink = presentation_sink_.lock();
|
|
overlay = performance_;
|
|
}
|
|
if (overlay)
|
|
detail::record_performance_frame(*overlay, duration_ms, diagnostics(), this->renderable_count(), viewport);
|
|
if (sink)
|
|
sink->request_present(detail::full_rect(viewport));
|
|
return true;
|
|
}
|
|
void with_frame(const std::function<void(Image_View)>& consumer) {
|
|
if (!consumer)
|
|
return;
|
|
this->with_final_color_cache([&consumer](const detail::Blend2D_Color_Cache& cache) {
|
|
consumer(cache.view());
|
|
});
|
|
}
|
|
void activate_view() noexcept {
|
|
active_.store(true, std::memory_order_release);
|
|
this->notify_model_dirty();
|
|
}
|
|
void deactivate_view() noexcept {
|
|
active_.store(false, std::memory_order_release);
|
|
}
|
|
[[nodiscard]] bool view_active() const noexcept {
|
|
return active_.load(std::memory_order_acquire);
|
|
}
|
|
void set_max_render_fps(double fps) {
|
|
if constexpr (Mode != Frame_Control_Mode::Low_Latency) {
|
|
throw std::logic_error("maximum render FPS is only available in low-latency mode");
|
|
} else {
|
|
if (!std::isfinite(fps) || fps <= 0.0)
|
|
throw std::invalid_argument("maximum render FPS must be finite and positive");
|
|
this->frame_control.set_frequency_hz(fps);
|
|
this->notify_model_dirty();
|
|
}
|
|
}
|
|
void clear_max_render_fps() {
|
|
if constexpr (Mode != Frame_Control_Mode::Low_Latency) {
|
|
throw std::logic_error("maximum render FPS is only available in low-latency mode");
|
|
} else {
|
|
this->frame_control.clear_frequency_limit();
|
|
this->notify_model_dirty();
|
|
}
|
|
}
|
|
void set_consumer_feedback(Frame_Consumer_Feedback feedback) {
|
|
if constexpr (Mode != Frame_Control_Mode::Low_Latency)
|
|
throw std::logic_error("consumer feedback is only available in low-latency mode");
|
|
else
|
|
this->frame_control.set_consumer_feedback(feedback);
|
|
}
|
|
void clear_consumer_feedback() {
|
|
if constexpr (Mode != Frame_Control_Mode::Low_Latency)
|
|
throw std::logic_error("consumer feedback is only available in low-latency mode");
|
|
else
|
|
this->frame_control.clear_consumer_feedback();
|
|
}
|
|
[[nodiscard]] double max_render_fps() const noexcept {
|
|
if constexpr (Mode == Frame_Control_Mode::Low_Latency)
|
|
return this->frame_control.state().frequency_hz;
|
|
return std::numeric_limits<double>::quiet_NaN();
|
|
}
|
|
[[nodiscard]] Low_Latency_Diagnostics diagnostics() const {
|
|
if constexpr (Mode == Frame_Control_Mode::Low_Latency) {
|
|
const auto state = this->frame_control.state();
|
|
return {view_active(), {state.frequency_hz, state.completed_lifecycle_count, state.next_refresh_interval_ns}};
|
|
}
|
|
const auto observed = frame_observer_snapshot();
|
|
return {view_active(), {std::numeric_limits<double>::quiet_NaN(), observed.consumed_frame_count, 0}};
|
|
}
|
|
[[nodiscard]] Refresh_Control_Snapshot refresh_feedback_snapshot() const {
|
|
return diagnostics().refresh;
|
|
}
|
|
[[nodiscard]] static constexpr Frame_Control_Mode frame_control_mode() noexcept {
|
|
return Mode;
|
|
}
|
|
[[nodiscard]] Frame_Observer_Snapshot frame_observer_snapshot() const {
|
|
Frame_Observer_Snapshot snapshot;
|
|
{
|
|
std::lock_guard lock(observer_->mutex);
|
|
snapshot.mode = Mode;
|
|
snapshot.last_event = observer_->observation_count == 0 ? std::string("none") : detail::observer_event_name(Mode, observer_->last_event);
|
|
snapshot.limit_state = "not_applicable";
|
|
snapshot.observation_count = observer_->observation_count;
|
|
snapshot.produced_frame_count = observer_->produced_frame_count;
|
|
snapshot.consumed_frame_count = observer_->consumed_frame_count;
|
|
snapshot.dropped_frame_count = observer_->dropped_frame_count;
|
|
snapshot.failed_operation_count = observer_->failed_operation_count;
|
|
snapshot.pending_frame_count = observer_->pending_frame_count;
|
|
snapshot.latest_sequence = observer_->latest_sequence;
|
|
snapshot.paint_duration_ns = observer_->paint_duration_ns;
|
|
snapshot.render_duration_ns = observer_->render_duration_ns;
|
|
snapshot.paint_lease_wait_ns = observer_->paint_lease_wait_ns;
|
|
snapshot.paint_state_wait_ns = observer_->paint_state_wait_ns;
|
|
snapshot.publish_state_wait_ns = observer_->publish_state_wait_ns;
|
|
snapshot.ready_wait_ns = observer_->ready_wait_ns;
|
|
snapshot.frame_age_at_render_ns = observer_->frame_age_at_render_ns;
|
|
snapshot.render_lease_wait_ns = observer_->render_lease_wait_ns;
|
|
snapshot.render_state_wait_ns = observer_->render_state_wait_ns;
|
|
snapshot.render_finish_state_wait_ns = observer_->render_finish_state_wait_ns;
|
|
snapshot.queue_wait_ns = observer_->queue_wait_ns;
|
|
}
|
|
if constexpr (Mode == Frame_Control_Mode::Low_Latency) {
|
|
const auto state = this->frame_control.state();
|
|
snapshot.limit_state = detail::limit_state_name(static_cast<std::uint32_t>(state.limit_state));
|
|
snapshot.frequency_hz = state.frequency_hz;
|
|
snapshot.paint_duration_ns = state.paint_duration_ns;
|
|
snapshot.render_duration_ns = state.render_duration_ns;
|
|
snapshot.target_interval_ns = state.target_interval_ns;
|
|
snapshot.frequency_limit_enabled = state.frequency_limit_enabled;
|
|
snapshot.consumer_feedback_enabled = state.consumer_feedback_enabled;
|
|
snapshot.bottleneck_duration_ns = state.bottleneck_duration_ns;
|
|
snapshot.consumer_sample_interval_ns = state.consumer_sample_interval_ns;
|
|
snapshot.consumer_smoothed_interval_ns = state.consumer_smoothed_interval_ns;
|
|
snapshot.consumer_variation_ns = state.consumer_variation_ns;
|
|
snapshot.consumer_safety_interval_ns = state.consumer_safety_interval_ns;
|
|
snapshot.consumer_interval_ns = state.consumer_interval_ns;
|
|
snapshot.next_refresh_interval_ns = state.next_refresh_interval_ns;
|
|
snapshot.end_to_end_ns = state.end_to_end_ns;
|
|
}
|
|
return snapshot;
|
|
}
|
|
void set_presentation_sink(std::weak_ptr<Presentation_Sink> sink) {
|
|
std::lock_guard lock(control_mutex_);
|
|
presentation_sink_ = std::move(sink);
|
|
}
|
|
[[nodiscard]] std::shared_ptr<Performance_Overlay> performance_overlay() const {
|
|
std::lock_guard lock(control_mutex_);
|
|
return performance_;
|
|
}
|
|
void set_performance_overlay(std::shared_ptr<Performance_Overlay> overlay) {
|
|
{
|
|
std::lock_guard lock(control_mutex_);
|
|
performance_ = std::move(overlay);
|
|
}
|
|
this->notify_model_dirty();
|
|
}
|
|
private:
|
|
explicit Basic_Scene2D(std::shared_ptr<detail::Frame_Observer_Data> observer)
|
|
: Kernel_Scene(*memory_resource(Memory_Domain::Plot_Frame), detail::Frame_Observer_State(detail::Frame_Observer{observer})), observer_(std::move(observer)) {}
|
|
std::shared_ptr<detail::Frame_Observer_Data> observer_;
|
|
mutable std::mutex control_mutex_;
|
|
std::mutex initialization_mutex_;
|
|
std::weak_ptr<Presentation_Sink> presentation_sink_;
|
|
std::shared_ptr<Performance_Overlay> performance_;
|
|
std::atomic_bool active_{};
|
|
};
|
|
using Manual_Scene2D = Basic_Scene2D<detail::Manual_Frame_Control>;
|
|
using Scene2D = Basic_Scene2D<detail::Low_Latency_Frame_Control>;
|
|
using Playback_Scene2D = Basic_Scene2D<detail::Playback_Frame_Control>;
|
|
}
|