#include "Low_Latency_Renderable_Runtime.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 Low_Latency_Renderable_Runtime_Data { Renderable* owner{}; Renderable_Runtime::Copy_Render_State_Function copy_render_state; 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; 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 { 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 { 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{}; } Triple_Buffer_Lease try_mark_input_role(std::uint8_t role) { auto record = input_control.try_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(); } 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 initialize_buffers(Renderable& renderable) { clear_state_buffers(); clear_input_buffers(); state_buffer_owners[State_Edit] = renderable.create_render_state(); state_buffer_owners[State_Ready] = renderable.create_render_state(); state_buffer_owners[State_Render] = renderable.create_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] = renderable.create_input_data(); input_buffer_owners[Input_Ready] = renderable.create_input_data(); input_buffer_owners[Input_Render] = renderable.create_input_data(); 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 sync_state_pipeline() { auto edit_record = state_control.try_acquire_role(State_Edit); if (edit_record.result != Triple_Buffer_Result::Success) return; auto ready_record = state_control.try_acquire_role(State_Ready); if (ready_record.result != Triple_Buffer_Result::Success) { state_control.unmark_use(edit_record.lease); return; } Render_State* ready = state_by_index(ready_record.lease.index); Render_State* edit = state_by_index(edit_record.lease.index); if (ready && edit && buffer_version_newer(edit->version, ready->version)) { if (copy_render_state) copy_render_state(*ready, *edit); else ready->version = edit->version; } state_control.unmark_use(ready_record.lease); state_control.unmark_use(edit_record.lease); state_control.try_swap_role(State_Ready, State_Render, [this](const Triple_Buffer_View& view) { auto* ready = state_by_index(view[State_Ready]); auto* render = state_by_index(view[State_Render]); return ready && render && buffer_version_newer(ready->version, render->version); }); input_control.try_swap_role(Input_Edit, Input_Ready, [this](const Triple_Buffer_View& view) { const Input_Buffer_Slot& edit = input_slot_by_index(view[Input_Edit]); const Input_Buffer_Slot& ready = input_slot_by_index(view[Input_Ready]); return edit.pending && buffer_version_newer(edit.version, ready.version); }); input_control.try_swap_role(Input_Ready, Input_Render, [this](const Triple_Buffer_View& view) { const Input_Buffer_Slot& ready = input_slot_by_index(view[Input_Ready]); const Input_Buffer_Slot& render = input_slot_by_index(view[Input_Render]); return ready.pending && buffer_version_newer(ready.version, render.version); }); } void clear_input_role(Renderable_Input_Role role) { 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; } void consume_input_lease(Triple_Buffer_Lease lease) { if (!lease) return; Input_Buffer_Slot& slot = input_slot_by_index(lease.index); if (slot.completion_state) { slot.completion_state->complete_until(Update_Stage::Committed); committed_update_states.push_back(std::move(slot.completion_state)); } if (slot.data) slot.data->consume(); slot.pending = false; } }; namespace { struct Low_Latency_State_Read_Handle final : Render_State_Read_Handle { Low_Latency_State_Read_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease) : runtime(runtime), lease(lease) {} ~Low_Latency_State_Read_Handle() override { if (runtime && lease) runtime->state_control.unmark_use(lease); } Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; }; struct Low_Latency_Input_Frame_Handle final : Render_Input_Frame_Handle { Low_Latency_Input_Frame_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease) : runtime(runtime), lease(lease) {} ~Low_Latency_Input_Frame_Handle() override { if (runtime && lease) runtime->input_control.unmark_use(lease); } void consume_input(Input_Data*) override { if (consumed || !runtime || !lease) return; consumed = true; runtime->consume_input_lease(lease); } Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; bool consumed = false; }; struct Low_Latency_State_Write_Handle final : Render_State_Write_Handle { Low_Latency_State_Write_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease) : runtime(runtime), lease(lease) {} ~Low_Latency_State_Write_Handle() override { if (!runtime || !lease) return; if (auto* state = runtime->state_by_index(lease.index)) ++state->version; runtime->state_control.unmark_use(lease); if (runtime->owner) runtime->owner->mark_render_dirty(Dirty_Reason::Config); } Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; }; struct Low_Latency_Input_Write_Handle final : Render_Input_Write_Handle { Low_Latency_Input_Write_Handle(Low_Latency_Renderable_Runtime_Data* runtime, Triple_Buffer_Lease lease, bool notify_render) : runtime(runtime), lease(lease), notify_render(notify_render) {} ~Low_Latency_Input_Write_Handle() override { if (!runtime || !lease) return; runtime->mark_input_dirty(lease); runtime->input_control.unmark_use(lease); if (notify_render && runtime->owner) runtime->owner->mark_render_dirty(); } void set_completion(std::shared_ptr state) override { if (runtime && lease) runtime->set_input_completion(lease, std::move(state)); } Low_Latency_Renderable_Runtime_Data* runtime{}; Triple_Buffer_Lease lease{}; bool notify_render{}; }; } // namespace Low_Latency_Renderable_Runtime::Low_Latency_Renderable_Runtime(Renderable& owner) : d(std::make_unique()) { d->owner = &owner; d->initialize_buffers(owner); } Low_Latency_Renderable_Runtime::~Low_Latency_Renderable_Runtime() { d->clear_state_buffers(); d->clear_input_buffers(); } void Low_Latency_Renderable_Runtime::set_render_state_copier(Copy_Render_State_Function copier) { d->copy_render_state = std::move(copier); } void Low_Latency_Renderable_Runtime::cancel_pending_completions() { d->cancel_pending_completions(); } void Low_Latency_Renderable_Runtime::drain_committed_update_states(std::pmr::vector>& output) { for (auto& state : d->committed_update_states) output.push_back(std::move(state)); d->committed_update_states.clear(); } Render_State_Read_Guard Low_Latency_Renderable_Runtime::acquire_state_read(Renderable_State_Role role) { Triple_Buffer_Lease lease = d->mark_state_role(state_role_index(role)); if (!lease) return {}; return Render_State_Read_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } Render_State_Read_Guard Low_Latency_Renderable_Runtime::acquire_render_state_read() { Triple_Buffer_Lease lease = d->try_mark_state_role(State_Render); if (!lease) return {}; return Render_State_Read_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } Render_State_Read_Guard Low_Latency_Renderable_Runtime::acquire_edit_state_read() { Triple_Buffer_Lease lease = d->mark_state_role(State_Edit); if (!lease) return {}; return Render_State_Read_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } Render_State_Write_Guard Low_Latency_Renderable_Runtime::acquire_edit_state() { Triple_Buffer_Lease lease = d->mark_state_role(State_Edit); if (!lease) return {}; return Render_State_Write_Guard(d->state_by_index(lease.index), std::make_unique(d.get(), lease)); } Render_Input_Write_Guard Low_Latency_Renderable_Runtime::acquire_edit_input(bool notify_render) { auto record = d->input_control.acquire_role(Input_Edit); if (record.result != Triple_Buffer_Result::Success) return {}; Input_Data* input = d->input_data_by_index(record.lease.index); if (!input) { d->input_control.unmark_use(record.lease); return {}; } return Render_Input_Write_Guard(input, std::make_unique(d.get(), record.lease, notify_render)); } Render_Input_Frame_Guard Low_Latency_Renderable_Runtime::acquire_input_read(Renderable_Input_Role role) { Triple_Buffer_Lease lease = d->try_mark_input_role(input_role_index(role)); if (!lease) return {}; Input_Data* input = d->input_data_by_index(lease.index); if (!input) { d->input_control.unmark_use(lease); return {}; } return Render_Input_Frame_Guard(input, std::make_unique(d.get(), lease)); } Renderable_Frame_View Low_Latency_Renderable_Runtime::capture_frame_view() { d->sync_state_pipeline(); Triple_Buffer_Lease state_lease = d->try_mark_state_role(State_Render); if (!state_lease) return {}; auto state_guard = Render_State_Read_Guard( d->state_by_index(state_lease.index), std::make_unique(d.get(), state_lease)); Render_Input_Frame_Guard input_guard; Triple_Buffer_Lease input_lease = d->try_mark_input_role(Input_Render); if (input_lease) { Input_Data* input = d->input_data_by_index(input_lease.index); if (input) { input_guard = Render_Input_Frame_Guard( input, std::make_unique(d.get(), input_lease)); } else { d->input_control.unmark_use(input_lease); } } return Renderable_Frame_View(std::move(state_guard), std::move(input_guard)); } Render_State* Low_Latency_Renderable_Runtime::state(Renderable_State_Role role) { return d->state(state_role_index(role)); } Input_Data* Low_Latency_Renderable_Runtime::input_data(Renderable_Input_Role role) { return d->input_data(input_role_index(role)); } void Low_Latency_Renderable_Runtime::clear_render_input() { d->clear_input_role(Renderable_Input_Role::Render); } std::unique_ptr make_low_latency_renderable_runtime(Renderable& owner) { return std::make_unique(owner); } } // namespace renderive