881 lines
33 KiB
C++
881 lines
33 KiB
C++
#pragma once
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <concepts>
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <qlinkedlist.h>
|
|
#include <QImage>
|
|
#include <QRegion>
|
|
#include <qscreen.h>
|
|
#include "GlobalTypes.h"
|
|
namespace YSG {
|
|
static constexpr std::uint8_t triple_buffer_invalid_index = 255;
|
|
enum class Triple_Buffer_Result : std::uint8_t {
|
|
Success,
|
|
Busy,
|
|
State_Changed,
|
|
Condition_Failed
|
|
};
|
|
enum class Triple_Buffer_Mode : std::uint8_t {
|
|
Try,
|
|
Wait
|
|
};
|
|
enum class Plot_Buffer_Acquire_Mode : std::uint8_t {
|
|
Try,
|
|
Wait
|
|
};
|
|
enum class Plot_Render_Schedule_Mode : std::uint8_t {
|
|
Timer_Check,
|
|
Chase_Latest,
|
|
Max_Fps,
|
|
Manual
|
|
};
|
|
enum class RenderAble_Render_Schedule_Mode : std::uint8_t {
|
|
Inherit,
|
|
Chase_Latest,
|
|
Max_Fps,
|
|
On_Dirty,
|
|
Manual
|
|
};
|
|
enum class Render_Request_Source : std::uint8_t {
|
|
Timer,
|
|
Dirty,
|
|
Finish,
|
|
Manual
|
|
};
|
|
inline Triple_Buffer_Mode to_triple_buffer_mode(Plot_Buffer_Acquire_Mode mode) {
|
|
return mode == Plot_Buffer_Acquire_Mode::Wait ? Triple_Buffer_Mode::Wait : Triple_Buffer_Mode::Try;
|
|
}
|
|
struct Triple_Buffer_View {
|
|
std::array<std::uint8_t, 3> index{};
|
|
std::uint8_t operator[](std::uint8_t role) const {
|
|
return index[role];
|
|
}
|
|
};
|
|
struct Triple_Buffer_Lease {
|
|
std::uint8_t index = triple_buffer_invalid_index;
|
|
explicit operator bool() const {
|
|
return index != triple_buffer_invalid_index;
|
|
}
|
|
};
|
|
struct Triple_Buffer_Mark_Record {
|
|
Triple_Buffer_Result result = Triple_Buffer_Result::Condition_Failed;
|
|
Triple_Buffer_Lease lease{};
|
|
std::uint8_t busy_index = triple_buffer_invalid_index;
|
|
};
|
|
struct Triple_Buffer_Swap_Record {
|
|
Triple_Buffer_Result result = Triple_Buffer_Result::Condition_Failed;
|
|
Triple_Buffer_View old_view{};
|
|
Triple_Buffer_View new_view{};
|
|
std::uint8_t busy_index = triple_buffer_invalid_index;
|
|
};
|
|
struct alignas(64) Triple_Buffer_Busy {
|
|
std::atomic_bool busy{false};
|
|
};
|
|
struct Triple_Buffer_Stats {
|
|
std::atomic<std::uint64_t> mark_success{0};
|
|
std::atomic<std::uint64_t> mark_busy{0};
|
|
std::atomic<std::uint64_t> mark_state_changed{0};
|
|
std::atomic<std::uint64_t> swap_success{0};
|
|
std::atomic<std::uint64_t> swap_busy{0};
|
|
std::atomic<std::uint64_t> swap_state_changed{0};
|
|
std::atomic<std::uint64_t> swap_condition_failed{0};
|
|
std::atomic<std::uint64_t> wait_count{0};
|
|
};
|
|
class Triple_Role_Buffer_Control {
|
|
public:
|
|
void set_stats(Triple_Buffer_Stats* value) {
|
|
stats = value;
|
|
}
|
|
void reset() {
|
|
slot_state.store(0, std::memory_order_release);
|
|
for (auto& item : buffer_busy) {
|
|
item.busy.store(false, std::memory_order_release);
|
|
item.busy.notify_all();
|
|
}
|
|
slot_state.notify_all();
|
|
}
|
|
Triple_Buffer_View read_view() const {
|
|
return decode_order(slot_state.load(std::memory_order_acquire));
|
|
}
|
|
Triple_Buffer_Mark_Record try_mark_use_index(std::uint8_t index) {
|
|
if (!try_mark_index(index)) {
|
|
record_mark(Triple_Buffer_Result::Busy);
|
|
return {Triple_Buffer_Result::Busy, {}, index};
|
|
}
|
|
record_mark(Triple_Buffer_Result::Success);
|
|
return {Triple_Buffer_Result::Success, {index}, triple_buffer_invalid_index};
|
|
}
|
|
Triple_Buffer_Lease wait_mark_use_index(std::uint8_t index) {
|
|
for (;;) {
|
|
if (try_mark_index(index)) {
|
|
record_mark(Triple_Buffer_Result::Success);
|
|
return {index};
|
|
}
|
|
record_mark(Triple_Buffer_Result::Busy);
|
|
record_wait();
|
|
wait_index_free(index);
|
|
}
|
|
}
|
|
Triple_Buffer_Mark_Record try_mark_use_role(std::uint8_t role) {
|
|
auto order = slot_state.load(std::memory_order_acquire);
|
|
auto view = decode_order(order);
|
|
auto index = view[role];
|
|
if (!try_mark_index(index)) {
|
|
record_mark(Triple_Buffer_Result::Busy);
|
|
return {Triple_Buffer_Result::Busy, {}, index};
|
|
}
|
|
auto cur_order = slot_state.load(std::memory_order_acquire);
|
|
if (cur_order != order) {
|
|
unmark_index(index);
|
|
record_mark(Triple_Buffer_Result::State_Changed);
|
|
return {Triple_Buffer_Result::State_Changed, {}, triple_buffer_invalid_index};
|
|
}
|
|
record_mark(Triple_Buffer_Result::Success);
|
|
return {Triple_Buffer_Result::Success, {index}, triple_buffer_invalid_index};
|
|
}
|
|
Triple_Buffer_Lease wait_mark_use_role(std::uint8_t role) {
|
|
for (;;) {
|
|
auto record = try_mark_use_role(role);
|
|
if (record.result == Triple_Buffer_Result::Success) {
|
|
return record.lease;
|
|
}
|
|
if (record.result == Triple_Buffer_Result::Busy) {
|
|
record_wait();
|
|
wait_index_free(record.busy_index);
|
|
}
|
|
}
|
|
}
|
|
void unmark_use(Triple_Buffer_Lease lease) {
|
|
unmark_index(lease.index);
|
|
}
|
|
template <typename Can_Swap>
|
|
Triple_Buffer_Swap_Record try_swap_role(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap) {
|
|
std::uint8_t busy_index = triple_buffer_invalid_index;
|
|
return try_swap_role_once(left_role, right_role, can_swap, busy_index);
|
|
}
|
|
template <typename Can_Swap>
|
|
Triple_Buffer_Swap_Record wait_swap_role(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap) {
|
|
for (;;) {
|
|
std::uint8_t busy_index = triple_buffer_invalid_index;
|
|
auto record = try_swap_role_once(left_role, right_role, can_swap, busy_index);
|
|
if (record.result == Triple_Buffer_Result::Success || record.result == Triple_Buffer_Result::Condition_Failed) {
|
|
return record;
|
|
}
|
|
if (record.result == Triple_Buffer_Result::Busy) {
|
|
record_wait();
|
|
wait_index_free(busy_index);
|
|
}
|
|
else {
|
|
record_wait();
|
|
slot_state.wait(encode_order(record.old_view), std::memory_order_acquire);
|
|
}
|
|
}
|
|
}
|
|
private:
|
|
std::atomic<std::uint8_t> slot_state{0};
|
|
std::array<Triple_Buffer_Busy, 3> buffer_busy{};
|
|
Triple_Buffer_Stats* stats{};
|
|
void record_mark(Triple_Buffer_Result result) {
|
|
if (!stats) return;
|
|
switch (result) {
|
|
case Triple_Buffer_Result::Success:
|
|
stats->mark_success.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
case Triple_Buffer_Result::Busy:
|
|
stats->mark_busy.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
case Triple_Buffer_Result::State_Changed:
|
|
stats->mark_state_changed.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
case Triple_Buffer_Result::Condition_Failed:
|
|
break;
|
|
}
|
|
}
|
|
void record_swap(Triple_Buffer_Result result) {
|
|
if (!stats) return;
|
|
switch (result) {
|
|
case Triple_Buffer_Result::Success:
|
|
stats->swap_success.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
case Triple_Buffer_Result::Busy:
|
|
stats->swap_busy.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
case Triple_Buffer_Result::State_Changed:
|
|
stats->swap_state_changed.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
case Triple_Buffer_Result::Condition_Failed:
|
|
stats->swap_condition_failed.fetch_add(1, std::memory_order_relaxed);
|
|
break;
|
|
}
|
|
}
|
|
void record_wait() {
|
|
if (stats) stats->wait_count.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
static Triple_Buffer_View decode_order(std::uint8_t order) {
|
|
switch (order) {
|
|
case 0:
|
|
return {{0, 1, 2}};
|
|
case 1:
|
|
return {{0, 2, 1}};
|
|
case 2:
|
|
return {{1, 0, 2}};
|
|
case 3:
|
|
return {{1, 2, 0}};
|
|
case 4:
|
|
return {{2, 0, 1}};
|
|
case 5:
|
|
return {{2, 1, 0}};
|
|
}
|
|
return {{0, 1, 2}};
|
|
}
|
|
static std::uint8_t encode_order(const Triple_Buffer_View& view) {
|
|
if (view.index[0] == 0) {
|
|
return view.index[1] == 1 ? 0 : 1;
|
|
}
|
|
if (view.index[0] == 1) {
|
|
return view.index[1] == 0 ? 2 : 3;
|
|
}
|
|
return view.index[1] == 0 ? 4 : 5;
|
|
}
|
|
static std::uint8_t swap_order(std::uint8_t order, std::uint8_t left_role, std::uint8_t right_role) {
|
|
auto view = decode_order(order);
|
|
auto temp = view.index[left_role];
|
|
view.index[left_role] = view.index[right_role];
|
|
view.index[right_role] = temp;
|
|
return encode_order(view);
|
|
}
|
|
bool try_mark_index(std::uint8_t index) {
|
|
bool expected = false;
|
|
return buffer_busy[index].busy.compare_exchange_strong(expected, true, std::memory_order_acq_rel, std::memory_order_acquire);
|
|
}
|
|
void unmark_index(std::uint8_t index) {
|
|
buffer_busy[index].busy.store(false, std::memory_order_release);
|
|
buffer_busy[index].busy.notify_all();
|
|
}
|
|
void wait_index_free(std::uint8_t index) {
|
|
while (buffer_busy[index].busy.load(std::memory_order_acquire)) {
|
|
buffer_busy[index].busy.wait(true, std::memory_order_acquire);
|
|
}
|
|
}
|
|
bool try_mark_two_indices(std::uint8_t left_index, std::uint8_t right_index, std::uint8_t& busy_index) {
|
|
if (left_index == right_index) {
|
|
if (!try_mark_index(left_index)) {
|
|
busy_index = left_index;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
auto first = left_index < right_index ? left_index : right_index;
|
|
auto second = left_index < right_index ? right_index : left_index;
|
|
if (!try_mark_index(first)) {
|
|
busy_index = first;
|
|
return false;
|
|
}
|
|
if (!try_mark_index(second)) {
|
|
unmark_index(first);
|
|
busy_index = second;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
void unmark_two_indices(std::uint8_t left_index, std::uint8_t right_index) {
|
|
if (left_index == right_index) {
|
|
unmark_index(left_index);
|
|
return;
|
|
}
|
|
unmark_index(left_index);
|
|
unmark_index(right_index);
|
|
}
|
|
template <typename Can_Swap>
|
|
Triple_Buffer_Swap_Record try_swap_role_once(std::uint8_t left_role, std::uint8_t right_role, Can_Swap can_swap, std::uint8_t& busy_index) {
|
|
auto old_order = slot_state.load(std::memory_order_acquire);
|
|
auto old_view = decode_order(old_order);
|
|
auto left_index = old_view[left_role];
|
|
auto right_index = old_view[right_role];
|
|
if (!try_mark_two_indices(left_index, right_index, busy_index)) {
|
|
record_swap(Triple_Buffer_Result::Busy);
|
|
return {Triple_Buffer_Result::Busy, old_view, old_view, busy_index};
|
|
}
|
|
auto cur_order = slot_state.load(std::memory_order_acquire);
|
|
if (cur_order != old_order) {
|
|
auto cur_view = decode_order(cur_order);
|
|
unmark_two_indices(left_index, right_index);
|
|
record_swap(Triple_Buffer_Result::State_Changed);
|
|
return {Triple_Buffer_Result::State_Changed, old_view, cur_view, triple_buffer_invalid_index};
|
|
}
|
|
if (!can_swap(old_view)) {
|
|
unmark_two_indices(left_index, right_index);
|
|
record_swap(Triple_Buffer_Result::Condition_Failed);
|
|
return {Triple_Buffer_Result::Condition_Failed, old_view, old_view, triple_buffer_invalid_index};
|
|
}
|
|
auto new_order = swap_order(old_order, left_role, right_role);
|
|
auto new_view = decode_order(new_order);
|
|
auto expected = old_order;
|
|
if (!slot_state.compare_exchange_strong(expected, new_order, std::memory_order_acq_rel, std::memory_order_acquire)) {
|
|
auto cur_view = decode_order(expected);
|
|
unmark_two_indices(left_index, right_index);
|
|
record_swap(Triple_Buffer_Result::State_Changed);
|
|
return {Triple_Buffer_Result::State_Changed, old_view, cur_view, triple_buffer_invalid_index};
|
|
}
|
|
unmark_two_indices(left_index, right_index);
|
|
slot_state.notify_all();
|
|
record_swap(Triple_Buffer_Result::Success);
|
|
return {Triple_Buffer_Result::Success, old_view, new_view, triple_buffer_invalid_index};
|
|
}
|
|
};
|
|
enum State_Role : std::uint8_t {
|
|
State_Edit,
|
|
State_Ready,
|
|
State_Render
|
|
};
|
|
enum Input_Role : std::uint8_t {
|
|
Input_Edit,
|
|
Input_Ready,
|
|
Input_Render
|
|
};
|
|
enum Color_Role : std::uint8_t {
|
|
Color_Front,
|
|
Color_Ready,
|
|
Color_Render
|
|
};
|
|
inline bool buffer_version_newer(std::uint64_t a, std::uint64_t b) {
|
|
return static_cast<std::int64_t>(a - b) > 0;
|
|
}
|
|
struct Input_Data {
|
|
virtual ~Input_Data() = default;
|
|
virtual void clear() {}
|
|
};
|
|
struct Render_State {
|
|
virtual ~Render_State() = default;
|
|
std::uint64_t version = 0;
|
|
Render_Data* dPtr{};
|
|
SRC src();
|
|
};
|
|
struct Input_Buffer_Slot {
|
|
Input_Data* data{};
|
|
std::uint64_t version = 0;
|
|
bool pending = false;
|
|
};
|
|
struct Pixel_Buffer_Slot {
|
|
std::atomic<std::uint64_t> version{0};
|
|
QImage image;
|
|
QRegion dirtyRegion;
|
|
};
|
|
struct Pixel_Buffer_Lease {
|
|
Pixel_Buffer_Slot* buffer{};
|
|
Triple_Buffer_Lease lease{};
|
|
explicit operator bool() const {
|
|
return buffer && lease;
|
|
}
|
|
};
|
|
struct Render_Data {
|
|
Render_Data() {
|
|
mStateControl.set_stats(&mStateBufferStats);
|
|
mInputControl.set_stats(&mInputBufferStats);
|
|
mPixelControl.set_stats(&mPixelBufferStats);
|
|
}
|
|
Renderable* qPtr{};
|
|
bool mRenderStart = false;
|
|
Triple_Role_Buffer_Control mStateControl;
|
|
Triple_Buffer_Stats mStateBufferStats;
|
|
std::array<Render_State*, 3> mStateBuffers{};
|
|
Triple_Role_Buffer_Control mInputControl;
|
|
Triple_Buffer_Stats mInputBufferStats;
|
|
std::array<Input_Buffer_Slot, 3> mInputBuffers{};
|
|
std::uint64_t mInputVersion = 0;
|
|
std::atomic<Plot_Buffer_Acquire_Mode> mBufferAcquireMode{Plot_Buffer_Acquire_Mode::Wait};
|
|
std::atomic<RenderAble_Render_Schedule_Mode> mRenderScheduleMode{RenderAble_Render_Schedule_Mode::Inherit};
|
|
std::atomic<double> mRenderScheduleMaxFps{0.0};
|
|
std::atomic<std::uint64_t> mNextRenderSubmitNs{0};
|
|
Triple_Role_Buffer_Control mPixelControl;
|
|
Triple_Buffer_Stats mPixelBufferStats;
|
|
std::array<Pixel_Buffer_Slot, 3> mPixelBuffers{};
|
|
std::atomic_bool mIndependentPixelCache{false};
|
|
std::atomic_bool mIndependentRenderQueued{false};
|
|
std::atomic<std::uint64_t> mPixelEditVersion{1};
|
|
std::uint64_t mPixelRenderVersion = 0;
|
|
Render_State* state(std::uint8_t role) const {
|
|
return state_by_index(mStateControl.read_view()[role]);
|
|
}
|
|
Render_State* state(SRC src) const {
|
|
return state(static_cast<std::uint8_t>(src));
|
|
}
|
|
Render_State* state_by_index(std::uint8_t index) const {
|
|
return mStateBuffers[index];
|
|
}
|
|
Plot_Buffer_Acquire_Mode buffer_acquire_mode() const {
|
|
return mBufferAcquireMode.load(std::memory_order_acquire);
|
|
}
|
|
void set_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode) {
|
|
mBufferAcquireMode.store(mode, std::memory_order_release);
|
|
}
|
|
RenderAble_Render_Schedule_Mode render_schedule_mode() const {
|
|
return mRenderScheduleMode.load(std::memory_order_acquire);
|
|
}
|
|
void set_render_schedule_mode(RenderAble_Render_Schedule_Mode mode) {
|
|
mRenderScheduleMode.store(mode, std::memory_order_release);
|
|
}
|
|
double render_schedule_max_fps() const {
|
|
return mRenderScheduleMaxFps.load(std::memory_order_acquire);
|
|
}
|
|
void set_render_schedule_max_fps(double fps) {
|
|
mRenderScheduleMaxFps.store(fps, std::memory_order_release);
|
|
}
|
|
Triple_Buffer_Lease mark_state_role(std::uint8_t role) {
|
|
if (buffer_acquire_mode() == Plot_Buffer_Acquire_Mode::Wait) {
|
|
return mStateControl.wait_mark_use_role(role);
|
|
}
|
|
auto record = mStateControl.try_mark_use_role(role);
|
|
if (record.result != Triple_Buffer_Result::Success) {
|
|
return {};
|
|
}
|
|
return record.lease;
|
|
}
|
|
void unmark_state(Triple_Buffer_Lease lease) {
|
|
mStateControl.unmark_use(lease);
|
|
}
|
|
void clear_state_buffers() {
|
|
for (Render_State*& state : mStateBuffers) {
|
|
delete state;
|
|
state = nullptr;
|
|
}
|
|
mStateControl.reset();
|
|
}
|
|
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 mInputBuffers[index].data;
|
|
}
|
|
Input_Buffer_Slot& input_slot(std::uint8_t role) {
|
|
return input_slot_by_index(mInputControl.read_view()[role]);
|
|
}
|
|
const Input_Buffer_Slot& input_slot(std::uint8_t role) const {
|
|
return input_slot_by_index(mInputControl.read_view()[role]);
|
|
}
|
|
Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) {
|
|
return mInputBuffers[index];
|
|
}
|
|
const Input_Buffer_Slot& input_slot_by_index(std::uint8_t index) const {
|
|
return mInputBuffers[index];
|
|
}
|
|
bool has_pending_state() const {
|
|
auto view = mStateControl.read_view();
|
|
return buffer_version_newer(mStateBuffers[view[State_Edit]]->version, mStateBuffers[view[State_Render]]->version);
|
|
}
|
|
bool has_pending_input() const {
|
|
auto view = mInputControl.read_view();
|
|
const Input_Buffer_Slot& edit = mInputBuffers[view[Input_Edit]];
|
|
const Input_Buffer_Slot& ready = mInputBuffers[view[Input_Ready]];
|
|
const Input_Buffer_Slot& render = mInputBuffers[view[Input_Render]];
|
|
return (edit.pending && buffer_version_newer(edit.version, render.version)) || (ready.pending && buffer_version_newer(ready.version, render.version));
|
|
}
|
|
bool has_pending_render_data() const {
|
|
return has_pending_state() || has_pending_input();
|
|
}
|
|
void mark_input_dirty(Triple_Buffer_Lease lease, bool notifyRender = true);
|
|
void clear_input_role(std::uint8_t role) {
|
|
Input_Buffer_Slot& slot = input_slot(role);
|
|
slot.data->clear();
|
|
slot.pending = false;
|
|
}
|
|
void clear_render_input() {
|
|
clear_input_role(Input_Render);
|
|
}
|
|
void clear_input_buffers() {
|
|
for (Input_Buffer_Slot& slot : mInputBuffers) {
|
|
delete slot.data;
|
|
slot.data = nullptr;
|
|
slot.version = 0;
|
|
slot.pending = false;
|
|
}
|
|
mInputVersion = 0;
|
|
mInputControl.reset();
|
|
}
|
|
bool independent_pixel_cache() const {
|
|
return mIndependentPixelCache.load(std::memory_order_acquire);
|
|
}
|
|
void set_independent_pixel_cache(bool enabled) {
|
|
mIndependentPixelCache.store(enabled, std::memory_order_release);
|
|
}
|
|
Pixel_Buffer_Slot* pixel_by_index(std::uint8_t index) {
|
|
return &mPixelBuffers[index];
|
|
}
|
|
Pixel_Buffer_Lease wait_render_pixel() {
|
|
auto lease = mPixelControl.wait_mark_use_role(Color_Render);
|
|
return {pixel_by_index(lease.index), lease};
|
|
}
|
|
Pixel_Buffer_Lease try_front_pixel() {
|
|
auto record = mPixelControl.try_mark_use_role(Color_Front);
|
|
if (record.result != Triple_Buffer_Result::Success) return {};
|
|
return {pixel_by_index(record.lease.index), record.lease};
|
|
}
|
|
void unmark_pixel(Pixel_Buffer_Lease lease) {
|
|
mPixelControl.unmark_use(lease.lease);
|
|
}
|
|
bool has_new_pixel_state() const {
|
|
return buffer_version_newer(mPixelEditVersion.load(std::memory_order_acquire), mPixelRenderVersion);
|
|
}
|
|
void mark_pixel_dirty() {
|
|
mPixelEditVersion.fetch_add(1, std::memory_order_release);
|
|
}
|
|
QRegion publish_render_pixel() {
|
|
mPixelControl.wait_swap_role(Color_Ready, Color_Render, [this](const Triple_Buffer_View& view) {
|
|
auto render = pixel_by_index(view[Color_Render]);
|
|
auto ready = pixel_by_index(view[Color_Ready]);
|
|
return buffer_version_newer(render->version.load(std::memory_order_acquire), ready->version.load(std::memory_order_acquire));
|
|
});
|
|
auto view = mPixelControl.read_view();
|
|
auto ready = pixel_by_index(view[Color_Ready]);
|
|
auto front = pixel_by_index(view[Color_Front]);
|
|
if (buffer_version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire))) return ready->dirtyRegion;
|
|
return {};
|
|
}
|
|
Pixel_Buffer_Lease consume_ready_pixel(Triple_Buffer_Mode mode) {
|
|
auto canSwap = [this](const Triple_Buffer_View& view) {
|
|
auto ready = pixel_by_index(view[Color_Ready]);
|
|
auto front = pixel_by_index(view[Color_Front]);
|
|
return buffer_version_newer(ready->version.load(std::memory_order_acquire), front->version.load(std::memory_order_acquire));
|
|
};
|
|
if (mode == Triple_Buffer_Mode::Wait) {
|
|
mPixelControl.wait_swap_role(Color_Front, Color_Ready, canSwap);
|
|
}
|
|
else {
|
|
mPixelControl.try_swap_role(Color_Front, Color_Ready, canSwap);
|
|
}
|
|
return try_front_pixel();
|
|
}
|
|
protected:
|
|
virtual void sync_state_pipeline() = 0;
|
|
virtual Render_State* render_state() = 0;
|
|
virtual Render_State* ready_state() = 0;
|
|
virtual Render_State* edit_state() = 0;
|
|
virtual Input_Data* render_input_data() = 0;
|
|
virtual Input_Data* edit_input_data() = 0;
|
|
virtual ~Render_Data() {
|
|
clear_state_buffers();
|
|
clear_input_buffers();
|
|
}
|
|
virtual void draw(QPainter* painter) {}
|
|
virtual void prepare_data() {
|
|
sync_state_pipeline();
|
|
}
|
|
virtual bool select_test(const QPointF& pos) {
|
|
return false;
|
|
}
|
|
virtual void plot_event(QEvent* event) {}
|
|
virtual void mousePressEvent(QMouseEvent* event) {}
|
|
virtual void mouseMoveEvent(QMouseEvent* event) {}
|
|
virtual void mouseReleaseEvent(QMouseEvent* event) {}
|
|
virtual void set_hover_state(const QPoint& pos, bool active) {}
|
|
virtual void keyPressEvent(QKeyEvent* event) {}
|
|
virtual void keyReleaseEvent(QKeyEvent* event) {}
|
|
virtual void first_resize_event(QResizeEvent* event) {}
|
|
virtual void resizeEvent(QResizeEvent* event) {}
|
|
virtual void wheelEvent(QWheelEvent* event) {}
|
|
friend struct Renderable;
|
|
};
|
|
struct Base_Render_Data : Render_Data {
|
|
Render_State* render_state() override {
|
|
return nullptr;
|
|
}
|
|
Render_State* ready_state() override {
|
|
return nullptr;
|
|
}
|
|
Render_State* edit_state() override {
|
|
return nullptr;
|
|
}
|
|
Input_Data* render_input_data() override {
|
|
return nullptr;
|
|
}
|
|
Input_Data* edit_input_data() override {
|
|
return nullptr;
|
|
}
|
|
void sync_state_pipeline() override {}
|
|
};
|
|
class Plot;
|
|
struct LIB_DECL Render_Edit_Guard {
|
|
Render_Edit_Guard(Render_Data* data, Triple_Buffer_Lease lease);
|
|
~Render_Edit_Guard();
|
|
Render_Data* mData{};
|
|
Triple_Buffer_Lease mLease{};
|
|
};
|
|
struct LIB_DECL Render_Input_Guard {
|
|
Render_Input_Guard(Render_Data* data, Triple_Buffer_Lease lease);
|
|
~Render_Input_Guard();
|
|
Render_Data* mData{};
|
|
Triple_Buffer_Lease mLease{};
|
|
};
|
|
struct Renderable {
|
|
enum class JobState {
|
|
Idle,
|
|
Queued,
|
|
Running
|
|
};
|
|
struct Color_Buffer {
|
|
std::atomic<std::uint64_t> version{0};
|
|
QImage image;
|
|
QRegion dirtyRegion;
|
|
};
|
|
struct Color_Buffer_Lease {
|
|
Color_Buffer* buffer{};
|
|
Triple_Buffer_Lease lease{};
|
|
explicit operator bool() const {
|
|
return buffer && lease;
|
|
}
|
|
};
|
|
struct Render_Pipeline {
|
|
Triple_Role_Buffer_Control colorControl;
|
|
Triple_Buffer_Stats colorBufferStats;
|
|
std::array<Color_Buffer, 3> colorBuffers;
|
|
std::atomic<std::uint64_t> editStateVersion{1};
|
|
std::uint64_t renderStateVersion = 0;
|
|
JobState jobState = JobState::Idle;
|
|
std::atomic_bool paintRequestPending{false};
|
|
Render_Pipeline();
|
|
void mark_edit_state();
|
|
bool has_new_state() const;
|
|
Color_Buffer* color_by_index(std::uint8_t index);
|
|
Color_Buffer* color_by_role(std::uint8_t role);
|
|
Color_Buffer_Lease wait_render_color();
|
|
Color_Buffer_Lease wait_front_color();
|
|
Color_Buffer_Lease try_front_color();
|
|
void unmark_color(Color_Buffer_Lease lease);
|
|
QRegion publish_render_color();
|
|
Color_Buffer_Lease consume_ready_color(Triple_Buffer_Mode mode);
|
|
};
|
|
virtual Render_Data* create_render_data() {
|
|
return new Base_Render_Data;
|
|
}
|
|
virtual Render_State* create_render_state() {
|
|
return new Render_State;
|
|
}
|
|
virtual Input_Data* create_input_data() {
|
|
return new Input_Data;
|
|
}
|
|
virtual void init(Plot* plot, QString layerName = "");
|
|
virtual void draw(QPainter* painter) {
|
|
dPtr->draw(painter);
|
|
}
|
|
virtual void prepare_data() {
|
|
dPtr->prepare_data();
|
|
}
|
|
virtual bool select_test(const QPointF& pos) {
|
|
return dPtr->select_test(pos);
|
|
}
|
|
virtual void plot_event(QEvent* event) {
|
|
dPtr->plot_event(event);
|
|
}
|
|
virtual void mousePressEvent(QMouseEvent* event) {
|
|
dPtr->mousePressEvent(event);
|
|
}
|
|
virtual void mouseMoveEvent(QMouseEvent* event) {
|
|
dPtr->mouseMoveEvent(event);
|
|
}
|
|
virtual void mouseReleaseEvent(QMouseEvent* event) {
|
|
dPtr->mouseReleaseEvent(event);
|
|
}
|
|
virtual void set_hover_state(const QPoint& pos, bool active) {
|
|
dPtr->set_hover_state(pos, active);
|
|
}
|
|
virtual void keyPressEvent(QKeyEvent* event) {
|
|
dPtr->keyPressEvent(event);
|
|
}
|
|
virtual void keyReleaseEvent(QKeyEvent* event) {
|
|
dPtr->keyReleaseEvent(event);
|
|
}
|
|
virtual void first_resize_event(QResizeEvent* event) {
|
|
dPtr->first_resize_event(event);
|
|
}
|
|
virtual void resizeEvent(QResizeEvent* event) {
|
|
dPtr->resizeEvent(event);
|
|
}
|
|
virtual void wheelEvent(QWheelEvent* event) {
|
|
dPtr->wheelEvent(event);
|
|
};
|
|
static bool version_newer(std::uint64_t a, std::uint64_t b) {
|
|
return buffer_version_newer(a, b);
|
|
}
|
|
Plot_Buffer_Acquire_Mode buffer_acquire_mode() const;
|
|
void set_buffer_acquire_mode(Plot_Buffer_Acquire_Mode mode);
|
|
RenderAble_Render_Schedule_Mode render_schedule_mode() const;
|
|
void set_render_schedule_mode(RenderAble_Render_Schedule_Mode mode);
|
|
double render_schedule_max_fps() const;
|
|
void set_render_schedule_max_fps(double fps);
|
|
bool independent_pixel_cache() const;
|
|
void set_independent_pixel_cache(bool enabled);
|
|
void mark_state_dirty();
|
|
void mark_render_dirty();
|
|
QString mObjectName;
|
|
Render_Data* dPtr{};
|
|
Plot* mPlot{};
|
|
[[nodiscard]] bool ok() const;
|
|
bool mPrepared = false;
|
|
// 这里面可以预先准备数据用来同步
|
|
virtual ~Renderable();
|
|
virtual bool select(QPointF pos) {
|
|
return false;
|
|
}
|
|
bool mVisable = true;
|
|
Renderable* mParent{};
|
|
QVector<Renderable*> mBeRelyList;
|
|
bool dect_cycle_rely();
|
|
private:
|
|
bool has_cycle_rely(Renderable* node, QSet<Renderable*>& visited, QSet<Renderable*>& recursionStack, QVector<Renderable*>& currentPath);
|
|
};
|
|
template <typename T>
|
|
concept Render_State_Type = std::derived_from<T, Render_State> && std::is_copy_assignable_v<T>;
|
|
template <typename T>
|
|
concept Input_Data_Type = std::derived_from<T, Input_Data>;
|
|
template <typename T>
|
|
concept Render_Data_Type = std::derived_from<T, Render_Data>;
|
|
template <Render_Data_Type Base, typename Owner, Render_State_Type State, Input_Data_Type Input>
|
|
struct Typed_Render_Data_Base : Base {
|
|
Owner* q() {
|
|
return reinterpret_cast<Owner*>(this->qPtr);
|
|
}
|
|
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(SRC 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>
|
|
Field state_value(Field State::* field, SRC src) {
|
|
return state_data(src)->*field;
|
|
}
|
|
template <typename Field>
|
|
void set_state_value(Field State::* field, Field value) {
|
|
auto lease = this->mStateControl.wait_mark_use_role(State_Edit);
|
|
auto state = state_data_by_index(lease.index);
|
|
Render_Edit_Guard guard(this, lease);
|
|
state->*field = std::move(value);
|
|
}
|
|
void sync_state_pipeline() override {
|
|
Triple_Buffer_Lease editLease = this->mStateControl.wait_mark_use_role(State_Edit);
|
|
Triple_Buffer_Lease readyLease = this->mStateControl.wait_mark_use_role(State_Ready);
|
|
auto ready = reinterpret_cast<State*>(this->state_by_index(readyLease.index));
|
|
auto edit = reinterpret_cast<State*>(this->state_by_index(editLease.index));
|
|
if (buffer_version_newer(edit->version, ready->version)) *ready = *edit;
|
|
this->mStateControl.unmark_use(readyLease);
|
|
this->mStateControl.unmark_use(editLease);
|
|
this->mStateControl.wait_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->mInputControl.wait_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->mInputControl.wait_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);
|
|
});
|
|
if (!this->mRenderStart) this->mRenderStart = true;
|
|
}
|
|
};
|
|
template <typename Owner, Render_State_Type State, Input_Data_Type Input>
|
|
using Typed_Render_Data = Typed_Render_Data_Base<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->dPtr = new Data;
|
|
owner->mObjectName = name;
|
|
}
|
|
static Data* d(Owner* owner) {
|
|
return reinterpret_cast<Data*>(owner->dPtr);
|
|
}
|
|
static Render_Data* create_render_data() {
|
|
return new Data;
|
|
}
|
|
static Render_State* create_render_state() {
|
|
return new State;
|
|
}
|
|
static Input_Data* create_input_data() {
|
|
return new Input;
|
|
}
|
|
};
|
|
template <Render_Data_Type Data, Render_State_Type State>
|
|
struct Render_State_Lease {
|
|
explicit Render_State_Lease(Data* data) : data(data), lease(data->mStateControl.wait_mark_use_role(State_Edit)), state(reinterpret_cast<State*>(data->state_by_index(lease.index))) {}
|
|
Render_State_Lease(const Render_State_Lease&) = delete;
|
|
Render_State_Lease& operator=(const Render_State_Lease&) = delete;
|
|
~Render_State_Lease() {
|
|
data->unmark_state(lease);
|
|
}
|
|
State* operator->() {
|
|
return state;
|
|
}
|
|
State& operator*() {
|
|
return *state;
|
|
}
|
|
Data* data{};
|
|
Triple_Buffer_Lease lease{};
|
|
State* state{};
|
|
};
|
|
template <Render_Data_Type Data, Render_State_Type State>
|
|
struct Render_Edit_Lease {
|
|
explicit Render_Edit_Lease(Data* data) : data(data), lease(data->mStateControl.wait_mark_use_role(State_Edit)), state(reinterpret_cast<State*>(data->state_by_index(lease.index))) {}
|
|
Render_Edit_Lease(const Render_Edit_Lease&) = delete;
|
|
Render_Edit_Lease& operator=(const Render_Edit_Lease&) = delete;
|
|
~Render_Edit_Lease() {
|
|
++data->state_by_index(lease.index)->version;
|
|
if (data->qPtr) data->qPtr->mark_render_dirty();
|
|
data->unmark_state(lease);
|
|
}
|
|
State* operator->() {
|
|
return state;
|
|
}
|
|
State& operator*() {
|
|
return *state;
|
|
}
|
|
Data* data{};
|
|
Triple_Buffer_Lease lease{};
|
|
State* state{};
|
|
};
|
|
template <Render_Data_Type Data, Input_Data_Type Input>
|
|
struct Render_Input_Lease {
|
|
explicit Render_Input_Lease(Data* data, bool notify_render = true) : data(data), notify_render(notify_render), lease(data->mInputControl.wait_mark_use_role(Input_Edit)), input(reinterpret_cast<Input*>(data->input_data_by_index(lease.index))) {}
|
|
Render_Input_Lease(const Render_Input_Lease&) = delete;
|
|
Render_Input_Lease& operator=(const Render_Input_Lease&) = delete;
|
|
~Render_Input_Lease() {
|
|
data->mark_input_dirty(lease, notify_render);
|
|
data->mInputControl.unmark_use(lease);
|
|
}
|
|
Input* operator->() {
|
|
return input;
|
|
}
|
|
Input& operator*() {
|
|
return *input;
|
|
}
|
|
Data* data{};
|
|
bool notify_render{};
|
|
Triple_Buffer_Lease lease{};
|
|
Input* input{};
|
|
};
|
|
}
|