策略抽离
This commit is contained in:
@@ -27,6 +27,14 @@ double Low_Latency_Smoothed_Interval::safe_ms(double jitter_factor) const {
|
||||
return sample_count ? srtt_ms + jitter_factor * jitter_ms : 0.0;
|
||||
}
|
||||
|
||||
std::uint64_t Low_Latency_Smoothed_Interval::srtt_ns() const {
|
||||
return sample_count ? static_cast<std::uint64_t>(srtt_ms * 1000000.0) : 0;
|
||||
}
|
||||
|
||||
std::uint64_t Low_Latency_Smoothed_Interval::jitter_ns() const {
|
||||
return sample_count ? static_cast<std::uint64_t>(jitter_ms * 1000000.0) : 0;
|
||||
}
|
||||
|
||||
bool Low_Latency_Smoothed_Interval::valid() const {
|
||||
return sample_count > 0;
|
||||
}
|
||||
@@ -43,11 +51,16 @@ void Low_Latency_Feedback_Controller::set_max_render_fps(double fps) {
|
||||
refresh_control_snapshot();
|
||||
}
|
||||
|
||||
void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t, std::uint64_t input_version) {
|
||||
void Low_Latency_Feedback_Controller::on_input_changed(std::uint64_t now_ns, std::uint64_t input_version, Dirty_Reason reason) {
|
||||
if (reason == Dirty_Reason::Core_Data && last_input_change_ns && now_ns > last_input_change_ns)
|
||||
input_period.update(static_cast<double>(now_ns - last_input_change_ns) / 1000000.0);
|
||||
if (reason == Dirty_Reason::Core_Data)
|
||||
last_input_change_ns = now_ns;
|
||||
if (input_version)
|
||||
latest_input_version = input_version;
|
||||
else
|
||||
++latest_input_version;
|
||||
refresh_control_snapshot();
|
||||
}
|
||||
|
||||
void Low_Latency_Feedback_Controller::on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) {
|
||||
@@ -62,6 +75,8 @@ void Low_Latency_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record
|
||||
std::uint64_t render_duration_ns = frame.render_end_ns > frame.render_begin_ns ? frame.render_end_ns - frame.render_begin_ns : 0;
|
||||
last_render_begin_ns = frame.render_begin_ns;
|
||||
render_period.update(static_cast<double>(render_duration_ns + frame.render_queue_wait_ns) / 1000000.0);
|
||||
if (frame.ready_queue_wait_ns)
|
||||
ready_queue_period.update(static_cast<double>(frame.ready_queue_wait_ns) / 1000000.0);
|
||||
refresh_control_snapshot();
|
||||
write_state(frame);
|
||||
}
|
||||
@@ -69,18 +84,38 @@ void Low_Latency_Feedback_Controller::on_render_completed(Frame_Lifecycle_Record
|
||||
void Low_Latency_Feedback_Controller::on_frame_presented(Frame_Lifecycle_Record& frame) {
|
||||
std::uint64_t paint_duration_ns = frame.paint_end_ns > frame.paint_begin_ns ? frame.paint_end_ns - frame.paint_begin_ns : 0;
|
||||
paint_period.update(static_cast<double>(paint_duration_ns + frame.present_queue_wait_ns) / 1000000.0);
|
||||
if (frame.update_request_time_ns && frame.paint_begin_ns > frame.update_request_time_ns)
|
||||
gui_period.update(static_cast<double>(frame.paint_begin_ns - frame.update_request_time_ns) / 1000000.0);
|
||||
if (frame.paint_return_wait_ns)
|
||||
consumption_period.update(static_cast<double>(frame.paint_return_wait_ns) / 1000000.0);
|
||||
if (frame.core_data_time_ns && frame.paint_end_ns > frame.core_data_time_ns)
|
||||
frame_age_period.update(static_cast<double>(frame.paint_end_ns - frame.core_data_time_ns) / 1000000.0);
|
||||
++presented_frame_count;
|
||||
frame.outcome = Frame_Outcome::Presented;
|
||||
refresh_control_snapshot();
|
||||
write_state(frame);
|
||||
}
|
||||
|
||||
void Low_Latency_Feedback_Controller::on_frame_superseded(Frame_Lifecycle_Record& frame) {
|
||||
++superseded_frame_count;
|
||||
frame.outcome = Frame_Outcome::Superseded;
|
||||
refresh_control_snapshot();
|
||||
write_state(frame);
|
||||
}
|
||||
|
||||
void Low_Latency_Feedback_Controller::on_frame_repeated() {
|
||||
++repeated_frame_count;
|
||||
refresh_control_snapshot();
|
||||
}
|
||||
|
||||
void Low_Latency_Feedback_Controller::on_frame_underrun() {
|
||||
++underrun_count;
|
||||
refresh_control_snapshot();
|
||||
}
|
||||
|
||||
void Low_Latency_Feedback_Controller::on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome) {
|
||||
if (outcome == Frame_Outcome::Paint_Acquire_Dropped || outcome == Frame_Outcome::Render_Acquire_Dropped)
|
||||
++underrun_count;
|
||||
frame.outcome = outcome;
|
||||
refresh_control_snapshot();
|
||||
write_state(frame);
|
||||
@@ -105,8 +140,9 @@ void Low_Latency_Feedback_Controller::write_state(Frame_Lifecycle_Record& frame)
|
||||
void Low_Latency_Feedback_Controller::refresh_control_snapshot() {
|
||||
std::uint64_t render_period_ns = render_period.valid() ? ms_to_ns(render_period.safe_ms(0.5)) : 0;
|
||||
std::uint64_t paint_period_ns = paint_period.valid() ? ms_to_ns(paint_period.safe_ms(0.5)) : 0;
|
||||
std::uint64_t input_period_ns = input_period.valid() ? ms_to_ns(input_period.safe_ms(0.5)) : 0;
|
||||
std::uint64_t user_period_ns = state_value.user_period_ns;
|
||||
Refresh_Limit_Source candidate = classify_limit_source(render_period_ns, paint_period_ns, user_period_ns);
|
||||
Refresh_Limit_Source candidate = classify_limit_source(std::max(render_period_ns, input_period_ns), paint_period_ns, user_period_ns);
|
||||
std::uint64_t candidate_period = 0;
|
||||
switch (candidate) {
|
||||
case Refresh_Limit_Source::Render:
|
||||
@@ -122,14 +158,43 @@ void Low_Latency_Feedback_Controller::refresh_control_snapshot() {
|
||||
break;
|
||||
}
|
||||
update_limit_source(candidate, candidate_period);
|
||||
std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, user_period_ns});
|
||||
std::uint64_t target_period = std::max({render_period_ns, paint_period_ns, input_period_ns, user_period_ns});
|
||||
Low_Latency_Bottleneck_State bottleneck = Low_Latency_Bottleneck_State::Unknown;
|
||||
if (candidate == Refresh_Limit_Source::User)
|
||||
bottleneck = Low_Latency_Bottleneck_State::User_Limited;
|
||||
else if (paint_period_ns && paint_period_ns == target_period)
|
||||
bottleneck = Low_Latency_Bottleneck_State::Consumer_Limited;
|
||||
else if (render_period_ns && render_period_ns == target_period)
|
||||
bottleneck = Low_Latency_Bottleneck_State::Producer_Limited;
|
||||
else if (input_period_ns && input_period_ns == target_period)
|
||||
bottleneck = Low_Latency_Bottleneck_State::Input_Limited;
|
||||
state_value.render_period_ns = render_period_ns;
|
||||
state_value.render_jitter_ns = render_period.jitter_ns();
|
||||
state_value.paint_period_ns = paint_period_ns;
|
||||
state_value.paint_jitter_ns = paint_period.jitter_ns();
|
||||
state_value.input_period_ns = input_period_ns;
|
||||
state_value.input_jitter_ns = input_period.jitter_ns();
|
||||
state_value.ready_queue_srtt_ns = ready_queue_period.srtt_ns();
|
||||
state_value.ready_queue_jitter_ns = ready_queue_period.jitter_ns();
|
||||
state_value.gui_srtt_ns = gui_period.srtt_ns();
|
||||
state_value.gui_jitter_ns = gui_period.jitter_ns();
|
||||
state_value.consumption_srtt_ns = consumption_period.srtt_ns();
|
||||
state_value.consumption_jitter_ns = consumption_period.jitter_ns();
|
||||
state_value.frame_age_srtt_ns = frame_age_period.srtt_ns();
|
||||
state_value.frame_age_jitter_ns = frame_age_period.jitter_ns();
|
||||
state_value.bottleneck = bottleneck;
|
||||
state_value.production_strategy = bottleneck == Low_Latency_Bottleneck_State::Consumer_Limited ? Low_Latency_Production_Strategy::Cost_Balanced : Low_Latency_Production_Strategy::Latency_Eager;
|
||||
state_value.target_period_ns = target_period;
|
||||
state_value.presented_frame_count = presented_frame_count;
|
||||
state_value.superseded_frame_count = superseded_frame_count;
|
||||
state_value.repeated_frame_count = repeated_frame_count;
|
||||
state_value.underrun_count = underrun_count;
|
||||
std::uint64_t total_display_attempts = presented_frame_count + superseded_frame_count + repeated_frame_count + underrun_count;
|
||||
state_value.display_efficiency = total_display_attempts ? static_cast<double>(presented_frame_count) / static_cast<double>(total_display_attempts) : 1.0;
|
||||
if (state_value.source == Refresh_Limit_Source::Unknown)
|
||||
state_value.source = candidate;
|
||||
if (state_value.source == Refresh_Limit_Source::User && last_render_begin_ns)
|
||||
next_render_time = last_render_begin_ns + user_period_ns;
|
||||
if ((state_value.source == Refresh_Limit_Source::User || state_value.production_strategy == Low_Latency_Production_Strategy::Cost_Balanced) && last_render_begin_ns && state_value.target_period_ns)
|
||||
next_render_time = last_render_begin_ns + state_value.target_period_ns;
|
||||
else
|
||||
next_render_time = 0;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ struct Low_Latency_Smoothed_Interval {
|
||||
std::uint64_t sample_count{};
|
||||
void update(double sample_ms);
|
||||
[[nodiscard]] double safe_ms(double jitter_factor = 2.0) const;
|
||||
[[nodiscard]] std::uint64_t srtt_ns() const;
|
||||
[[nodiscard]] std::uint64_t jitter_ns() const;
|
||||
[[nodiscard]] bool valid() const;
|
||||
};
|
||||
|
||||
@@ -23,11 +25,13 @@ class Low_Latency_Feedback_Controller {
|
||||
public:
|
||||
void reset();
|
||||
void set_max_render_fps(double fps);
|
||||
void on_input_changed(std::uint64_t now_ns, std::uint64_t input_version = 0);
|
||||
void on_input_changed(std::uint64_t now_ns, std::uint64_t input_version = 0, Dirty_Reason reason = Dirty_Reason::Core_Data);
|
||||
void on_render_started(Frame_Lifecycle_Record& frame, std::uint64_t now_ns);
|
||||
void on_render_completed(Frame_Lifecycle_Record& frame);
|
||||
void on_frame_presented(Frame_Lifecycle_Record& frame);
|
||||
void on_frame_superseded(Frame_Lifecycle_Record& frame);
|
||||
void on_frame_repeated();
|
||||
void on_frame_underrun();
|
||||
void on_frame_dropped(Frame_Lifecycle_Record& frame, Frame_Outcome outcome);
|
||||
[[nodiscard]] bool rate_limit_ready(std::uint64_t now_ns) const;
|
||||
[[nodiscard]] std::uint64_t next_render_time_ns() const;
|
||||
@@ -43,11 +47,21 @@ private:
|
||||
Refresh_Control_Snapshot state_value;
|
||||
Low_Latency_Smoothed_Interval render_period;
|
||||
Low_Latency_Smoothed_Interval paint_period;
|
||||
Low_Latency_Smoothed_Interval input_period;
|
||||
Low_Latency_Smoothed_Interval ready_queue_period;
|
||||
Low_Latency_Smoothed_Interval gui_period;
|
||||
Low_Latency_Smoothed_Interval consumption_period;
|
||||
Low_Latency_Smoothed_Interval frame_age_period;
|
||||
Refresh_Limit_Source candidate_source = Refresh_Limit_Source::Unknown;
|
||||
int candidate_count{};
|
||||
std::uint64_t latest_input_version{};
|
||||
std::uint64_t last_input_change_ns{};
|
||||
std::uint64_t last_render_begin_ns{};
|
||||
std::uint64_t next_render_time{};
|
||||
std::uint64_t presented_frame_count{};
|
||||
std::uint64_t superseded_frame_count{};
|
||||
std::uint64_t repeated_frame_count{};
|
||||
std::uint64_t underrun_count{};
|
||||
};
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -62,6 +62,17 @@ struct Low_Latency_Frame_Flow::Private {
|
||||
return;
|
||||
}
|
||||
Refresh_Control_Snapshot control = feedback.state();
|
||||
if (control.production_strategy == Low_Latency_Production_Strategy::Cost_Balanced) {
|
||||
if (runtime.middle_has_new_frame)
|
||||
return;
|
||||
std::uint64_t next_ns = feedback.next_render_time_ns();
|
||||
if (next_ns && !feedback.rate_limit_ready(runtime.now_ns)) {
|
||||
host->schedule_render_deadline(next_ns);
|
||||
return;
|
||||
}
|
||||
(void)host->try_render_once();
|
||||
return;
|
||||
}
|
||||
switch (control.source) {
|
||||
case Refresh_Limit_Source::Paint:
|
||||
if (!runtime.middle_has_new_frame)
|
||||
@@ -116,6 +127,10 @@ void Low_Latency_Frame_Flow::attach(Frame_Flow_Host& host) {
|
||||
d->host = &host;
|
||||
}
|
||||
|
||||
Renderable_Runtime_Strategy Low_Latency_Frame_Flow::renderable_runtime_strategy() const {
|
||||
return Renderable_Runtime_Strategy::Low_Latency;
|
||||
}
|
||||
|
||||
Frame_Flow_Runtime_Snapshot Low_Latency_Frame_Flow::runtime_snapshot(std::uint64_t now_ns, bool destroying) const {
|
||||
return {
|
||||
destroying,
|
||||
@@ -169,6 +184,8 @@ Render_Surface_Publish_Result Low_Latency_Frame_Flow::publish_rendered_frame(Ren
|
||||
if (handle->lease.frame && handle->lease.frame->metadata)
|
||||
handle->lease.frame->version.store(handle->lease.frame->metadata->frame_id, std::memory_order_release);
|
||||
result = to_surface_result(d->pipeline.try_publish_rendered_frame(std::move(handle->lease), now_ns));
|
||||
if (result.dropped_frame_record)
|
||||
d->feedback.on_frame_dropped(*result.dropped_frame_record, result.dropped_frame_record->outcome);
|
||||
}
|
||||
d->pipeline.render_job_state = Frame_Job_State::Idle;
|
||||
return result;
|
||||
@@ -181,6 +198,10 @@ Render_Surface_Publish_Result Low_Latency_Frame_Flow::request_present(std::uint6
|
||||
Present_Surface_Lease Low_Latency_Frame_Flow::begin_present(std::uint64_t now_ns, std::shared_ptr<Frame_Lifecycle_Record>& returned_frame_record) {
|
||||
auto consumed = d->pipeline.try_acquire_paint_frame(now_ns);
|
||||
returned_frame_record = std::move(consumed.returned_frame_record);
|
||||
if (consumed.paint_frame_unavailable)
|
||||
d->feedback.on_frame_underrun();
|
||||
else if (consumed.lease && !consumed.has_new_frame)
|
||||
d->feedback.on_frame_repeated();
|
||||
if (!consumed.lease)
|
||||
return Present_Surface_Lease_Access::empty(consumed.has_new_frame, consumed.present_request_was_pending);
|
||||
return Present_Surface_Lease_Access::from_handle<Low_Latency_Present_Surface_Handle>(
|
||||
@@ -220,8 +241,8 @@ bool Low_Latency_Frame_Flow::view_active() const {
|
||||
return d->active.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Low_Latency_Frame_Flow::notify_model_dirty(std::uint64_t now_ns) {
|
||||
d->feedback.on_input_changed(now_ns);
|
||||
void Low_Latency_Frame_Flow::notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) {
|
||||
d->feedback.on_input_changed(now_ns, 0, reason);
|
||||
d->evaluate(now_ns);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
[[nodiscard]] Low_Latency_Diagnostics low_latency_diagnostics() const;
|
||||
|
||||
void attach(Frame_Flow_Host& host) override;
|
||||
[[nodiscard]] Renderable_Runtime_Strategy renderable_runtime_strategy() const override;
|
||||
[[nodiscard]] Frame_Flow_Runtime_Snapshot runtime_snapshot(std::uint64_t now_ns, bool destroying) const override;
|
||||
void advance_edit_version() override;
|
||||
void cancel_all_update_states() override;
|
||||
@@ -30,7 +31,10 @@ public:
|
||||
void activate_view(std::uint64_t now_ns) override;
|
||||
void deactivate_view() override;
|
||||
[[nodiscard]] bool view_active() const override;
|
||||
void notify_model_dirty(std::uint64_t now_ns) override;
|
||||
void notify_model_dirty(std::uint64_t now_ns) {
|
||||
notify_model_dirty(Dirty_Reason::Core_Data, now_ns);
|
||||
}
|
||||
void notify_model_dirty(Dirty_Reason reason, std::uint64_t now_ns) override;
|
||||
void notify_viewport_changed(Size viewport, std::uint64_t now_ns) override;
|
||||
void notify_render_finished(Frame_Lifecycle_Record& frame, std::uint64_t now_ns) override;
|
||||
void notify_paint_finished(std::uint64_t now_ns) override;
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
#include "Render_Data.h"
|
||||
#include "../../architecture/Strategy_Render_Data.h"
|
||||
#include "../../architecture/Renderable.h"
|
||||
#include "../../base/Memory.h"
|
||||
#include "Low_Latency_Roles.h"
|
||||
#include "Triple_Buffer.h"
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
namespace renderive {
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const Range& range) {
|
||||
stream << "Range(" << range.origin << ", " << range.target << ")";
|
||||
return stream;
|
||||
@@ -10,70 +18,353 @@ std::ostream& operator<<(std::ostream& stream, const Range& range) {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Low_Latency_State_Read_Handle final : Render_State_Read_Handle {
|
||||
Low_Latency_State_Read_Handle(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease)
|
||||
: data(data), lease(lease) {}
|
||||
struct Input_Buffer_Slot {
|
||||
Input_Data* data{};
|
||||
std::uint64_t version = 0;
|
||||
bool pending = false;
|
||||
std::shared_ptr<Update_State> completion_state;
|
||||
};
|
||||
|
||||
~Low_Latency_State_Read_Handle() override {
|
||||
if (data && lease)
|
||||
data->unmark_state(lease);
|
||||
std::uint8_t state_role_index(Renderable_State_Role role) {
|
||||
switch (role) {
|
||||
case Renderable_State_Role::Edit:
|
||||
return State_Edit;
|
||||
case Renderable_State_Role::Ready:
|
||||
return State_Ready;
|
||||
case Renderable_State_Role::Render:
|
||||
return State_Render;
|
||||
}
|
||||
return State_Render;
|
||||
}
|
||||
|
||||
Low_Latency_Render_Data* data{};
|
||||
std::uint8_t input_role_index(Renderable_Input_Role role) {
|
||||
switch (role) {
|
||||
case Renderable_Input_Role::Edit:
|
||||
return Input_Edit;
|
||||
case Renderable_Input_Role::Ready:
|
||||
return Input_Ready;
|
||||
case Renderable_Input_Role::Render:
|
||||
return Input_Render;
|
||||
}
|
||||
return Input_Render;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct Strategy_Render_Data_Private {
|
||||
Renderable_Runtime_Strategy runtime_strategy_value = Renderable_Runtime_Strategy::Low_Latency;
|
||||
Triple_Role_Buffer_Control state_control;
|
||||
std::array<Render_State*, 3> state_buffers{};
|
||||
std::array<Render_Object_Owner, 3> state_buffer_owners{};
|
||||
Triple_Role_Buffer_Control input_control;
|
||||
std::array<Input_Buffer_Slot, 3> input_buffers{};
|
||||
std::array<Render_Object_Owner, 3> input_buffer_owners{};
|
||||
std::pmr::vector<std::shared_ptr<Update_State>> committed_update_states{memory_resource(Memory_Domain::Update_Completion)};
|
||||
std::uint64_t input_version = 0;
|
||||
mutable std::mutex explicit_state_mutex;
|
||||
mutable std::mutex explicit_input_mutex;
|
||||
Render_Object_Owner explicit_state_owner;
|
||||
Render_State* explicit_state{};
|
||||
Render_Object_Owner explicit_input_owner;
|
||||
Input_Buffer_Slot explicit_input;
|
||||
std::uint64_t explicit_input_version = 0;
|
||||
|
||||
Render_State* state_by_index(std::uint8_t index) const {
|
||||
return index < state_buffers.size() ? state_buffers[index] : nullptr;
|
||||
}
|
||||
Render_State* state(std::uint8_t role) const {
|
||||
if (runtime_strategy_value == Renderable_Runtime_Strategy::Explicit)
|
||||
return explicit_state;
|
||||
return state_by_index(state_control.read_view()[role]);
|
||||
}
|
||||
Input_Data* input_data_by_index(std::uint8_t index) const {
|
||||
return index < input_buffers.size() ? input_buffers[index].data : nullptr;
|
||||
}
|
||||
Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) {
|
||||
return input_buffers[index];
|
||||
}
|
||||
const Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) const {
|
||||
return input_buffers[index];
|
||||
}
|
||||
Input_Buffer_Slot& input_slot(std::uint8_t role) {
|
||||
return input_slot_by_index(input_control.read_view()[role]);
|
||||
}
|
||||
const Input_Buffer_Slot& input_slot(std::uint8_t role) const {
|
||||
return input_slot_by_index(input_control.read_view()[role]);
|
||||
}
|
||||
Input_Data* input_data(std::uint8_t role) const {
|
||||
if (runtime_strategy_value == Renderable_Runtime_Strategy::Explicit)
|
||||
return explicit_input.data;
|
||||
return input_slot(role).data;
|
||||
}
|
||||
Triple_Buffer_Lease try_mark_state_role(std::uint8_t role) {
|
||||
auto record = state_control.try_acquire_role(role);
|
||||
return record.result == Triple_Buffer_Result::Success ? record.lease : Triple_Buffer_Lease{};
|
||||
}
|
||||
Triple_Buffer_Lease mark_state_role(std::uint8_t role) {
|
||||
auto record = state_control.acquire_role(role);
|
||||
return record.result == Triple_Buffer_Result::Success ? record.lease : Triple_Buffer_Lease{};
|
||||
}
|
||||
void mark_input_dirty(Triple_Buffer_Lease lease) {
|
||||
Input_Buffer_Slot& slot = input_slot_by_index(lease.index);
|
||||
slot.version = ++input_version;
|
||||
slot.pending = true;
|
||||
}
|
||||
void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr<Update_State> state) {
|
||||
Input_Buffer_Slot& slot = input_slot_by_index(lease.index);
|
||||
if (slot.completion_state && slot.completion_state != state)
|
||||
slot.completion_state->complete_all({}, Update_Outcome::Superseded);
|
||||
slot.completion_state = std::move(state);
|
||||
}
|
||||
void cancel_pending_completions() {
|
||||
for (Input_Buffer_Slot& slot : input_buffers) {
|
||||
if (!slot.completion_state)
|
||||
continue;
|
||||
slot.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
|
||||
slot.completion_state.reset();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(explicit_input_mutex);
|
||||
if (explicit_input.completion_state) {
|
||||
explicit_input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
|
||||
explicit_input.completion_state.reset();
|
||||
}
|
||||
}
|
||||
cancel_update_states(committed_update_states);
|
||||
}
|
||||
void clear_state_buffers() {
|
||||
state_control.close_and_wait();
|
||||
for (std::size_t i = 0; i < state_buffers.size(); ++i) {
|
||||
state_buffer_owners[i].reset();
|
||||
state_buffers[i] = nullptr;
|
||||
}
|
||||
state_control.reset();
|
||||
}
|
||||
void clear_input_buffers() {
|
||||
input_control.close_and_wait();
|
||||
cancel_pending_completions();
|
||||
for (std::size_t i = 0; i < input_buffers.size(); ++i) {
|
||||
input_buffer_owners[i].reset();
|
||||
input_buffers[i].data = nullptr;
|
||||
input_buffers[i].version = 0;
|
||||
input_buffers[i].pending = false;
|
||||
input_buffers[i].completion_state.reset();
|
||||
}
|
||||
input_version = 0;
|
||||
input_control.reset();
|
||||
}
|
||||
void clear_explicit_runtime() {
|
||||
{
|
||||
std::lock_guard<std::mutex> input_lock(explicit_input_mutex);
|
||||
if (explicit_input.completion_state) {
|
||||
explicit_input.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
|
||||
explicit_input.completion_state.reset();
|
||||
}
|
||||
explicit_input.data = nullptr;
|
||||
explicit_input.version = 0;
|
||||
explicit_input.pending = false;
|
||||
explicit_input_owner.reset();
|
||||
explicit_input_version = 0;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(explicit_state_mutex);
|
||||
explicit_state = nullptr;
|
||||
explicit_state_owner.reset();
|
||||
}
|
||||
}
|
||||
void initialize_low_latency_runtime_buffers(
|
||||
Render_Object_Owner edit_state,
|
||||
Render_Object_Owner ready_state,
|
||||
Render_Object_Owner render_state,
|
||||
Render_Object_Owner edit_input,
|
||||
Render_Object_Owner ready_input,
|
||||
Render_Object_Owner render_input) {
|
||||
clear_state_buffers();
|
||||
clear_input_buffers();
|
||||
clear_explicit_runtime();
|
||||
state_buffer_owners[State_Edit] = std::move(edit_state);
|
||||
state_buffer_owners[State_Ready] = std::move(ready_state);
|
||||
state_buffer_owners[State_Render] = std::move(render_state);
|
||||
state_buffers[State_Edit] = state_buffer_owners[State_Edit].get<Render_State>();
|
||||
state_buffers[State_Ready] = state_buffer_owners[State_Ready].get<Render_State>();
|
||||
state_buffers[State_Render] = state_buffer_owners[State_Render].get<Render_State>();
|
||||
input_buffer_owners[Input_Edit] = std::move(edit_input);
|
||||
input_buffer_owners[Input_Ready] = std::move(ready_input);
|
||||
input_buffer_owners[Input_Render] = std::move(render_input);
|
||||
if (input_buffer_owners[Input_Edit])
|
||||
input_buffers[Input_Edit].data = input_buffer_owners[Input_Edit].get<Input_Data>();
|
||||
if (input_buffer_owners[Input_Ready])
|
||||
input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get<Input_Data>();
|
||||
if (input_buffer_owners[Input_Render])
|
||||
input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get<Input_Data>();
|
||||
if (state_buffers[State_Edit])
|
||||
state_buffers[State_Edit]->version = 1;
|
||||
}
|
||||
void initialize_explicit_runtime_buffers(Render_Object_Owner state, Render_Object_Owner input) {
|
||||
clear_state_buffers();
|
||||
clear_input_buffers();
|
||||
clear_explicit_runtime();
|
||||
explicit_state_owner = std::move(state);
|
||||
explicit_state = explicit_state_owner.get<Render_State>();
|
||||
if (explicit_state)
|
||||
explicit_state->version = 1;
|
||||
explicit_input_owner = std::move(input);
|
||||
explicit_input.data = explicit_input_owner.get<Input_Data>();
|
||||
explicit_input.version = 0;
|
||||
explicit_input.pending = false;
|
||||
explicit_input.completion_state.reset();
|
||||
}
|
||||
void clear_input_role(Renderable_Input_Role role) {
|
||||
if (runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) {
|
||||
std::lock_guard<std::mutex> lock(explicit_input_mutex);
|
||||
if (explicit_input.completion_state) {
|
||||
if (role == Renderable_Input_Role::Render) {
|
||||
explicit_input.completion_state->complete_until(Update_Stage::Committed);
|
||||
committed_update_states.push_back(std::move(explicit_input.completion_state));
|
||||
}
|
||||
else {
|
||||
explicit_input.completion_state->complete_all({}, Update_Outcome::Superseded);
|
||||
explicit_input.completion_state.reset();
|
||||
}
|
||||
}
|
||||
if (explicit_input.data)
|
||||
explicit_input.data->clear();
|
||||
explicit_input.pending = false;
|
||||
return;
|
||||
}
|
||||
Input_Buffer_Slot& slot = input_slot(input_role_index(role));
|
||||
if (slot.completion_state) {
|
||||
if (role == Renderable_Input_Role::Render) {
|
||||
slot.completion_state->complete_until(Update_Stage::Committed);
|
||||
committed_update_states.push_back(std::move(slot.completion_state));
|
||||
}
|
||||
else {
|
||||
slot.completion_state->complete_all({}, Update_Outcome::Superseded);
|
||||
slot.completion_state.reset();
|
||||
}
|
||||
}
|
||||
if (slot.data)
|
||||
slot.data->clear();
|
||||
slot.pending = false;
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct Low_Latency_State_Read_Handle final : Render_State_Read_Handle {
|
||||
Low_Latency_State_Read_Handle(Strategy_Render_Data_Private* runtime, Triple_Buffer_Lease lease)
|
||||
: runtime(runtime), lease(lease) {}
|
||||
~Low_Latency_State_Read_Handle() override {
|
||||
if (runtime && lease)
|
||||
runtime->state_control.unmark_use(lease);
|
||||
}
|
||||
Strategy_Render_Data_Private* runtime{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
};
|
||||
|
||||
struct Low_Latency_State_Edit_Handle final : Render_State_Edit_Handle {
|
||||
Low_Latency_State_Edit_Handle(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease)
|
||||
: data(data), lease(lease) {}
|
||||
|
||||
Low_Latency_State_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, Triple_Buffer_Lease lease)
|
||||
: runtime(runtime), owner(owner), lease(lease) {}
|
||||
~Low_Latency_State_Edit_Handle() override {
|
||||
if (!data || !lease)
|
||||
if (!runtime || !lease)
|
||||
return;
|
||||
++data->state_by_index(lease.index)->version;
|
||||
auto q = data->q_ptr;
|
||||
data->unmark_state(lease);
|
||||
if (q)
|
||||
q->mark_render_dirty();
|
||||
if (auto* state = runtime->state_by_index(lease.index))
|
||||
++state->version;
|
||||
runtime->state_control.unmark_use(lease);
|
||||
if (owner)
|
||||
owner->mark_render_dirty(Dirty_Reason::Config);
|
||||
}
|
||||
|
||||
Low_Latency_Render_Data* data{};
|
||||
Strategy_Render_Data_Private* runtime{};
|
||||
Renderable* owner{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
};
|
||||
|
||||
struct Low_Latency_Input_Edit_Handle final : Render_Input_Edit_Handle {
|
||||
Low_Latency_Input_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, Triple_Buffer_Lease lease, bool notify_render)
|
||||
: runtime(runtime), owner(owner), lease(lease), notify_render(notify_render) {}
|
||||
~Low_Latency_Input_Edit_Handle() override {
|
||||
if (!runtime || !lease)
|
||||
return;
|
||||
runtime->mark_input_dirty(lease);
|
||||
runtime->input_control.unmark_use(lease);
|
||||
if (notify_render && owner)
|
||||
owner->mark_render_dirty();
|
||||
}
|
||||
void set_completion(std::shared_ptr<Update_State> state) override {
|
||||
if (runtime && lease)
|
||||
runtime->set_input_completion(lease, std::move(state));
|
||||
}
|
||||
Strategy_Render_Data_Private* runtime{};
|
||||
Renderable* owner{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
bool notify_render{};
|
||||
};
|
||||
|
||||
struct Explicit_State_Read_Handle final : Render_State_Read_Handle {
|
||||
explicit Explicit_State_Read_Handle(std::unique_lock<std::mutex> lock)
|
||||
: lock(std::move(lock)) {}
|
||||
std::unique_lock<std::mutex> lock;
|
||||
};
|
||||
|
||||
struct Explicit_State_Edit_Handle final : Render_State_Edit_Handle {
|
||||
Explicit_State_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, std::unique_lock<std::mutex> lock)
|
||||
: runtime(runtime), owner(owner), lock(std::move(lock)) {}
|
||||
~Explicit_State_Edit_Handle() override {
|
||||
if (!runtime || !runtime->explicit_state)
|
||||
return;
|
||||
++runtime->explicit_state->version;
|
||||
lock.unlock();
|
||||
if (owner)
|
||||
owner->mark_render_dirty(Dirty_Reason::Config);
|
||||
}
|
||||
Strategy_Render_Data_Private* runtime{};
|
||||
Renderable* owner{};
|
||||
std::unique_lock<std::mutex> lock;
|
||||
};
|
||||
|
||||
struct Explicit_Input_Edit_Handle final : Render_Input_Edit_Handle {
|
||||
Explicit_Input_Edit_Handle(Strategy_Render_Data_Private* runtime, Renderable* owner, std::unique_lock<std::mutex> lock, bool notify_render)
|
||||
: runtime(runtime), owner(owner), lock(std::move(lock)), notify_render(notify_render) {}
|
||||
~Explicit_Input_Edit_Handle() override {
|
||||
if (!runtime)
|
||||
return;
|
||||
runtime->explicit_input.version = ++runtime->explicit_input_version;
|
||||
runtime->explicit_input.pending = true;
|
||||
lock.unlock();
|
||||
if (notify_render && owner)
|
||||
owner->mark_render_dirty();
|
||||
}
|
||||
void set_completion(std::shared_ptr<Update_State> state) override {
|
||||
if (!runtime)
|
||||
return;
|
||||
if (runtime->explicit_input.completion_state && runtime->explicit_input.completion_state != state)
|
||||
runtime->explicit_input.completion_state->complete_all({}, Update_Outcome::Superseded);
|
||||
runtime->explicit_input.completion_state = std::move(state);
|
||||
}
|
||||
Strategy_Render_Data_Private* runtime{};
|
||||
Renderable* owner{};
|
||||
std::unique_lock<std::mutex> lock;
|
||||
bool notify_render{};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Render_Edit_Guard::Render_Edit_Guard(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease) : data(data), lease(lease) {}
|
||||
Render_Edit_Guard::~Render_Edit_Guard() {
|
||||
if (!data || !lease)
|
||||
return;
|
||||
++data->state_by_index(lease.index)->version;
|
||||
auto q = data->q_ptr;
|
||||
data->unmark_state(lease);
|
||||
if (q)
|
||||
q->mark_render_dirty();
|
||||
}
|
||||
void Low_Latency_Render_Data::mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render) {
|
||||
Input_Buffer_Slot& slot = input_slot_by_index(lease.index);
|
||||
slot.version = ++input_version;
|
||||
slot.pending = true;
|
||||
if (notify_render && q_ptr)
|
||||
q_ptr->mark_render_dirty();
|
||||
}
|
||||
void Low_Latency_Render_Data::set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr<Update_State> state) {
|
||||
Input_Buffer_Slot& slot = input_slot_by_index(lease.index);
|
||||
if (slot.completion_state && slot.completion_state != state)
|
||||
slot.completion_state->complete_all({}, Update_Outcome::Superseded);
|
||||
slot.completion_state = std::move(state);
|
||||
}
|
||||
void Low_Latency_Render_Data::drain_committed_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& output) {
|
||||
for (auto& state : committed_update_states)
|
||||
output.push_back(std::move(state));
|
||||
committed_update_states.clear();
|
||||
Strategy_Render_Data::Strategy_Render_Data()
|
||||
: runtime(std::make_unique<Strategy_Render_Data_Private>()) {}
|
||||
|
||||
Strategy_Render_Data::~Strategy_Render_Data() {
|
||||
runtime->clear_state_buffers();
|
||||
runtime->clear_input_buffers();
|
||||
runtime->clear_explicit_runtime();
|
||||
}
|
||||
|
||||
void Low_Latency_Render_Data::initialize_runtime(Renderable& owner) {
|
||||
initialize_runtime_buffers(
|
||||
void Strategy_Render_Data::initialize_runtime(Renderable& owner, Renderable_Runtime_Strategy strategy) {
|
||||
runtime->runtime_strategy_value = strategy;
|
||||
if (strategy == Renderable_Runtime_Strategy::Explicit) {
|
||||
runtime->initialize_explicit_runtime_buffers(owner.create_render_state(), owner.create_input_data());
|
||||
return;
|
||||
}
|
||||
runtime->initialize_low_latency_runtime_buffers(
|
||||
owner.create_render_state(),
|
||||
owner.create_render_state(),
|
||||
owner.create_render_state(),
|
||||
@@ -82,105 +373,147 @@ void Low_Latency_Render_Data::initialize_runtime(Renderable& owner) {
|
||||
owner.create_input_data());
|
||||
}
|
||||
|
||||
void Low_Latency_Render_Data::initialize_runtime_buffers(
|
||||
Render_Object_Owner edit_state,
|
||||
Render_Object_Owner ready_state,
|
||||
Render_Object_Owner render_state,
|
||||
Render_Object_Owner edit_input,
|
||||
Render_Object_Owner ready_input,
|
||||
Render_Object_Owner render_input) {
|
||||
clear_state_buffers();
|
||||
clear_input_buffers();
|
||||
state_buffer_owners[State_Edit] = std::move(edit_state);
|
||||
state_buffer_owners[State_Ready] = std::move(ready_state);
|
||||
state_buffer_owners[State_Render] = std::move(render_state);
|
||||
state_buffers[State_Edit] = state_buffer_owners[State_Edit].get<Render_State>();
|
||||
state_buffers[State_Ready] = state_buffer_owners[State_Ready].get<Render_State>();
|
||||
state_buffers[State_Render] = state_buffer_owners[State_Render].get<Render_State>();
|
||||
input_buffer_owners[Input_Edit] = std::move(edit_input);
|
||||
input_buffer_owners[Input_Ready] = std::move(ready_input);
|
||||
input_buffer_owners[Input_Render] = std::move(render_input);
|
||||
if (input_buffer_owners[Input_Edit])
|
||||
input_buffers[Input_Edit].data = input_buffer_owners[Input_Edit].get<Input_Data>();
|
||||
if (input_buffer_owners[Input_Ready])
|
||||
input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get<Input_Data>();
|
||||
if (input_buffer_owners[Input_Render])
|
||||
input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get<Input_Data>();
|
||||
if (state_buffers[State_Edit])
|
||||
state_buffers[State_Edit]->version = 1;
|
||||
Renderable_Runtime_Strategy Strategy_Render_Data::runtime_strategy() const {
|
||||
return runtime->runtime_strategy_value;
|
||||
}
|
||||
Render_State_Read_Guard Low_Latency_Render_Data::acquire_render_state_read() {
|
||||
Triple_Buffer_Lease lease = try_mark_state_role(State_Render);
|
||||
|
||||
void Strategy_Render_Data::cancel_pending_completions() {
|
||||
runtime->cancel_pending_completions();
|
||||
}
|
||||
|
||||
void Strategy_Render_Data::drain_committed_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& output) {
|
||||
for (auto& state : runtime->committed_update_states)
|
||||
output.push_back(std::move(state));
|
||||
runtime->committed_update_states.clear();
|
||||
}
|
||||
|
||||
Render_State_Read_Guard Strategy_Render_Data::acquire_state_read(Renderable_State_Role role) {
|
||||
if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) {
|
||||
std::unique_lock<std::mutex> lock(runtime->explicit_state_mutex);
|
||||
if (!runtime->explicit_state)
|
||||
return {};
|
||||
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(std::move(lock)));
|
||||
}
|
||||
Triple_Buffer_Lease lease = runtime->mark_state_role(state_role_index(role));
|
||||
if (!lease)
|
||||
return {};
|
||||
return Render_State_Read_Guard(state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(this, lease));
|
||||
return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(runtime.get(), lease));
|
||||
}
|
||||
|
||||
Render_State_Read_Guard Low_Latency_Render_Data::acquire_edit_state_read() {
|
||||
Triple_Buffer_Lease lease = mark_state_role(State_Edit);
|
||||
Render_State_Read_Guard Strategy_Render_Data::acquire_render_state_read() {
|
||||
if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) {
|
||||
std::unique_lock<std::mutex> lock(runtime->explicit_state_mutex);
|
||||
if (!runtime->explicit_state)
|
||||
return {};
|
||||
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(std::move(lock)));
|
||||
}
|
||||
Triple_Buffer_Lease lease = runtime->try_mark_state_role(State_Render);
|
||||
if (!lease)
|
||||
return {};
|
||||
return Render_State_Read_Guard(state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(this, lease));
|
||||
return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(runtime.get(), lease));
|
||||
}
|
||||
|
||||
Render_State_Edit_Guard Low_Latency_Render_Data::acquire_edit_state() {
|
||||
Triple_Buffer_Lease lease = mark_state_role(State_Edit);
|
||||
Render_State_Read_Guard Strategy_Render_Data::acquire_edit_state_read() {
|
||||
if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) {
|
||||
std::unique_lock<std::mutex> lock(runtime->explicit_state_mutex);
|
||||
if (!runtime->explicit_state)
|
||||
return {};
|
||||
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(std::move(lock)));
|
||||
}
|
||||
Triple_Buffer_Lease lease = runtime->mark_state_role(State_Edit);
|
||||
if (!lease)
|
||||
return {};
|
||||
return Render_State_Edit_Guard(state_by_index(lease.index), std::make_unique<Low_Latency_State_Edit_Handle>(this, lease));
|
||||
return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Read_Handle>(runtime.get(), lease));
|
||||
}
|
||||
|
||||
void Low_Latency_Render_Data::clear_state_buffers() {
|
||||
state_control.close_and_wait();
|
||||
for (std::size_t i = 0; i < state_buffers.size(); ++i) {
|
||||
state_buffer_owners[i].reset();
|
||||
state_buffers[i] = nullptr;
|
||||
}
|
||||
state_control.reset();
|
||||
Render_State_Read_Guard Strategy_Render_Data::acquire_prepare_state_read() {
|
||||
if (runtime->runtime_strategy_value != Renderable_Runtime_Strategy::Explicit)
|
||||
return {};
|
||||
std::unique_lock<std::mutex> lock(runtime->explicit_state_mutex);
|
||||
if (!runtime->explicit_state)
|
||||
return {};
|
||||
return Render_State_Read_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Read_Handle>(std::move(lock)));
|
||||
}
|
||||
|
||||
void Low_Latency_Render_Data::clear_input_role(std::uint8_t role) {
|
||||
Input_Buffer_Slot& slot = input_slot(role);
|
||||
if (slot.completion_state) {
|
||||
if (role == Input_Render) {
|
||||
slot.completion_state->complete_until(Update_Stage::Committed);
|
||||
committed_update_states.push_back(std::move(slot.completion_state));
|
||||
}
|
||||
else {
|
||||
slot.completion_state->complete_all({}, Update_Outcome::Superseded);
|
||||
slot.completion_state.reset();
|
||||
}
|
||||
Render_State_Edit_Guard Strategy_Render_Data::acquire_edit_state() {
|
||||
if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) {
|
||||
std::unique_lock<std::mutex> lock(runtime->explicit_state_mutex);
|
||||
if (!runtime->explicit_state)
|
||||
return {};
|
||||
return Render_State_Edit_Guard(runtime->explicit_state, std::make_unique<Explicit_State_Edit_Handle>(runtime.get(), q_ptr, std::move(lock)));
|
||||
}
|
||||
if (slot.data)
|
||||
slot.data->clear();
|
||||
slot.pending = false;
|
||||
}
|
||||
void Low_Latency_Render_Data::clear_input_buffers() {
|
||||
input_control.close_and_wait();
|
||||
cancel_pending_completions();
|
||||
for (std::size_t i = 0; i < input_buffers.size(); ++i) {
|
||||
input_buffer_owners[i].reset();
|
||||
input_buffers[i].data = nullptr;
|
||||
input_buffers[i].version = 0;
|
||||
input_buffers[i].pending = false;
|
||||
input_buffers[i].completion_state.reset();
|
||||
}
|
||||
input_version = 0;
|
||||
input_control.reset();
|
||||
Triple_Buffer_Lease lease = runtime->mark_state_role(State_Edit);
|
||||
if (!lease)
|
||||
return {};
|
||||
return Render_State_Edit_Guard(runtime->state_by_index(lease.index), std::make_unique<Low_Latency_State_Edit_Handle>(runtime.get(), q_ptr, lease));
|
||||
}
|
||||
|
||||
void Low_Latency_Render_Data::cancel_pending_completions() {
|
||||
for (Input_Buffer_Slot& slot : input_buffers) {
|
||||
if (slot.completion_state) {
|
||||
slot.completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
|
||||
slot.completion_state.reset();
|
||||
}
|
||||
Render_Input_Edit_Guard Strategy_Render_Data::acquire_edit_input(bool notify_render) {
|
||||
if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) {
|
||||
std::unique_lock<std::mutex> lock(runtime->explicit_input_mutex);
|
||||
if (!runtime->explicit_input.data)
|
||||
return {};
|
||||
return Render_Input_Edit_Guard(runtime->explicit_input.data, std::make_unique<Explicit_Input_Edit_Handle>(runtime.get(), q_ptr, std::move(lock), notify_render));
|
||||
}
|
||||
cancel_update_states(committed_update_states);
|
||||
auto record = runtime->input_control.acquire_role(Input_Edit);
|
||||
if (record.result != Triple_Buffer_Result::Success)
|
||||
return {};
|
||||
Input_Data* input = runtime->input_data_by_index(record.lease.index);
|
||||
if (!input) {
|
||||
runtime->input_control.unmark_use(record.lease);
|
||||
return {};
|
||||
}
|
||||
return Render_Input_Edit_Guard(input, std::make_unique<Low_Latency_Input_Edit_Handle>(runtime.get(), q_ptr, record.lease, notify_render));
|
||||
}
|
||||
|
||||
Low_Latency_Render_Data::~Low_Latency_Render_Data() {
|
||||
clear_state_buffers();
|
||||
clear_input_buffers();
|
||||
void Strategy_Render_Data::sync_state_pipeline() {
|
||||
if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit)
|
||||
return;
|
||||
auto edit_record = runtime->state_control.try_acquire_role(State_Edit);
|
||||
if (edit_record.result != Triple_Buffer_Result::Success)
|
||||
return;
|
||||
auto ready_record = runtime->state_control.try_acquire_role(State_Ready);
|
||||
if (ready_record.result != Triple_Buffer_Result::Success) {
|
||||
runtime->state_control.unmark_use(edit_record.lease);
|
||||
return;
|
||||
}
|
||||
Render_State* ready = runtime->state_by_index(ready_record.lease.index);
|
||||
Render_State* edit = runtime->state_by_index(edit_record.lease.index);
|
||||
if (ready && edit && buffer_version_newer(edit->version, ready->version))
|
||||
copy_render_state_object(*ready, *edit);
|
||||
runtime->state_control.unmark_use(ready_record.lease);
|
||||
runtime->state_control.unmark_use(edit_record.lease);
|
||||
runtime->state_control.try_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) {
|
||||
auto* ready = runtime->state_by_index(view[State_Ready]);
|
||||
auto* render = runtime->state_by_index(view[State_Render]);
|
||||
return ready && render && buffer_version_newer(ready->version, render->version);
|
||||
});
|
||||
runtime->input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) {
|
||||
const Input_Buffer_Slot& edit = runtime->input_slot_by_index(view[Input_Edit]);
|
||||
const Input_Buffer_Slot& ready = runtime->input_slot_by_index(view[Input_Ready]);
|
||||
return edit.pending && buffer_version_newer(edit.version, ready.version);
|
||||
});
|
||||
runtime->input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) {
|
||||
const Input_Buffer_Slot& ready = runtime->input_slot_by_index(view[Input_Ready]);
|
||||
const Input_Buffer_Slot& render = runtime->input_slot_by_index(view[Input_Render]);
|
||||
return ready.pending && buffer_version_newer(ready.version, render.version);
|
||||
});
|
||||
}
|
||||
|
||||
Render_State* Strategy_Render_Data::state(Renderable_State_Role role) {
|
||||
return runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit
|
||||
? runtime->explicit_state
|
||||
: runtime->state(state_role_index(role));
|
||||
}
|
||||
|
||||
Input_Data* Strategy_Render_Data::input_data(Renderable_Input_Role role) {
|
||||
return runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit
|
||||
? runtime->explicit_input.data
|
||||
: runtime->input_data(input_role_index(role));
|
||||
}
|
||||
|
||||
void Strategy_Render_Data::clear_render_input() {
|
||||
runtime->clear_input_role(Renderable_Input_Role::Render);
|
||||
}
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -1,117 +1,8 @@
|
||||
#pragma once
|
||||
#include "../../architecture/Buffer_Role.h"
|
||||
#include "../../architecture/Render_Data.h"
|
||||
#include "../../architecture/Update_Completion.h"
|
||||
#include "Low_Latency_Roles.h"
|
||||
#include "Triple_Buffer.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include "../../architecture/Strategy_Render_Data.h"
|
||||
|
||||
namespace renderive {
|
||||
|
||||
struct Input_Buffer_Slot {
|
||||
Input_Data* data{};
|
||||
std::uint64_t version = 0;
|
||||
bool pending = false;
|
||||
std::shared_ptr<Update_State> completion_state;
|
||||
};
|
||||
|
||||
struct Low_Latency_Render_Data : Render_Data {
|
||||
Triple_Role_Buffer_Control state_control;
|
||||
std::array<Render_State*, 3> state_buffers{};
|
||||
std::array<Render_Object_Owner, 3> state_buffer_owners{};
|
||||
Triple_Role_Buffer_Control input_control;
|
||||
std::array<Input_Buffer_Slot, 3> input_buffers{};
|
||||
std::array<Render_Object_Owner, 3> input_buffer_owners{};
|
||||
std::pmr::vector<std::shared_ptr<Update_State>> committed_update_states{memory_resource(Memory_Domain::Update_Completion)};
|
||||
std::uint64_t input_version = 0;
|
||||
|
||||
Render_State* state(std::uint8_t role) const {
|
||||
return state_by_index(state_control.read_view()[role]);
|
||||
}
|
||||
Render_State* state(State_Role src) const {
|
||||
return state(static_cast<std::uint8_t>(src));
|
||||
}
|
||||
Render_State* state_by_index(std::uint8_t index) const {
|
||||
return state_buffers[index];
|
||||
}
|
||||
Triple_Buffer_Lease try_mark_state_role(std::uint8_t role) {
|
||||
auto record = state_control.try_acquire_role(role);
|
||||
if (record.result != Triple_Buffer_Result::Success)
|
||||
return {};
|
||||
return record.lease;
|
||||
}
|
||||
Triple_Buffer_Lease mark_state_role(std::uint8_t role) {
|
||||
auto record = state_control.acquire_role(role);
|
||||
if (record.result != Triple_Buffer_Result::Success)
|
||||
return {};
|
||||
return record.lease;
|
||||
}
|
||||
void unmark_state(Triple_Buffer_Lease lease) {
|
||||
state_control.unmark_use(lease);
|
||||
}
|
||||
void clear_state_buffers();
|
||||
Input_Data* input_data(std::uint8_t role) const {
|
||||
return input_slot(role).data;
|
||||
}
|
||||
Input_Data* input_data_by_index(std::uint8_t index) const {
|
||||
return input_buffers[index].data;
|
||||
}
|
||||
Input_Buffer_Slot& input_slot(std::uint8_t role) {
|
||||
return input_slot_by_index(input_control.read_view()[role]);
|
||||
}
|
||||
const Input_Buffer_Slot& input_slot(std::uint8_t role) const {
|
||||
return input_slot_by_index(input_control.read_view()[role]);
|
||||
}
|
||||
Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) {
|
||||
return input_buffers[index];
|
||||
}
|
||||
const Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) const {
|
||||
return input_buffers[index];
|
||||
}
|
||||
void mark_input_dirty(Triple_Buffer_Lease lease, bool notify_render = true);
|
||||
void set_input_completion(Triple_Buffer_Lease lease, std::shared_ptr<Update_State> state);
|
||||
void drain_committed_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& output) override;
|
||||
void initialize_runtime(Renderable& owner) override;
|
||||
void initialize_runtime_buffers(
|
||||
Render_Object_Owner edit_state,
|
||||
Render_Object_Owner ready_state,
|
||||
Render_Object_Owner render_state,
|
||||
Render_Object_Owner edit_input,
|
||||
Render_Object_Owner ready_input,
|
||||
Render_Object_Owner render_input) override;
|
||||
Render_State_Read_Guard acquire_render_state_read() override;
|
||||
Render_State_Read_Guard acquire_edit_state_read() override;
|
||||
Render_State_Edit_Guard acquire_edit_state() override;
|
||||
void clear_input_role(std::uint8_t role);
|
||||
void clear_render_input() {
|
||||
clear_input_role(Input_Render);
|
||||
}
|
||||
Triple_Buffer_Lease try_mark_render_state() {
|
||||
return try_mark_state_role(State_Render);
|
||||
}
|
||||
Triple_Buffer_Lease try_mark_edit_state() {
|
||||
return try_mark_state_role(State_Edit);
|
||||
}
|
||||
Triple_Buffer_Lease mark_edit_state() {
|
||||
return mark_state_role(State_Edit);
|
||||
}
|
||||
void clear_input_buffers();
|
||||
void cancel_pending_completions() override;
|
||||
~Low_Latency_Render_Data() override;
|
||||
};
|
||||
|
||||
class Plot_Core;
|
||||
|
||||
struct LIB_DECL Render_Edit_Guard {
|
||||
Render_Edit_Guard(Low_Latency_Render_Data* data, Triple_Buffer_Lease lease);
|
||||
~Render_Edit_Guard();
|
||||
|
||||
Low_Latency_Render_Data* data{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
};
|
||||
using Low_Latency_Render_Data = Strategy_Render_Data;
|
||||
|
||||
} // namespace renderive
|
||||
|
||||
@@ -1,246 +1,4 @@
|
||||
#pragma once
|
||||
#include "../../architecture/Buffer_Role.h"
|
||||
#include "../../architecture/Renderable.h"
|
||||
#include "Render_Data.h"
|
||||
#include "../../architecture/Render_Lease.h"
|
||||
#include "Low_Latency_Roles.h"
|
||||
#include <utility>
|
||||
namespace renderive {
|
||||
template <typename Base, typename Owner, Render_State_Type State, Input_Data_Type Input>
|
||||
requires std::derived_from<Base, Low_Latency_Render_Data>
|
||||
struct Typed_Render_Data_Base : Base {
|
||||
Owner* q() {
|
||||
return reinterpret_cast<Owner*>(this->q_ptr);
|
||||
}
|
||||
State* render_state() override {
|
||||
return reinterpret_cast<State*>(this->state(State_Render));
|
||||
}
|
||||
State* ready_state() override {
|
||||
return reinterpret_cast<State*>(this->state(State_Ready));
|
||||
}
|
||||
State* edit_state() override {
|
||||
return reinterpret_cast<State*>(this->state(State_Edit));
|
||||
}
|
||||
Input* render_input_data() override {
|
||||
return reinterpret_cast<Input*>(this->input_data(Input_Render));
|
||||
}
|
||||
Input* edit_input_data() override {
|
||||
return reinterpret_cast<Input*>(this->input_data(Input_Edit));
|
||||
}
|
||||
State* state_data(State_Role src) {
|
||||
return reinterpret_cast<State*>(this->state(src));
|
||||
}
|
||||
State* state_data_by_index(std::uint8_t index) {
|
||||
return reinterpret_cast<State*>(this->state_by_index(index));
|
||||
}
|
||||
Input* input_data_by_index(std::uint8_t index) {
|
||||
return reinterpret_cast<Input*>(Base::input_data_by_index(index));
|
||||
}
|
||||
template <typename Field, typename Source_State>
|
||||
requires std::derived_from<State, Source_State>
|
||||
Field state_value(Field Source_State::* field, State_Role src) {
|
||||
auto record = this->state_control.acquire_role(static_cast<std::uint8_t>(src));
|
||||
if (record.result != Triple_Buffer_Result::Success)
|
||||
return {};
|
||||
Triple_Buffer_Lease lease = record.lease;
|
||||
auto state = state_data_by_index(lease.index);
|
||||
Field result = state->*field;
|
||||
this->state_control.unmark_use(lease);
|
||||
return result;
|
||||
}
|
||||
template <typename Field, typename Source_State>
|
||||
requires std::derived_from<State, Source_State>
|
||||
Field edit_state_value(Field Source_State::* field) {
|
||||
return state_value(field, State_Edit);
|
||||
}
|
||||
template <typename Field, typename Source_State>
|
||||
requires std::derived_from<State, Source_State>
|
||||
Field ready_state_value(Field Source_State::* field) {
|
||||
return state_value(field, State_Ready);
|
||||
}
|
||||
template <typename Field, typename Source_State>
|
||||
requires std::derived_from<State, Source_State>
|
||||
Field render_state_value(Field Source_State::* field) {
|
||||
return state_value(field, State_Render);
|
||||
}
|
||||
template <typename Field, typename Source_State>
|
||||
requires std::derived_from<State, Source_State>
|
||||
void set_state_value(Field Source_State::* field, Field value) {
|
||||
auto record = this->state_control.acquire_role(State_Edit);
|
||||
if (record.result != Triple_Buffer_Result::Success)
|
||||
return;
|
||||
auto lease = record.lease;
|
||||
auto state = state_data_by_index(lease.index);
|
||||
Render_Edit_Guard guard(this, lease);
|
||||
state->*field = std::move(value);
|
||||
}
|
||||
void sync_state_pipeline() override {
|
||||
auto edit_record = this->state_control.try_acquire_role(State_Edit);
|
||||
if (edit_record.result != Triple_Buffer_Result::Success)
|
||||
return;
|
||||
auto ready_record = this->state_control.try_acquire_role(State_Ready);
|
||||
if (ready_record.result != Triple_Buffer_Result::Success) {
|
||||
this->state_control.unmark_use(edit_record.lease);
|
||||
return;
|
||||
}
|
||||
Triple_Buffer_Lease edit_lease = edit_record.lease;
|
||||
Triple_Buffer_Lease ready_lease = ready_record.lease;
|
||||
auto ready = reinterpret_cast<State*>(this->state_by_index(ready_lease.index));
|
||||
auto edit = reinterpret_cast<State*>(this->state_by_index(edit_lease.index));
|
||||
if (buffer_version_newer(edit->version, ready->version))
|
||||
*ready = *edit;
|
||||
this->state_control.unmark_use(ready_lease);
|
||||
this->state_control.unmark_use(edit_lease);
|
||||
this->state_control.try_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) {
|
||||
auto ready = reinterpret_cast<State*>(this->state_by_index(view[State_Ready]));
|
||||
auto render = reinterpret_cast<State*>(this->state_by_index(view[State_Render]));
|
||||
return buffer_version_newer(ready->version, render->version);
|
||||
});
|
||||
this->input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) {
|
||||
const Input_Buffer_Slot& edit = this->input_slot_by_index(view[Input_Edit]);
|
||||
const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]);
|
||||
return edit.pending && buffer_version_newer(edit.version, ready.version);
|
||||
});
|
||||
this->input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) {
|
||||
const Input_Buffer_Slot& ready = this->input_slot_by_index(view[Input_Ready]);
|
||||
const Input_Buffer_Slot& render = this->input_slot_by_index(view[Input_Render]);
|
||||
return ready.pending && buffer_version_newer(ready.version, render.version);
|
||||
});
|
||||
}
|
||||
};
|
||||
template <typename Owner, Render_State_Type State, Input_Data_Type Input>
|
||||
using Typed_Render_Data = Typed_Render_Data_Base<Low_Latency_Render_Data, Owner, State, Input>;
|
||||
template <typename Owner, Render_Data_Type Data, Render_State_Type State, Input_Data_Type Input>
|
||||
struct Renderable_Binding {
|
||||
static void init(Owner* owner, const char* name) {
|
||||
owner->d_owner = make_render_object<Data>();
|
||||
owner->d_ptr = static_cast<Render_Data*>(owner->d_owner.template get<Data>());
|
||||
owner->set_object_name(name);
|
||||
}
|
||||
static Data* d(Owner* owner) {
|
||||
return reinterpret_cast<Data*>(owner->d_ptr);
|
||||
}
|
||||
static Render_Object_Owner create_render_data() {
|
||||
return make_render_object<Data>();
|
||||
}
|
||||
static Render_Object_Owner create_render_state() {
|
||||
return make_render_object<State>();
|
||||
}
|
||||
static Render_Object_Owner create_input_data() {
|
||||
if constexpr (std::is_same_v<Input, Input_Data>)
|
||||
return {};
|
||||
return make_render_object<Input>();
|
||||
}
|
||||
};
|
||||
#define RENDERIVE_DEFINE_RENDERABLE_BINDING(Owner, Data, State, Input, Name) \
|
||||
using Owner##_Binding = ::renderive::Renderable_Binding<Owner, Data, State, Input>; \
|
||||
Owner::Owner() { \
|
||||
Owner##_Binding::init(this, Name); \
|
||||
} \
|
||||
Data* Owner::d() { \
|
||||
return Owner##_Binding::d(this); \
|
||||
} \
|
||||
::renderive::Render_Object_Owner Owner::create_render_data() { \
|
||||
return Owner##_Binding::create_render_data(); \
|
||||
} \
|
||||
::renderive::Render_Object_Owner Owner::create_render_state() { \
|
||||
return Owner##_Binding::create_render_state(); \
|
||||
} \
|
||||
::renderive::Render_Object_Owner Owner::create_input_data() { \
|
||||
return Owner##_Binding::create_input_data(); \
|
||||
}
|
||||
template <typename Data, Render_State_Type State>
|
||||
requires std::derived_from<Data, Low_Latency_Render_Data>
|
||||
struct Render_State_Lease : Immovable {
|
||||
explicit Render_State_Lease(Data* data) : data(data) {
|
||||
auto record = data->state_control.acquire_role(State_Edit);
|
||||
if (record.result == Triple_Buffer_Result::Success) {
|
||||
lease = record.lease;
|
||||
state = reinterpret_cast<State*>(data->state_by_index(lease.index));
|
||||
}
|
||||
}
|
||||
~Render_State_Lease() {
|
||||
if (lease)
|
||||
data->unmark_state(lease);
|
||||
}
|
||||
State* operator->() {
|
||||
return state;
|
||||
}
|
||||
State& operator*() {
|
||||
return *state;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return state && lease;
|
||||
}
|
||||
Data* data{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
State* state{};
|
||||
};
|
||||
template <typename Data, Render_State_Type State>
|
||||
requires std::derived_from<Data, Low_Latency_Render_Data>
|
||||
struct Render_Edit_Lease : Immovable {
|
||||
explicit Render_Edit_Lease(Data* data) : data(data) {
|
||||
auto record = data->state_control.acquire_role(State_Edit);
|
||||
if (record.result == Triple_Buffer_Result::Success) {
|
||||
lease = record.lease;
|
||||
state = reinterpret_cast<State*>(data->state_by_index(lease.index));
|
||||
}
|
||||
}
|
||||
~Render_Edit_Lease() {
|
||||
if (!lease)
|
||||
return;
|
||||
++data->state_by_index(lease.index)->version;
|
||||
auto q = data->q_ptr;
|
||||
data->unmark_state(lease);
|
||||
if (q)
|
||||
q->mark_render_dirty();
|
||||
}
|
||||
State* operator->() {
|
||||
return state;
|
||||
}
|
||||
State& operator*() {
|
||||
return *state;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return state && lease;
|
||||
}
|
||||
Data* data{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
State* state{};
|
||||
};
|
||||
template <typename Data, Input_Data_Type Input>
|
||||
requires std::derived_from<Data, Low_Latency_Render_Data>
|
||||
struct Render_Input_Lease : Immovable {
|
||||
explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render) {
|
||||
auto record = data->input_control.acquire_role(Input_Edit);
|
||||
if (record.result == Triple_Buffer_Result::Success) {
|
||||
lease = record.lease;
|
||||
input = reinterpret_cast<Input*>(data->input_data_by_index(lease.index));
|
||||
if (!input) {
|
||||
data->input_control.unmark_use(lease);
|
||||
lease = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
~Render_Input_Lease() {
|
||||
if (!lease)
|
||||
return;
|
||||
data->mark_input_dirty(lease, false);
|
||||
data->input_control.unmark_use(lease);
|
||||
if (notify_render && data->q_ptr)
|
||||
data->q_ptr->mark_render_dirty();
|
||||
}
|
||||
Input* operator->() {
|
||||
return input;
|
||||
}
|
||||
Input& operator*() {
|
||||
return *input;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return input && lease;
|
||||
}
|
||||
Data* data{};
|
||||
bool notify_render{};
|
||||
Triple_Buffer_Lease lease{};
|
||||
Input* input{};
|
||||
};
|
||||
} // namespace renderive
|
||||
|
||||
|
||||
Reference in New Issue
Block a user