#include "../../architecture/Strategy_Render_Data.h" #include "../../architecture/Renderable.h" #include "../../base/Memory.h" #include "Low_Latency_Roles.h" #include "Triple_Buffer.h" #include #include #include #include #include namespace renderive { std::ostream& operator<<(std::ostream& stream, const Range& range) { stream << "Range(" << range.origin << ", " << range.target << ")"; return stream; } namespace { struct Input_Buffer_Slot { Input_Data* data{}; std::uint64_t version = 0; bool pending = false; std::shared_ptr completion_state; }; 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; } 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 state_buffers{}; std::array state_buffer_owners{}; Triple_Role_Buffer_Control input_control; std::array input_buffers{}; std::array input_buffer_owners{}; std::pmr::vector> 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 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 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 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 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(); state_buffers[State_Ready] = state_buffer_owners[State_Ready].get(); state_buffers[State_Render] = state_buffer_owners[State_Render].get(); 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(); if (input_buffer_owners[Input_Ready]) input_buffers[Input_Ready].data = input_buffer_owners[Input_Ready].get(); if (input_buffer_owners[Input_Render]) input_buffers[Input_Render].data = input_buffer_owners[Input_Render].get(); 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(); if (explicit_state) explicit_state->version = 1; explicit_input_owner = std::move(input); explicit_input.data = explicit_input_owner.get(); 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 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(Strategy_Render_Data_Private* runtime, Renderable* owner, Triple_Buffer_Lease lease) : runtime(runtime), owner(owner), lease(lease) {} ~Low_Latency_State_Edit_Handle() override { if (!runtime || !lease) return; 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); } 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 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 lock) : lock(std::move(lock)) {} std::unique_lock 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 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 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 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 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 lock; bool notify_render{}; }; } // namespace Strategy_Render_Data::Strategy_Render_Data() : runtime(std::make_unique()) {} Strategy_Render_Data::~Strategy_Render_Data() { runtime->clear_state_buffers(); runtime->clear_input_buffers(); runtime->clear_explicit_runtime(); } 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(), owner.create_input_data(), owner.create_input_data(), owner.create_input_data()); } Renderable_Runtime_Strategy Strategy_Render_Data::runtime_strategy() const { return runtime->runtime_strategy_value; } void Strategy_Render_Data::cancel_pending_completions() { runtime->cancel_pending_completions(); } void Strategy_Render_Data::drain_committed_update_states(std::pmr::vector>& 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 lock(runtime->explicit_state_mutex); if (!runtime->explicit_state) return {}; return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); } Triple_Buffer_Lease lease = runtime->mark_state_role(state_role_index(role)); if (!lease) return {}; return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); } Render_State_Read_Guard Strategy_Render_Data::acquire_render_state_read() { if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { std::unique_lock lock(runtime->explicit_state_mutex); if (!runtime->explicit_state) return {}; return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); } Triple_Buffer_Lease lease = runtime->try_mark_state_role(State_Render); if (!lease) return {}; return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); } Render_State_Read_Guard Strategy_Render_Data::acquire_edit_state_read() { if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { std::unique_lock lock(runtime->explicit_state_mutex); if (!runtime->explicit_state) return {}; return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); } Triple_Buffer_Lease lease = runtime->mark_state_role(State_Edit); if (!lease) return {}; return Render_State_Read_Guard(runtime->state_by_index(lease.index), std::make_unique(runtime.get(), lease)); } Render_State_Read_Guard Strategy_Render_Data::acquire_prepare_state_read() { if (runtime->runtime_strategy_value != Renderable_Runtime_Strategy::Explicit) return {}; std::unique_lock lock(runtime->explicit_state_mutex); if (!runtime->explicit_state) return {}; return Render_State_Read_Guard(runtime->explicit_state, std::make_unique(std::move(lock))); } Render_State_Edit_Guard Strategy_Render_Data::acquire_edit_state() { if (runtime->runtime_strategy_value == Renderable_Runtime_Strategy::Explicit) { std::unique_lock lock(runtime->explicit_state_mutex); if (!runtime->explicit_state) return {}; return Render_State_Edit_Guard(runtime->explicit_state, std::make_unique(runtime.get(), q_ptr, std::move(lock))); } 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(runtime.get(), q_ptr, lease)); } 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 lock(runtime->explicit_input_mutex); if (!runtime->explicit_input.data) return {}; return Render_Input_Edit_Guard(runtime->explicit_input.data, std::make_unique(runtime.get(), q_ptr, std::move(lock), notify_render)); } 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(runtime.get(), q_ptr, record.lease, notify_render)); } 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