636 lines
27 KiB
C++
636 lines
27 KiB
C++
#include "Plot_Core.h"
|
|
#include "Plot_Core_Access.h"
|
|
#include "../architecture/Renderable.h"
|
|
#include "../architecture/Plot_Render_Context.h"
|
|
#include "../architecture/Render_Time.h"
|
|
#include "../architecture/Update_Completion.h"
|
|
#include "../base/global.h"
|
|
#include "../render/Canvas.h"
|
|
#include "../render/Frame_Prepare_Subflow.h"
|
|
#include "../render/Render_Frame_Snapshot.h"
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <utility>
|
|
namespace renderive {
|
|
class Plot_Core_Private;
|
|
struct Frame_Render_Job {
|
|
std::shared_ptr<Plot_Core_Private> owner;
|
|
std::uint64_t generation{};
|
|
Render_Frame_Surface frame_surface;
|
|
Render_Output* buffer{};
|
|
std::shared_ptr<Frame_Lifecycle_Record> lifecycle;
|
|
Render_Frame_Snapshot render_snapshot;
|
|
Renderable_Frame_Tree renderables;
|
|
std::shared_ptr<Frame_Worker_Stats> worker_stat;
|
|
std::atomic_uint64_t prepared_output_count{0};
|
|
};
|
|
class Plot_Core_Private final : public std::enable_shared_from_this<Plot_Core_Private>, public Frame_Flow_Host {
|
|
public:
|
|
explicit Plot_Core_Private(std::unique_ptr<Frame_Flow> selected_flow)
|
|
: flow(std::move(selected_flow)) {}
|
|
~Plot_Core_Private() = default;
|
|
bool destroying() const {
|
|
return !context || context->destroying.load(std::memory_order_acquire);
|
|
}
|
|
Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns) const override {
|
|
return flow ? flow->runtime_snapshot(now_ns, destroying()) : Frame_Flow_Runtime_Snapshot{destroying(), false, false, false, false, now_ns};
|
|
}
|
|
bool submit_render_once() {
|
|
if (destroying())
|
|
return false;
|
|
if (!root)
|
|
return false;
|
|
Size size{
|
|
context->render_width.load(std::memory_order_acquire),
|
|
context->render_height.load(std::memory_order_acquire)
|
|
};
|
|
if (size.empty())
|
|
return false;
|
|
auto frame_surface = flow ? flow->begin_render_frame() : Render_Frame_Surface{};
|
|
if (!frame_surface)
|
|
return false;
|
|
Render_Output* buffer = frame_surface.output;
|
|
buffer->render_complete.store(false, std::memory_order_release);
|
|
cancel_update_states(buffer->presented_update_states);
|
|
if (flow)
|
|
flow->drain_frame_update_states(buffer->presented_update_states);
|
|
std::uint64_t render_role_enter_ns = buffer->metadata ? buffer->metadata->render_role_enter_ns : 0;
|
|
buffer->metadata = std::make_shared<Frame_Lifecycle_Record>();
|
|
auto frame_record = buffer->metadata;
|
|
Frame_Lifecycle_Record& frame = *frame_record;
|
|
std::uint64_t submit_time = steady_now_ns();
|
|
frame.frame_id = ++next_frame_id;
|
|
frame.input_version = frame_surface.input_version;
|
|
frame.render_request_ns = submit_time;
|
|
frame.render_role_enter_ns = render_role_enter_ns ? render_role_enter_ns : submit_time;
|
|
frame.core_data_time_ns = latest_input_time_ns.load(std::memory_order_acquire);
|
|
frame.raw_input_count = pending_input_count.exchange(0, std::memory_order_acq_rel);
|
|
frame.pixel_width = static_cast<std::uint64_t>(size.width);
|
|
frame.pixel_height = static_cast<std::uint64_t>(size.height);
|
|
frame.viewport_version = context->viewport_version.load(std::memory_order_acquire);
|
|
frame.dirty_reason_mask = pending_dirty_reason_mask.exchange(0, std::memory_order_acq_rel);
|
|
frame.render_slot_idle_wait_ns = submit_time > frame.render_role_enter_ns ? submit_time - frame.render_role_enter_ns : 0;
|
|
auto job = std::make_shared<Frame_Render_Job>();
|
|
job->owner = shared_from_this();
|
|
job->generation = generation.load(std::memory_order_acquire);
|
|
job->frame_surface = std::move(frame_surface);
|
|
job->buffer = buffer;
|
|
job->lifecycle = frame_record;
|
|
job->worker_stat = std::make_shared<Frame_Worker_Stats>();
|
|
job->render_snapshot.destroying_token = destroying_token;
|
|
job->render_snapshot.size = size;
|
|
job->render_snapshot.background_color = background_color();
|
|
job->render_snapshot.previous_frame_time_ns = previous_frame_time_ns;
|
|
job->render_snapshot.frame_time_ns = submit_time;
|
|
job->render_snapshot.frame_metadata = frame_record.get();
|
|
job->render_snapshot.frame_update_states = &buffer->presented_update_states;
|
|
previous_frame_time_ns = submit_time;
|
|
job->renderables = renderable_tree_snapshot();
|
|
job->render_snapshot.frame_tree = &job->renderables;
|
|
if (!frame_tree_views_complete(job->renderables)) {
|
|
frame.outcome = Frame_Outcome::Cancelled;
|
|
if (flow)
|
|
flow->cancel_render_frame(std::move(job->frame_surface));
|
|
supersede_update_states(buffer->presented_update_states);
|
|
release_frame_views(job);
|
|
collect_frame(frame_record);
|
|
notify_render_finished(frame);
|
|
return false;
|
|
}
|
|
auto& scheduler = Global::instance()->render_scheduler();
|
|
bool submitted = scheduler.try_submit_render_task(Render_Executor_Task{
|
|
plot_execution_id,
|
|
frame.frame_id,
|
|
Render_Task_Kind::Frame,
|
|
Task{},
|
|
[job](Render_Task_Graph& graph, Render_Task_Graph_Node entry, Render_Task_Graph_Node exit) {
|
|
auto prepare_begin = graph.emplace(Render_Task_Kind::Frame, Task([job]() {
|
|
job->owner->begin_prepare_frame_job(job);
|
|
}));
|
|
auto prepare_dispatch = graph.emplace_subflow(Render_Task_Kind::Frame, [job](Render_Task_Subflow& subflow) {
|
|
job->owner->dispatch_prepare_subflow(job, subflow);
|
|
});
|
|
auto prepare_end = graph.emplace(Render_Task_Kind::Frame, Task([job]() {
|
|
job->owner->end_prepare_frame_job(job);
|
|
}));
|
|
auto draw = graph.emplace(Render_Task_Kind::Primitive, Task([job]() {
|
|
job->owner->draw_frame_job(job);
|
|
}));
|
|
auto finalize = graph.emplace(Render_Task_Kind::Frame, Task([job]() {
|
|
job->owner->finalize_frame_job(job);
|
|
}));
|
|
graph.precede(entry, prepare_begin);
|
|
graph.precede(prepare_begin, prepare_dispatch);
|
|
graph.precede(prepare_dispatch, prepare_end);
|
|
graph.precede(prepare_end, draw);
|
|
graph.precede(draw, finalize);
|
|
graph.precede(finalize, exit);
|
|
},
|
|
Task([job]() {
|
|
Global::instance()->render_scheduler().post([job]() {
|
|
job->owner->complete_frame_job(job);
|
|
});
|
|
}),
|
|
job->worker_stat,
|
|
true
|
|
});
|
|
if (submitted)
|
|
return true;
|
|
frame.outcome = Frame_Outcome::Worker_Budget_Dropped;
|
|
frame.worker.rejected_topology_count++;
|
|
if (flow)
|
|
flow->discard_render_frame(std::move(job->frame_surface));
|
|
release_frame_views(job);
|
|
collect_frame(frame_record);
|
|
notify_render_finished(frame);
|
|
return false;
|
|
}
|
|
static bool frame_tree_views_complete(const Renderable_Frame_Tree& tree) {
|
|
return std::all_of(tree.begin(), tree.end(), [](const Renderable_Frame_Node& node) {
|
|
return !node.frame_view_required || static_cast<bool>(node.frame_view);
|
|
});
|
|
}
|
|
static void release_frame_views(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!job)
|
|
return;
|
|
job->render_snapshot.frame_tree = nullptr;
|
|
job->renderables.clear();
|
|
}
|
|
static bool frame_job_valid(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
return job && job->buffer && job->lifecycle && !job->render_snapshot.destroying();
|
|
}
|
|
void begin_prepare_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!frame_job_valid(job))
|
|
return;
|
|
Frame_Lifecycle_Record& frame = *job->lifecycle;
|
|
std::uint64_t begin_ns = steady_now_ns();
|
|
frame.render_begin_ns = begin_ns;
|
|
frame.prepare_begin_ns = begin_ns;
|
|
job->prepared_output_count.store(0, std::memory_order_release);
|
|
}
|
|
void dispatch_prepare_subflow(const std::shared_ptr<Frame_Render_Job>& job, Render_Task_Subflow& subflow) {
|
|
if (!frame_job_valid(job) || !job->lifecycle->prepare_begin_ns)
|
|
return;
|
|
for (const auto& node : job->renderables) {
|
|
const auto& renderable = node.renderable;
|
|
if (!renderable || !renderable->should_prepare())
|
|
continue;
|
|
build_renderable_prepare(*renderable, subflow, job->render_snapshot, node.frame_view);
|
|
job->prepared_output_count.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
void end_prepare_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!frame_job_valid(job) || !job->lifecycle->prepare_begin_ns)
|
|
return;
|
|
Frame_Lifecycle_Record& frame = *job->lifecycle;
|
|
if (job->render_snapshot.frame_update_states) {
|
|
for (const auto& node : job->renderables) {
|
|
if (node.renderable)
|
|
drain_renderable_prepare_updates(*node.renderable, *job->render_snapshot.frame_update_states);
|
|
}
|
|
}
|
|
frame.prepared_output_count = job->prepared_output_count.load(std::memory_order_acquire);
|
|
frame.prepare_end_ns = steady_now_ns();
|
|
if (job->owner && job->owner->context)
|
|
job->owner->context->first_prepare_data.store(false, std::memory_order_release);
|
|
}
|
|
void draw_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!frame_job_valid(job) || !job->lifecycle->prepare_end_ns)
|
|
return;
|
|
Frame_Lifecycle_Record& frame = *job->lifecycle;
|
|
job->buffer->render_complete.store(false, std::memory_order_release);
|
|
job->buffer->image.resize(job->render_snapshot.size.width, job->render_snapshot.size.height);
|
|
job->buffer->image.fill(job->render_snapshot.background_color);
|
|
Canvas canvas(job->buffer->image);
|
|
frame.draw_begin_ns = steady_now_ns();
|
|
if (!job->renderables.empty() && job->renderables.front().renderable)
|
|
job->renderables.front().renderable->render_tree_snapshot(canvas, job->render_snapshot, job->renderables, 0);
|
|
frame.draw_end_ns = steady_now_ns();
|
|
frame.render_end_ns = steady_now_ns();
|
|
}
|
|
void finalize_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!frame_job_valid(job) || !job->lifecycle->render_end_ns)
|
|
return;
|
|
Frame_Lifecycle_Record& frame = *job->lifecycle;
|
|
for (const auto& update_state : job->buffer->presented_update_states) {
|
|
if (update_state)
|
|
update_state->complete_until(Update_Stage::Rendered);
|
|
}
|
|
job->buffer->render_complete.store(true, std::memory_order_release);
|
|
}
|
|
void execute_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!job || !job->buffer || !job->lifecycle || job->render_snapshot.destroying())
|
|
return;
|
|
begin_prepare_frame_job(job);
|
|
Render_Task_Subflow no_subflow(nullptr);
|
|
dispatch_prepare_subflow(job, no_subflow);
|
|
end_prepare_frame_job(job);
|
|
draw_frame_job(job);
|
|
finalize_frame_job(job);
|
|
}
|
|
void complete_frame_job(const std::shared_ptr<Frame_Render_Job>& job) {
|
|
if (!job || !job->buffer || !job->lifecycle)
|
|
return;
|
|
Frame_Lifecycle_Record& frame = *job->lifecycle;
|
|
if (job->worker_stat) {
|
|
frame.worker = *job->worker_stat;
|
|
frame.executor_queue_wait_ns = frame.worker.topology_queue_wait_total_ns;
|
|
}
|
|
bool valid_generation = job->generation == generation.load(std::memory_order_acquire);
|
|
bool valid_context = !destroying();
|
|
bool render_complete = job->buffer->render_complete.load(std::memory_order_acquire);
|
|
if (!valid_generation || !valid_context || !frame.render_end_ns || !render_complete) {
|
|
if (flow)
|
|
flow->cancel_render_frame(std::move(job->frame_surface));
|
|
frame.outcome = Frame_Outcome::Cancelled;
|
|
supersede_update_states(job->buffer->presented_update_states);
|
|
release_frame_views(job);
|
|
notify_render_finished(frame);
|
|
return;
|
|
}
|
|
auto publish_result = flow ? flow->publish_rendered_frame(std::move(job->frame_surface), steady_now_ns()) : Render_Surface_Publish_Result{};
|
|
release_frame_views(job);
|
|
if (publish_result.superseded_frame_record) {
|
|
notify_frame_returned(publish_result.superseded_frame_record);
|
|
}
|
|
if (publish_result.dropped_frame_record) {
|
|
collect_frame(publish_result.dropped_frame_record);
|
|
}
|
|
if (publish_result.request_present)
|
|
request_present(publish_result.dirty_rect);
|
|
notify_render_finished(frame);
|
|
}
|
|
void post_scheduler_action(Task task) {
|
|
if (!task)
|
|
return;
|
|
auto guard = context;
|
|
auto action = [guard, task = std::move(task)]() mutable {
|
|
if (!guard || guard->destroying.load(std::memory_order_acquire))
|
|
return;
|
|
task();
|
|
};
|
|
auto& scheduler = Global::instance()->render_scheduler();
|
|
if (scheduler.is_scheduler_thread()) {
|
|
action();
|
|
return;
|
|
}
|
|
scheduler.post(std::move(action));
|
|
}
|
|
bool try_render_once() override {
|
|
cancel_render_deadline();
|
|
return submit_render_once();
|
|
}
|
|
void request_paint_from_middle() override {
|
|
auto update_result = flow ? flow->request_present(steady_now_ns()) : Render_Surface_Publish_Result{};
|
|
if (update_result.request_present)
|
|
request_present(update_result.dirty_rect);
|
|
}
|
|
void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) {
|
|
if (flow)
|
|
flow->notify_model_dirty(reason, now_ns);
|
|
}
|
|
void notify_viewport_changed(Size viewport, std::uint64_t now_ns) {
|
|
if (flow)
|
|
flow->notify_viewport_changed(viewport, now_ns);
|
|
}
|
|
void notify_render_finished(Frame_Lifecycle_Record& frame) {
|
|
if (flow)
|
|
flow->notify_render_finished(frame, steady_now_ns());
|
|
}
|
|
void notify_started() {
|
|
if (flow)
|
|
flow->activate_view(steady_now_ns());
|
|
}
|
|
void notify_paused() {
|
|
if (flow)
|
|
flow->deactivate_view();
|
|
}
|
|
void notify_frame_returned(std::shared_ptr<Frame_Lifecycle_Record> frame) {
|
|
if (!frame)
|
|
return;
|
|
if (flow)
|
|
flow->notify_frame_returned(*frame, steady_now_ns());
|
|
collect_frame(frame);
|
|
}
|
|
void notify_paint_finished() {
|
|
if (flow)
|
|
flow->notify_paint_finished(steady_now_ns());
|
|
}
|
|
void schedule_render_deadline(std::uint64_t deadline_ns) override {
|
|
if (deadline_ns <= steady_now_ns()) {
|
|
cancel_render_deadline();
|
|
if (flow)
|
|
flow->notify_timer_fired(steady_now_ns());
|
|
return;
|
|
}
|
|
if (scheduled_flow_deadline_ns && scheduled_flow_deadline_ns <= deadline_ns)
|
|
return;
|
|
scheduled_flow_deadline_ns = deadline_ns;
|
|
std::uint64_t generation = ++flow_deadline_generation;
|
|
auto guard = context;
|
|
auto self = shared_from_this();
|
|
Global::instance()->render_scheduler().post_at(deadline_ns, [guard, self, generation, deadline_ns]() {
|
|
if (!guard || guard->destroying.load(std::memory_order_acquire))
|
|
return;
|
|
if (self->flow_deadline_generation != generation || self->scheduled_flow_deadline_ns != deadline_ns)
|
|
return;
|
|
self->scheduled_flow_deadline_ns = 0;
|
|
if (self->flow)
|
|
self->flow->notify_timer_fired(steady_now_ns());
|
|
});
|
|
}
|
|
void cancel_render_deadline() override {
|
|
scheduled_flow_deadline_ns = 0;
|
|
++flow_deadline_generation;
|
|
}
|
|
void dispatch_event(const Event& event) {
|
|
if (!root)
|
|
return;
|
|
switch (event.type) {
|
|
case Event_Type::Pointer_Move: {
|
|
const auto& pointer_event = static_cast<const Pointer_Event&>(event);
|
|
update_hover_targets(pointer_event);
|
|
dispatch_to_renderables(root->hit_renderables(pointer_event.position), event);
|
|
break;
|
|
}
|
|
case Event_Type::Pointer_Press:
|
|
case Event_Type::Pointer_Release: {
|
|
const auto& pointer_event = static_cast<const Pointer_Event&>(event);
|
|
dispatch_to_renderables(root->hit_renderables(pointer_event.position), event);
|
|
break;
|
|
}
|
|
case Event_Type::Wheel: {
|
|
const auto& wheel_event = static_cast<const Wheel_Event&>(event);
|
|
dispatch_to_renderables(root->hit_renderables(wheel_event.position), event);
|
|
break;
|
|
}
|
|
case Event_Type::Key_Press:
|
|
case Event_Type::Key_Release:
|
|
dispatch_to_renderables(reverse_renderable_snapshot(), event);
|
|
break;
|
|
case Event_Type::Leave:
|
|
clear_hover_targets();
|
|
dispatch_to_renderables(renderable_snapshot(), event);
|
|
break;
|
|
default:
|
|
dispatch_to_renderables(renderable_snapshot(), event);
|
|
break;
|
|
}
|
|
}
|
|
std::vector<std::shared_ptr<Renderable>> renderable_snapshot() const {
|
|
std::vector<std::shared_ptr<Renderable>> result;
|
|
append_renderable_snapshot(result, root);
|
|
return result;
|
|
}
|
|
Renderable_Frame_Tree renderable_tree_snapshot() const {
|
|
Renderable_Frame_Tree result;
|
|
if (root)
|
|
root->append_tree_snapshot(result);
|
|
return result;
|
|
}
|
|
std::vector<std::shared_ptr<Renderable>> reverse_renderable_snapshot() const {
|
|
std::vector<std::shared_ptr<Renderable>> result = renderable_snapshot();
|
|
std::reverse(result.begin(), result.end());
|
|
return result;
|
|
}
|
|
void append_renderable_snapshot(std::vector<std::shared_ptr<Renderable>>& result, const std::shared_ptr<Renderable>& renderable) const {
|
|
if (!renderable)
|
|
return;
|
|
result.push_back(renderable);
|
|
for (const auto& child : renderable->children_snapshot())
|
|
append_renderable_snapshot(result, child);
|
|
}
|
|
void dispatch_to_renderables(const std::vector<std::shared_ptr<Renderable>>& renderables, const Event& event) {
|
|
for (const auto& renderable : renderables) {
|
|
if (!renderable || event.is_accepted())
|
|
break;
|
|
renderable->plot_event(event);
|
|
}
|
|
}
|
|
void update_hover_targets(const Pointer_Event& event) {
|
|
Point pos{static_cast<int>(event.position.x), static_cast<int>(event.position.y)};
|
|
std::vector<std::shared_ptr<Renderable>> hits = root->hit_renderables(event.position);
|
|
for (const auto& old_target : hover_targets) {
|
|
auto target = old_target.lock();
|
|
if (!target)
|
|
continue;
|
|
if (std::find(hits.begin(), hits.end(), target) == hits.end())
|
|
target->set_hover_state(pos, false);
|
|
}
|
|
hover_targets.clear();
|
|
hover_targets.reserve(hits.size());
|
|
for (const auto& target : hits) {
|
|
if (!target)
|
|
continue;
|
|
target->set_hover_state(pos, true);
|
|
hover_targets.push_back(target);
|
|
}
|
|
}
|
|
void clear_hover_targets() {
|
|
Point pos{};
|
|
for (const auto& old_target : hover_targets) {
|
|
if (auto target = old_target.lock())
|
|
target->set_hover_state(pos, false);
|
|
}
|
|
hover_targets.clear();
|
|
}
|
|
void request_present(Rect dirty_rect) {
|
|
if (auto target = sink.lock())
|
|
target->request_present(dirty_rect);
|
|
}
|
|
void collect_frame(std::shared_ptr<const Frame_Lifecycle_Record> frame) {
|
|
if (!frame)
|
|
return;
|
|
auto observer = std::atomic_load_explicit(&context->frame_lifecycle_observer, std::memory_order_acquire);
|
|
if (observer && observer->capture_enabled())
|
|
observer->collect_frame(frame);
|
|
}
|
|
void record_feedback_event(std::shared_ptr<const Frame_Feedback_Event_Record> event) override {
|
|
if (!event)
|
|
return;
|
|
auto observer = std::atomic_load_explicit(&context->frame_lifecycle_observer, std::memory_order_acquire);
|
|
if (observer && observer->capture_enabled())
|
|
observer->collect_feedback_event(event);
|
|
}
|
|
Color background_color() const {
|
|
Pixel p = context->background_rgba.load(std::memory_order_acquire);
|
|
return premultiplied_to_color(p);
|
|
}
|
|
std::unique_ptr<Frame_Flow> flow;
|
|
std::shared_ptr<Plot_Render_Context> context = std::make_shared<Plot_Render_Context>();
|
|
std::shared_ptr<std::atomic_bool> destroying_token = std::make_shared<std::atomic_bool>(false);
|
|
std::shared_ptr<Renderable> root;
|
|
std::vector<std::weak_ptr<Renderable>> hover_targets;
|
|
std::weak_ptr<Presentation_Sink> sink;
|
|
std::atomic<std::uint64_t> latest_input_time_ns{0};
|
|
std::atomic<std::uint64_t> pending_input_count{0};
|
|
std::atomic<std::uint32_t> pending_dirty_reason_mask{0};
|
|
std::uint64_t flow_deadline_generation{};
|
|
std::uint64_t scheduled_flow_deadline_ns{};
|
|
std::uint64_t next_frame_id{};
|
|
std::uint64_t previous_frame_time_ns{};
|
|
Plot_Execution_Id plot_execution_id{};
|
|
std::atomic<std::uint64_t> generation{1};
|
|
};
|
|
namespace {
|
|
Plot_Execution_Id next_plot_execution_id() {
|
|
static std::atomic<Plot_Execution_Id> next_id{1};
|
|
return next_id.fetch_add(1, std::memory_order_acq_rel);
|
|
}
|
|
} // namespace
|
|
Plot_Core::Plot_Core(std::unique_ptr<Frame_Flow> flow)
|
|
: d(std::make_shared<Plot_Core_Private>(std::move(flow))) {
|
|
d->plot_execution_id = next_plot_execution_id();
|
|
d->context->performance_plot_id.store(d->plot_execution_id, std::memory_order_release);
|
|
d->context->owner.store(this, std::memory_order_release);
|
|
if (d->flow)
|
|
d->flow->attach(*d);
|
|
Global::instance()->render_scheduler().register_plot(this);
|
|
}
|
|
Plot_Core::~Plot_Core() {
|
|
d->context->destroying.store(true, std::memory_order_release);
|
|
d->destroying_token->store(true, std::memory_order_release);
|
|
d->generation.fetch_add(1, std::memory_order_acq_rel);
|
|
if (d->flow)
|
|
d->flow->shutdown();
|
|
Global::instance()->render_scheduler().remove_plot(this);
|
|
if (d->flow)
|
|
d->flow->cancel_all_update_states();
|
|
d->context->owner.store(nullptr, std::memory_order_release);
|
|
}
|
|
void Plot_Core::init() {
|
|
if (!d->root) {
|
|
d->root = std::make_shared<Renderable>();
|
|
d->root->set_object_name("Root_Renderable");
|
|
d->root->init_root(this);
|
|
}
|
|
d->context->first_resize.store(true, std::memory_order_release);
|
|
}
|
|
std::shared_ptr<Renderable> Plot_Core::root_renderable() const {
|
|
return d->root;
|
|
}
|
|
std::shared_ptr<Renderable> Plot_Core::create_renderable_node(
|
|
const std::shared_ptr<Renderable>& parent, std::string object_name) {
|
|
if (!parent)
|
|
return {};
|
|
auto node = std::make_shared<Renderable>();
|
|
node->set_object_name(std::move(object_name));
|
|
node->init(parent);
|
|
return node;
|
|
}
|
|
void Plot_Core::remove_renderable(const std::shared_ptr<Renderable>& renderable) {
|
|
if (!renderable)
|
|
return;
|
|
if (renderable == d->root)
|
|
d->root.reset();
|
|
else
|
|
renderable->detach_from_parent();
|
|
notify_model_dirty();
|
|
}
|
|
void Plot_Core::set_background_color(Color color) {
|
|
d->context->background_rgba.store(premultiply(color), std::memory_order_release);
|
|
notify_model_dirty(Dirty_Reason::Config);
|
|
}
|
|
Color Plot_Core::background_color() const {
|
|
return d->background_color();
|
|
}
|
|
void Plot_Core::set_viewport_size(Size size) {
|
|
Size previous{
|
|
d->context->render_width.load(std::memory_order_acquire),
|
|
d->context->render_height.load(std::memory_order_acquire)
|
|
};
|
|
d->context->render_width.store(size.width, std::memory_order_release);
|
|
d->context->render_height.store(size.height, std::memory_order_release);
|
|
if (previous != size)
|
|
d->context->viewport_version.fetch_add(1, std::memory_order_acq_rel);
|
|
if (!size.empty())
|
|
d->context->first_resize.store(false, std::memory_order_release);
|
|
if (previous != size) {
|
|
d->pending_dirty_reason_mask.fetch_or(dirty_reason_bit(Dirty_Reason::Viewport), std::memory_order_acq_rel);
|
|
d->post_scheduler_action([data = d, size]() {
|
|
data->notify_viewport_changed(size, steady_now_ns());
|
|
});
|
|
}
|
|
}
|
|
Size Plot_Core::viewport_size() const {
|
|
return {
|
|
d->context->render_width.load(std::memory_order_acquire),
|
|
d->context->render_height.load(std::memory_order_acquire)
|
|
};
|
|
}
|
|
void Plot_Core::dispatch_event(const Event& event) {
|
|
d->dispatch_event(event);
|
|
}
|
|
void Plot_Core::notify_model_dirty(Dirty_Reason reason) {
|
|
if (d->destroying())
|
|
return;
|
|
std::uint64_t now_ns = steady_now_ns();
|
|
if (d->flow)
|
|
d->flow->advance_edit_version();
|
|
d->pending_dirty_reason_mask.fetch_or(dirty_reason_bit(reason), std::memory_order_acq_rel);
|
|
d->latest_input_time_ns.store(now_ns, std::memory_order_release);
|
|
d->pending_input_count.fetch_add(1, std::memory_order_relaxed);
|
|
d->post_scheduler_action([data = d, reason, now_ns]() {
|
|
data->notify_model_dirty(reason, now_ns);
|
|
});
|
|
}
|
|
void Plot_Core::request_explicit_render() {
|
|
if (d->destroying())
|
|
return;
|
|
d->post_scheduler_action([data = d]() {
|
|
data->notify_model_dirty(Dirty_Reason::Core_Data, steady_now_ns());
|
|
});
|
|
}
|
|
void Plot_Core::activate_view() {
|
|
if (d->destroying())
|
|
return;
|
|
d->post_scheduler_action([data = d]() {
|
|
data->notify_started();
|
|
});
|
|
}
|
|
void Plot_Core::deactivate_view() {
|
|
d->post_scheduler_action([data = d]() {
|
|
data->notify_paused();
|
|
});
|
|
}
|
|
bool Plot_Core::view_active() const {
|
|
return d->flow && d->flow->view_active();
|
|
}
|
|
void Plot_Core::set_presentation_sink(std::weak_ptr<Presentation_Sink> sink) {
|
|
d->sink = std::move(sink);
|
|
}
|
|
std::unique_ptr<Renderable_Runtime> Plot_Core::create_renderable_runtime(Renderable& owner) const {
|
|
return d->flow ? d->flow->create_renderable_runtime(owner) : nullptr;
|
|
}
|
|
Present_Surface_Lease Plot_Core::begin_present() {
|
|
Present_Surface_Lease result;
|
|
if (!d->root || !d->flow)
|
|
return result;
|
|
std::shared_ptr<Frame_Lifecycle_Record> returned_frame;
|
|
result = d->flow->begin_present(steady_now_ns(), returned_frame);
|
|
if (returned_frame) {
|
|
d->post_scheduler_action([data = d, frame = std::move(returned_frame)]() mutable {
|
|
data->notify_frame_returned(frame);
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
void Plot_Core::end_present(Present_Surface_Lease&& frame, std::uint64_t paint_begin_ns, std::uint64_t paint_end_ns) {
|
|
if (d->flow) {
|
|
if (auto returned = d->flow->end_present(std::move(frame), paint_begin_ns, paint_end_ns))
|
|
d->collect_frame(returned);
|
|
}
|
|
d->post_scheduler_action([data = d]() mutable {
|
|
data->notify_paint_finished();
|
|
});
|
|
}
|
|
|
|
std::shared_ptr<Plot_Render_Context> Plot_Core_Context_Access::render_context(Plot_Core& plot) {
|
|
return plot.d ? plot.d->context : std::shared_ptr<Plot_Render_Context>{};
|
|
}
|
|
|
|
std::shared_ptr<Plot_Render_Context> Plot_Core_Context_Access::render_context(const Plot_Core& plot) {
|
|
return plot.d ? plot.d->context : std::shared_ptr<Plot_Render_Context>{};
|
|
}
|
|
} // namespace renderive
|