diff --git a/YSGraphic_Core/RenderAble.cpp b/YSGraphic_Core/RenderAble.cpp index 797bd27..d2f1f25 100644 --- a/YSGraphic_Core/RenderAble.cpp +++ b/YSGraphic_Core/RenderAble.cpp @@ -17,6 +17,7 @@ namespace YSG { } RenderAble::RenderPipeline::RenderPipeline() { + colorControl.set_stats(&colorBufferStats); colorControl.reset(); } void RenderAble::RenderPipeline::markEditState() { @@ -49,12 +50,18 @@ namespace YSG { void RenderAble::RenderPipeline::unmarkColor(ColorBufferLease lease) { colorControl.unmark_use(lease.lease); } - Triple_Buffer_Mode RenderAble::bufferAcquireMode() const { + Plot_Buffer_Acquire_Mode RenderAble::bufferAcquireMode() const { return dPtr->bufferAcquireMode(); } - void RenderAble::setBufferAcquireMode(Triple_Buffer_Mode mode) { + void RenderAble::setBufferAcquireMode(Plot_Buffer_Acquire_Mode mode) { dPtr->setBufferAcquireMode(mode); } + bool RenderAble::independentPixelCache() const { + return dPtr->independentPixelCache(); + } + void RenderAble::setIndependentPixelCache(bool enabled) { + dPtr->setIndependentPixelCache(enabled); + } QRegion RenderAble::RenderPipeline::publishRenderColor() { colorControl.wait_swap_role(Color_Ready, Color_Render, [this](const Triple_Buffer_View& view) { auto render = colorByIndex(view[Color_Render]); @@ -101,11 +108,11 @@ namespace YSG { mData->markInputDirty(mLease); mData->mInputControl.unmark_use(mLease); } - void RenderData::markInputDirty(Triple_Buffer_Lease lease) { + void RenderData::markInputDirty(Triple_Buffer_Lease lease, bool notifyRender) { InputBufferSlot& slot = inputSlotByIndex(lease.index); slot.version = ++mInputVersion; slot.pending = true; - if(qPtr) qPtr->markRenderDirty(); + if(notifyRender && qPtr) qPtr->markRenderDirty(); } void RenderAble::init(Plot* plot, QString layerName) { if(layerName.isEmpty()) { @@ -114,6 +121,7 @@ namespace YSG { plot->mLayerMap[layerName]->mRenderAbles.append(this); mPlot = plot; if(!dPtr) dPtr = createRenderData(); + dPtr->setBufferAcquireMode(plot->renderAbleBufferAcquireMode()); dPtr->clearStateBuffers(); dPtr->clearInputBuffers(); dPtr->mStateBuffers[State_Edit] = createRenderState(); @@ -138,7 +146,13 @@ namespace YSG { markRenderDirty(); } void RenderAble::markRenderDirty() { - if(mPlot) mPlot->markRenderStateDirty(); + if(!mPlot) return; + if(dPtr && dPtr->independentPixelCache()) { + dPtr->markPixelDirty(); + mPlot->markIndependentRenderDirty(this); + return; + } + mPlot->markRenderStateDirty(); } bool RenderAble::ok() const { return dPtr->mRenderStart && mPlot && !mPlot->mFirstResize && !mPlot->mFirstPrepareData; diff --git a/YSGraphic_Core/RenderAble.h b/YSGraphic_Core/RenderAble.h index f7a415c..32d3be7 100644 --- a/YSGraphic_Core/RenderAble.h +++ b/YSGraphic_Core/RenderAble.h @@ -19,6 +19,13 @@ enum class Triple_Buffer_Mode : std::uint8_t { Try, Wait }; +enum class Plot_Buffer_Acquire_Mode : std::uint8_t { + Try, + Wait +}; +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 index{}; std::uint8_t operator[](std::uint8_t role) const { @@ -45,8 +52,21 @@ struct Triple_Buffer_Swap_Record { struct alignas(64) Triple_Buffer_Busy { std::atomic_bool busy{false}; }; +struct Triple_Buffer_Stats { + std::atomic mark_success{0}; + std::atomic mark_busy{0}; + std::atomic mark_state_changed{0}; + std::atomic swap_success{0}; + std::atomic swap_busy{0}; + std::atomic swap_state_changed{0}; + std::atomic swap_condition_failed{0}; + std::atomic 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) { @@ -60,15 +80,20 @@ public: } 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); } } @@ -77,13 +102,16 @@ public: 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) { @@ -93,6 +121,7 @@ public: return record.lease; } if(record.result == Triple_Buffer_Result::Busy) { + record_wait(); wait_index_free(record.busy_index); } } @@ -114,8 +143,10 @@ public: 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); } } @@ -123,6 +154,43 @@ public: private: std::atomic slot_state{0}; std::array 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: @@ -205,16 +273,19 @@ private: 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); @@ -223,10 +294,12 @@ private: 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}; } }; @@ -265,15 +338,41 @@ struct InputBufferSlot { std::uint64_t version = 0; bool pending = false; }; +struct PixelBufferSlot { + std::atomic version{0}; + QImage image; + QRegion dirtyRegion; +}; +struct PixelBufferLease { + PixelBufferSlot* buffer{}; + Triple_Buffer_Lease lease{}; + explicit operator bool() const { + return buffer && lease; + } +}; struct RenderData { + RenderData() { + 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 mStateBuffers{}; Triple_Role_Buffer_Control mInputControl; + Triple_Buffer_Stats mInputBufferStats; std::array mInputBuffers{}; std::uint64_t mInputVersion = 0; - std::atomic mBufferAcquireMode{Triple_Buffer_Mode::Wait}; + std::atomic mBufferAcquireMode{Plot_Buffer_Acquire_Mode::Wait}; + Triple_Role_Buffer_Control mPixelControl; + Triple_Buffer_Stats mPixelBufferStats; + std::array mPixelBuffers{}; + std::atomic_bool mIndependentPixelCache{false}; + std::atomic_bool mIndependentRenderQueued{false}; + std::atomic mPixelEditVersion{1}; + std::uint64_t mPixelRenderVersion = 0; RenderState* state(std::uint8_t role) const { return stateByIndex(mStateControl.read_view()[role]); } @@ -283,14 +382,14 @@ struct RenderData { RenderState* stateByIndex(std::uint8_t index) const { return mStateBuffers[index]; } - Triple_Buffer_Mode bufferAcquireMode() const { + Plot_Buffer_Acquire_Mode bufferAcquireMode() const { return mBufferAcquireMode.load(std::memory_order_acquire); } - void setBufferAcquireMode(Triple_Buffer_Mode mode) { + void setBufferAcquireMode(Plot_Buffer_Acquire_Mode mode) { mBufferAcquireMode.store(mode, std::memory_order_release); } Triple_Buffer_Lease markStateRole(std::uint8_t role) { - if(bufferAcquireMode() == Triple_Buffer_Mode::Wait) { + if(bufferAcquireMode() == Plot_Buffer_Acquire_Mode::Wait) { return mStateControl.wait_mark_use_role(role); } auto record = mStateControl.try_mark_use_role(role); @@ -327,7 +426,7 @@ struct RenderData { const InputBufferSlot& inputSlotByIndex(std::uint8_t index) const { return mInputBuffers[index]; } - void markInputDirty(Triple_Buffer_Lease lease); + void markInputDirty(Triple_Buffer_Lease lease, bool notifyRender = true); void clearInputRole(std::uint8_t role) { InputBufferSlot& slot = inputSlot(role); slot.data->clear(); @@ -346,6 +445,58 @@ struct RenderData { mInputVersion = 0; mInputControl.reset(); } + bool independentPixelCache() const { + return mIndependentPixelCache.load(std::memory_order_acquire); + } + void setIndependentPixelCache(bool enabled) { + mIndependentPixelCache.store(enabled, std::memory_order_release); + } + PixelBufferSlot* pixelByIndex(std::uint8_t index) { + return &mPixelBuffers[index]; + } + PixelBufferLease waitRenderPixel() { + auto lease = mPixelControl.wait_mark_use_role(Color_Render); + return {pixelByIndex(lease.index), lease}; + } + PixelBufferLease tryFrontPixel() { + auto record = mPixelControl.try_mark_use_role(Color_Front); + if(record.result != Triple_Buffer_Result::Success) return {}; + return {pixelByIndex(record.lease.index), record.lease}; + } + void unmarkPixel(PixelBufferLease lease) { + mPixelControl.unmark_use(lease.lease); + } + bool hasNewPixelState() const { + return buffer_version_newer(mPixelEditVersion.load(std::memory_order_acquire), mPixelRenderVersion); + } + void markPixelDirty() { + mPixelEditVersion.fetch_add(1, std::memory_order_release); + } + QRegion publishRenderPixel() { + mPixelControl.wait_swap_role(Color_Ready, Color_Render, [this](const Triple_Buffer_View& view) { + auto render = pixelByIndex(view[Color_Render]); + auto ready = pixelByIndex(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 = pixelByIndex(view[Color_Ready]); + auto front = pixelByIndex(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 {}; + } + PixelBufferLease consumeReadyPixel(Triple_Buffer_Mode mode) { + auto canSwap = [this](const Triple_Buffer_View& view) { + auto ready = pixelByIndex(view[Color_Ready]); + auto front = pixelByIndex(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 tryFrontPixel(); + } protected: virtual void syncStatePipeline() = 0; virtual RenderState* renderState() = 0; @@ -427,6 +578,7 @@ struct RenderAble { }; struct RenderPipeline { Triple_Role_Buffer_Control colorControl; + Triple_Buffer_Stats colorBufferStats; std::array colorBuffers; std::atomic editStateVersion{1}; std::uint64_t renderStateVersion = 0; @@ -496,8 +648,10 @@ struct RenderAble { static bool version_newer(std::uint64_t a, std::uint64_t b) { return buffer_version_newer(a, b); } - Triple_Buffer_Mode bufferAcquireMode() const; - void setBufferAcquireMode(Triple_Buffer_Mode mode); + Plot_Buffer_Acquire_Mode bufferAcquireMode() const; + void setBufferAcquireMode(Plot_Buffer_Acquire_Mode mode); + bool independentPixelCache() const; + void setIndependentPixelCache(bool enabled); void markStateDirty(); void markRenderDirty(); QString mObjectName; diff --git a/YSGraphic_Core/base/PerformanceShower.cpp b/YSGraphic_Core/base/PerformanceShower.cpp index d1847df..a4a3851 100644 --- a/YSGraphic_Core/base/PerformanceShower.cpp +++ b/YSGraphic_Core/base/PerformanceShower.cpp @@ -3,6 +3,7 @@ namespace YSG { PerformanceShower::PerformanceShower() { dPtr = new PerformanceShowerPrivate; mObjectName = "PerformanceShower"; + setIndependentPixelCache(true); } PerformanceShowerPrivate* PerformanceShower::d() { return reinterpret_cast(dPtr); @@ -30,38 +31,88 @@ namespace YSG { appendEvent({PerformanceEventType::Clear}); } void PerformanceShower::counterIncrease(const QString& counterName) { + incrementCounter("General", counterName); + } + void PerformanceShower::incrementCounter(const QString& scope, const QString& name) { PerformanceEvent event; event.type = PerformanceEventType::Counter; - event.key = counterName; + event.scope = scope; + event.key = name; appendEvent(event); } void PerformanceShower::setPerformanceLine(const QString& key, const PerformanceLine& line) { PerformanceEvent event; event.type = PerformanceEventType::Line; + event.scope = "General"; event.key = key; event.line = line; appendEvent(event); } void PerformanceShower::setInfo(const QString& key, const QString& value) { + setState("General", key, value); + } + void PerformanceShower::setState(const QString& scope, const QString& name, const QString& value) { PerformanceEvent event; - event.type = PerformanceEventType::Info; - event.key = key; + event.type = PerformanceEventType::State; + event.scope = scope; + event.key = name; event.value = value; appendEvent(event); } - void PerformanceShower::recordCost(const QString& key, double value) { + void PerformanceShower::setGauge(const QString& scope, const QString& name, double value) { PerformanceEvent event; - event.type = PerformanceEventType::Cost; - event.key = key; + event.type = PerformanceEventType::Gauge; + event.scope = scope; + event.key = name; event.number = value; appendEvent(event); } + void PerformanceShower::recordCost(const QString& key, double value) { + recordDuration("General", key, value); + } + void PerformanceShower::recordDuration(const QString& scope, const QString& name, double value) { + PerformanceEvent event; + event.type = PerformanceEventType::Duration; + event.scope = scope; + event.key = name; + event.number = value; + appendEvent(event); + } + void PerformanceShower::setViewFilter(const QString& name) { + setViewFilter(name, {}); + } + void PerformanceShower::setViewFilter(const QString& group, const QString& name) { + PerformanceEvent event; + event.type = PerformanceEventType::SelectView; + event.scope = group; + event.key = name; + appendEvent(event); + } + void PerformanceShower::scrollView(int lines) { + PerformanceEvent event; + event.type = PerformanceEventType::ScrollView; + event.number = lines; + appendEvent(event); + } void PerformanceShower::appendEvent(const PerformanceEvent& event) { if(!enabled() && event.type != PerformanceEventType::Clear) return; auto pd = d(); Triple_Buffer_Lease inputLease = pd->mInputControl.wait_mark_use_role(Input_Edit); auto input = reinterpret_cast(pd->inputDataByIndex(inputLease.index)); input->events.append(event); - RenderInputGuard inputGuard(pd, inputLease); + pd->markInputDirty(inputLease, false); + pd->mInputControl.unmark_use(inputLease); + notifyObservedUpdate(event.type == PerformanceEventType::Clear || event.type == PerformanceEventType::SelectView || event.type == PerformanceEventType::ScrollView); + } + void PerformanceShower::notifyObservedUpdate(bool force) { + std::uint64_t now = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); + std::uint64_t last = mLastNotifyNs.load(std::memory_order_acquire); + if(!force && now - last < 100000000) return; + if(force) { + mLastNotifyNs.store(now, std::memory_order_release); + markRenderDirty(); + return; + } + if(force || mLastNotifyNs.compare_exchange_strong(last, now, std::memory_order_acq_rel, std::memory_order_acquire)) markRenderDirty(); } } diff --git a/YSGraphic_Core/base/PerformanceShower_p.h b/YSGraphic_Core/base/PerformanceShower_p.h index 3af2a43..baf76de 100644 --- a/YSGraphic_Core/base/PerformanceShower_p.h +++ b/YSGraphic_Core/base/PerformanceShower_p.h @@ -2,7 +2,21 @@ #include "Core/Statistics/Statistics.h" #include "../RenderAble.h" #include +#include +#include +#include namespace YSG { + inline QString performanceAlignText(const QString& text, int& width) { + width = qMax(width, static_cast(text.size())); + return QString("%1").arg(text, width); + } + inline QString performanceNumber(double value, int& width, int precision = 2) { + return performanceAlignText(QString::number(value, 'f', precision), width); + } + template + inline QString performanceCount(T value, int& width) { + return performanceAlignText(QString::number(static_cast(value)), width); + } struct PerformanceLine { PerformanceLine() = default; explicit PerformanceLine(const QString& data) { @@ -13,10 +27,12 @@ namespace YSG { } mPlaceHolderNum = argIndex - 1; mValues.resize(mPlaceHolderNum); + mValueWidths.resize(mPlaceHolderNum); } QString mData; int mPlaceHolderNum{}; QVector mValues; + QVector mValueWidths; Psc::Value_Statistics mStatistics; void update(double value) { mStatistics.update(value); @@ -24,46 +40,99 @@ namespace YSG { if(mValues.size() > 1) mValues[1] = mStatistics.smooth; if(mValues.size() > 2) mValues[2] = mStatistics.variation; } + int& widthAt(int index) { + if(mValueWidths.size() <= index) mValueWidths.resize(index + 1); + return mValueWidths[index]; + } QString toString() { QString ret = mData; int argIndex = 1; for(const double& value : mValues) { - ret = ret.replace("%" + QString::number(argIndex), QString::number(value, 'f', 2)); + ret = ret.replace("%" + QString::number(argIndex), performanceNumber(value, widthAt(argIndex - 1))); argIndex++; } return ret; } }; - struct CounterLine { - QString mName; - Psc::Speed_Statistics mStatistics; - [[nodiscard]] QString toString() const { - return QString("%1 :%2 avg:%3 (次/s)") - .arg(mName, QString::number(mStatistics.instant_speed, 'f', 2), QString::number(mStatistics.average_speed, 'f', 2)); - } + enum class PerformanceMetricType : std::uint8_t { + Duration, + Counter, + Gauge, + State, + Line }; enum class PerformanceEventType : std::uint8_t { Clear, + Duration, Counter, Line, - Info, - Cost + Gauge, + State, + SelectView, + ScrollView }; struct PerformanceEvent { PerformanceEventType type = PerformanceEventType::Clear; + QString scope; QString key; QString value; PerformanceLine line; double number{}; }; + struct PerformanceMetric { + PerformanceMetricType type = PerformanceMetricType::State; + Psc::Value_Statistics value; + Psc::Speed_Statistics speed; + PerformanceLine line; + QString text; + double gauge{}; + std::uint64_t count{}; + QVector widths; + int& widthAt(int index) { + if(widths.size() <= index) widths.resize(index + 1); + return widths[index]; + } + [[nodiscard]] QString toString(const QString& name) { + switch(type) { + case PerformanceMetricType::Duration: + if(name.contains("%1")) { + QString ret = name; + ret = ret.replace("%1", performanceNumber(value.instant, widthAt(0))); + ret = ret.replace("%2", performanceNumber(value.smooth, widthAt(1))); + ret = ret.replace("%3", performanceNumber(value.variation, widthAt(2))); + ret = ret.replace("%4", performanceNumber(value.p95, widthAt(3))); + return ret; + } + return QString("%1 last:%2ms srtt:%3ms rttvar:%4ms min:%5ms max:%6ms p95:%7ms count:%8") + .arg(name, performanceNumber(value.instant, widthAt(0)), performanceNumber(value.smooth, widthAt(1)), performanceNumber(value.variation, widthAt(2)), performanceNumber(value.min, widthAt(3)), performanceNumber(value.max, widthAt(4)), performanceNumber(value.p95, widthAt(5)), performanceCount(value.times, widthAt(6))); + case PerformanceMetricType::Counter: + return QString("%1 speed:%2/s avg:%3/s count:%4") + .arg(name, performanceNumber(speed.instant_speed, widthAt(0)), performanceNumber(speed.average_speed, widthAt(1)), performanceCount(speed.times, widthAt(2))); + case PerformanceMetricType::Gauge: + return QString("%1: %2").arg(name, performanceNumber(gauge, widthAt(0))); + case PerformanceMetricType::State: + return QString("%1: %2").arg(name, text); + case PerformanceMetricType::Line: + return line.toString(); + } + return {}; + } + }; struct PerformanceShowerRenderState : RenderState { QFont font; int topMargin = 4; int leftMargin = 4; int rightMargin = 28; int bottomMargin = 8; + int columnGap = 12; + int groupSidebarWidth = 92; + int targetSidebarWidth = 170; QColor background = Qt::white; QColor foreground = Qt::black; + QColor sidebarBackground = QColor(235, 235, 235); + QColor selectedBackground = QColor(210, 225, 245); + QColor scrollbarBackground = QColor(210, 210, 210); + QColor scrollbarHandle = QColor(120, 120, 120); }; struct PerformanceShowerInputData : InputData { QVector events; @@ -83,19 +152,40 @@ namespace YSG { bool enabled() const; void refreshCounter(); void counterIncrease(const QString& counterName); + void incrementCounter(const QString& scope, const QString& name); void setPerformanceLine(const QString& key, const PerformanceLine& line); void setInfo(const QString& key, const QString& value); + void setState(const QString& scope, const QString& name, const QString& value); + void setGauge(const QString& scope, const QString& name, double value); void recordCost(const QString& key, double value); + void recordDuration(const QString& scope, const QString& name, double value); + void setViewFilter(const QString& name); + void setViewFilter(const QString& group, const QString& name); + void scrollView(int lines); private: void appendEvent(const PerformanceEvent& event); + void notifyObservedUpdate(bool force); std::atomic_bool mEnabled{false}; + std::atomic mLastNotifyNs{0}; }; struct PerformanceShowerPrivate : RenderData { D_Ptr(PerformanceShower) - QMap mInfoMap; - QMap mCounter; - QMap mInfos; + QMap> mScopes; QVector mAllInfo; + QVector mGroupItems; + QVector mTargetItems; + QVector mGroupRects; + QVector mTargetRects; + QRect mPanelRect; + int mGroupSidebarWidth{}; + int mTargetSidebarWidth{}; + int mContentWidth{}; + int mScrollLine{}; + int mMaxScrollLine{}; + bool mSidebarWidthDirty = true; + bool mContentWidthDirty = true; + QString mSelectedGroup = "Plot"; + QString mSelectedTarget = "Overview"; void prepareData() override { syncStatePipeline(); auto input = renderInputData(); @@ -105,73 +195,281 @@ namespace YSG { clearRenderInput(); rebuildLines(); } + bool selectTest(const QPointF& pos) override { + return q()->enabled() && mPanelRect.contains(pos.toPoint()); + } + void mousePressEvent(QMouseEvent* event) override { + for(int i = 0; i < mGroupRects.size(); ++i) { + if(mGroupRects[i].contains(event->pos())) { + q()->setViewFilter(mGroupItems[i], {}); + return; + } + } + for(int i = 0; i < mTargetRects.size(); ++i) { + if(mTargetRects[i].contains(event->pos())) { + q()->setViewFilter(mSelectedGroup, mTargetItems[i]); + return; + } + } + } + void wheelEvent(QWheelEvent* event) override { + if(!mPanelRect.contains(event->pos())) return; + int steps = event->angleDelta().y() > 0 ? -3 : 3; + q()->scrollView(steps); + } void draw(QPainter* painter) override { - if(!q()->enabled() || mAllInfo.isEmpty()) return; + if(!q()->enabled() || mGroupItems.isEmpty()) return; auto s = renderState(); painter->save(); painter->setFont(s->font); QFontMetrics fm(s->font); + if(mSidebarWidthDirty) updateSidebarWidth(fm, s); + if(mContentWidthDirty) updateContentWidth(fm, s); + int sideLineCount = qMax(mGroupItems.size(), mTargetItems.size()); + int lineCount = qMax(mAllInfo.size(), sideLineCount); + int maxHeight = painter->device()->height(); + int sidebarWidth = mGroupSidebarWidth + mTargetSidebarWidth; + int contentX = sidebarWidth + s->columnGap; + int width = sidebarWidth + s->columnGap + mContentWidth; + int fullHeight = fm.lineSpacing() * lineCount + s->topMargin + s->bottomMargin; + int height = qMin(fullHeight, maxHeight); + int visibleLines = qMax(1, (height - s->topMargin - s->bottomMargin) / fm.lineSpacing()); + mMaxScrollLine = qMax(0, mAllInfo.size() - visibleLines); + mScrollLine = qBound(0, mScrollLine, mMaxScrollLine); + QRect rect(0, 0, width, height); + QRect groupRect(0, 0, mGroupSidebarWidth, height); + QRect targetRect(groupRect.right() + 1, 0, mTargetSidebarWidth, height); + mPanelRect = rect; + mGroupRects.clear(); + mTargetRects.clear(); + painter->fillRect(rect, s->background); + painter->fillRect(groupRect, s->sidebarBackground); + painter->fillRect(targetRect, s->sidebarBackground); + painter->setPen(s->foreground); + for(int i = 0; i < visibleLines && i + mScrollLine < mAllInfo.size(); ++i) { + painter->drawText(QPointF(contentX + s->leftMargin, s->topMargin + i * fm.lineSpacing() + fm.lineSpacing()), mAllInfo[i + mScrollLine]); + } + for(int i = 0; i < mGroupItems.size() && i < visibleLines; ++i) { + QRect itemRect(groupRect.left(), s->topMargin + i * fm.lineSpacing(), mGroupSidebarWidth, fm.lineSpacing()); + mGroupRects.append(itemRect); + if(mGroupItems[i] == mSelectedGroup) painter->fillRect(itemRect, s->selectedBackground); + painter->drawText(QPointF(itemRect.left() + s->leftMargin, itemRect.top() + fm.lineSpacing()), mGroupItems[i]); + } + for(int i = 0; i < mTargetItems.size() && i < visibleLines; ++i) { + QRect itemRect(targetRect.left(), s->topMargin + i * fm.lineSpacing(), mTargetSidebarWidth, fm.lineSpacing()); + mTargetRects.append(itemRect); + if(mTargetItems[i] == mSelectedTarget) painter->fillRect(itemRect, s->selectedBackground); + painter->drawText(QPointF(itemRect.left() + s->leftMargin, itemRect.top() + fm.lineSpacing()), mTargetItems[i]); + } + if(mMaxScrollLine > 0) drawScrollbar(painter, s, QRect(width - 8, 0, 8, height), visibleLines); + painter->restore(); + } + void drawScrollbar(QPainter* painter, PerformanceShowerRenderState* state, const QRect& rect, int visibleLines) { + painter->fillRect(rect, state->scrollbarBackground); + double ratio = static_cast(visibleLines) / static_cast(mAllInfo.size()); + int handleHeight = qMax(16, qRound(rect.height() * ratio)); + int travel = qMax(1, rect.height() - handleHeight); + int top = qRound(static_cast(travel) * static_cast(mScrollLine) / static_cast(mMaxScrollLine)); + painter->fillRect(QRect(rect.left(), rect.top() + top, rect.width(), handleHeight), state->scrollbarHandle); + } + void updateSidebarWidth(const QFontMetrics& fm, PerformanceShowerRenderState* state) { + int groupWidth = 0; + int targetWidth = 0; + for(const QString& item : mGroupItems) groupWidth = qMax(groupWidth, fm.horizontalAdvance(item)); + for(const QString& item : mTargetItems) targetWidth = qMax(targetWidth, fm.horizontalAdvance(item)); + mGroupSidebarWidth = qMax(state->groupSidebarWidth, groupWidth + state->leftMargin + state->rightMargin); + mTargetSidebarWidth = qMax(state->targetSidebarWidth, targetWidth + state->leftMargin + state->rightMargin); + mSidebarWidthDirty = false; + } + void updateContentWidth(const QFontMetrics& fm, PerformanceShowerRenderState* state) { int maxWidth = 0; for(const QString& line : mAllInfo) maxWidth = qMax(maxWidth, fm.horizontalAdvance(line)); - int width = maxWidth + s->leftMargin + s->rightMargin; - int height = fm.lineSpacing() * mAllInfo.size() + s->topMargin + s->bottomMargin; - QRect rect(0, 0, width, height); - painter->fillRect(rect, s->background); - painter->setPen(s->foreground); - for(int i = 0; i < mAllInfo.size(); ++i) { - painter->drawText(QPointF(s->leftMargin, s->topMargin + i * fm.lineSpacing() + fm.lineSpacing()), mAllInfo[i]); - } - painter->restore(); + mContentWidth = maxWidth + state->leftMargin + state->rightMargin; + mContentWidthDirty = false; } void applyEvent(const PerformanceEvent& event) { switch(event.type) { case PerformanceEventType::Clear: - mInfoMap.clear(); - mCounter.clear(); - mInfos.clear(); + mScopes.clear(); mAllInfo.clear(); + mContentWidth = 0; + mContentWidthDirty = true; break; + case PerformanceEventType::Duration: { + PerformanceMetric& metric = mScopes[event.scope][event.key]; + metric.type = PerformanceMetricType::Duration; + metric.value.update(event.number); + ++metric.count; + break; + } case PerformanceEventType::Counter: - if(!mCounter.contains(event.key)) mCounter[event.key].mName = event.key; - mCounter[event.key].mStatistics.update(1); + mScopes[event.scope][event.key].type = PerformanceMetricType::Counter; + mScopes[event.scope][event.key].speed.update(1); + ++mScopes[event.scope][event.key].count; break; case PerformanceEventType::Line: - mInfoMap[event.key] = event.line; + mScopes[event.scope][event.key].type = PerformanceMetricType::Line; + mScopes[event.scope][event.key].line = event.line; break; - case PerformanceEventType::Info: - mInfos[event.key] = event.value; + case PerformanceEventType::Gauge: + mScopes[event.scope][event.key].type = PerformanceMetricType::Gauge; + mScopes[event.scope][event.key].gauge = event.number; + ++mScopes[event.scope][event.key].count; break; - case PerformanceEventType::Cost: - if(!mInfoMap.contains(event.key)) mInfoMap[event.key] = PerformanceLine(event.key); - mInfoMap[event.key].update(event.number); + case PerformanceEventType::State: + mScopes[event.scope][event.key].type = PerformanceMetricType::State; + mScopes[event.scope][event.key].text = event.value; + break; + case PerformanceEventType::SelectView: + mSelectedGroup = event.scope.isEmpty() ? "Plot" : event.scope; + mSelectedTarget = event.key; + mScrollLine = 0; + mContentWidth = 0; + mContentWidthDirty = true; + break; + case PerformanceEventType::ScrollView: + mScrollLine += static_cast(event.number); + mScrollLine = qBound(0, mScrollLine, mMaxScrollLine); break; } } - void rebuildLines() { - QVector lines; - for(PerformanceLine& line : mInfoMap) lines.append(line.toString()); - for(CounterLine& counter : mCounter) lines.append(counter.toString()); - for(auto it = mInfos.cbegin(); it != mInfos.cend(); ++it) { - lines.append(QString("%1: %2").arg(it.key(), it.value())); + static QString objectNameFromMetric(const QString& key) { + int stateIndex = key.indexOf(".State."); + if(stateIndex > 0) return key.left(stateIndex); + int inputIndex = key.indexOf(".Input."); + if(inputIndex > 0) return key.left(inputIndex); + int prepareIndex = key.indexOf(".prepareData"); + if(prepareIndex > 0) return key.left(prepareIndex); + int drawIndex = key.indexOf(".draw"); + if(drawIndex > 0) return key.left(drawIndex); + int leaseIndex = key.indexOf(".stateLeaseMiss"); + if(leaseIndex > 0) return key.left(leaseIndex); + int modeIndex = key.indexOf(".bufferAcquireMode"); + if(modeIndex > 0) return key.left(modeIndex); + return {}; + } + void rebuildViews() { + QSet objectItems; + QVector oldGroupItems = mGroupItems; + QVector oldTargetItems = mTargetItems; + QString oldSelectedTarget = mSelectedTarget; + auto renderAbleScope = mScopes.constFind("RenderAble"); + if(renderAbleScope != mScopes.cend()) { + for(auto metricIt = renderAbleScope.value().cbegin(); metricIt != renderAbleScope.value().cend(); ++metricIt) { + QString objectName = objectNameFromMetric(metricIt.key()); + if(!objectName.isEmpty()) objectItems.insert(objectName); + } + } + auto bufferScope = mScopes.constFind("Buffer"); + if(bufferScope != mScopes.cend()) { + for(auto metricIt = bufferScope.value().cbegin(); metricIt != bufferScope.value().cend(); ++metricIt) { + if(!metricIt.key().startsWith("Color.")) { + QString objectName = objectNameFromMetric(metricIt.key()); + if(!objectName.isEmpty()) objectItems.insert(objectName); + } + } + } + mGroupItems.clear(); + mGroupItems.append("Plot"); + QVector objectNames = objectItems.values().toVector(); + std::sort(objectNames.begin(), objectNames.end()); + for(const QString& item : objectNames) mGroupItems.append(item); + if(!mGroupItems.contains(mSelectedGroup)) mSelectedGroup = "Plot"; + if(mSelectedGroup == "Plot") { + mTargetItems.clear(); + if(mScopes.contains("Pipeline")) mTargetItems.append("Pipeline"); + if(mScopes.contains("General")) mTargetItems.append("General"); + if(hasColorBufferMetrics()) mTargetItems.append("Color"); + } else { + mTargetItems.clear(); + if(hasRenderAbleMetrics(mSelectedGroup)) mTargetItems.append("RenderAble"); + if(hasObjectBufferMetrics(mSelectedGroup, "State")) mTargetItems.append("State"); + if(hasObjectBufferMetrics(mSelectedGroup, "Input")) mTargetItems.append("Input"); + } + if(mTargetItems.isEmpty()) mTargetItems.append("None"); + if(mSelectedTarget.isEmpty() || !mTargetItems.contains(mSelectedTarget)) mSelectedTarget = mTargetItems.front(); + if(oldSelectedTarget != mSelectedTarget) { + mScrollLine = 0; + mContentWidth = 0; + mContentWidthDirty = true; + } + if(oldGroupItems != mGroupItems || oldTargetItems != mTargetItems) mSidebarWidthDirty = true; + } + bool hasColorBufferMetrics() const { + auto bufferScope = mScopes.constFind("Buffer"); + if(bufferScope == mScopes.cend()) return false; + for(auto metricIt = bufferScope.value().cbegin(); metricIt != bufferScope.value().cend(); ++metricIt) { + if(metricIt.key().startsWith("Color.")) return true; + } + return false; + } + bool hasRenderAbleMetrics(const QString& objectName) const { + auto renderAbleScope = mScopes.constFind("RenderAble"); + if(renderAbleScope == mScopes.cend()) return false; + for(auto metricIt = renderAbleScope.value().cbegin(); metricIt != renderAbleScope.value().cend(); ++metricIt) { + if(objectNameFromMetric(metricIt.key()) == objectName) return true; + } + return false; + } + bool hasObjectBufferMetrics(const QString& objectName, const QString& bufferName) const { + auto bufferScope = mScopes.constFind("Buffer"); + if(bufferScope == mScopes.cend()) return false; + QString prefix = objectName + "." + bufferName + "."; + for(auto metricIt = bufferScope.value().cbegin(); metricIt != bufferScope.value().cend(); ++metricIt) { + if(metricIt.key().startsWith(prefix)) return true; + } + return false; + } + bool metricVisible(const QString& scope, const QString& key) const { + if(mSelectedGroup == "Plot") { + if(mSelectedTarget == "Pipeline") return scope == "Pipeline"; + if(mSelectedTarget == "General") return scope == "General"; + return mSelectedTarget == "Color" && scope == "Buffer" && key.startsWith("Color."); + } + if(mSelectedTarget == "RenderAble") return scope == "RenderAble" && objectNameFromMetric(key) == mSelectedGroup; + return scope == "Buffer" && key.startsWith(mSelectedGroup + "." + mSelectedTarget + "."); + } + QString displayMetricName(const QString& key) const { + if(mSelectedGroup == "Plot" && mSelectedTarget == "Color") return key.mid(QString("Color.").size()); + if(mSelectedGroup != "Plot" && mSelectedTarget == "RenderAble") return key.mid(mSelectedGroup.size() + 1); + if(mSelectedGroup != "Plot") return key.mid((mSelectedGroup + "." + mSelectedTarget + ".").size()); + return key; + } + void rebuildLines() { + rebuildViews(); + QVector lines; + for(auto scopeIt = mScopes.begin(); scopeIt != mScopes.end(); ++scopeIt) { + for(auto metricIt = scopeIt.value().begin(); metricIt != scopeIt.value().end(); ++metricIt) { + if(metricVisible(scopeIt.key(), metricIt.key())) { + lines.append(metricIt.value().toString(displayMetricName(metricIt.key()))); + } + } + } + if(mAllInfo != lines) { + mAllInfo = lines; + mContentWidthDirty = true; } - mAllInfo = lines; } }; class Cacl { public: - std::chrono::system_clock::time_point mStart; + std::chrono::steady_clock::time_point mStart; + QString mScope; QString mName; PerformanceShower *mPerformanceShower{}; - Cacl(const QString& name, PerformanceShower* performanceShower) { + Cacl(const QString& name, PerformanceShower* performanceShower) : Cacl("General", name, performanceShower) { + } + Cacl(const QString& scope, const QString& name, PerformanceShower* performanceShower) { if(!performanceShower || !performanceShower->enabled()) return; - mStart = std::chrono::system_clock::now(); + mStart = std::chrono::steady_clock::now(); + mScope = scope; mName = name; mPerformanceShower = performanceShower; } ~Cacl() { if(!mPerformanceShower || !mPerformanceShower->enabled()) return; - double useTime = std::chrono::duration(std::chrono::system_clock::now() - mStart).count() * 1000; - mPerformanceShower->recordCost(mName, useTime); + double useTime = std::chrono::duration(std::chrono::steady_clock::now() - mStart).count() * 1000; + mPerformanceShower->recordDuration(mScope, mName, useTime); } }; } diff --git a/YSGraphic_Core/base/Plot.cpp b/YSGraphic_Core/base/Plot.cpp index dbbfb4e..f1d7df4 100644 --- a/YSGraphic_Core/base/Plot.cpp +++ b/YSGraphic_Core/base/Plot.cpp @@ -5,6 +5,63 @@ #include namespace YSG { + static std::uint64_t steadyNowNs() { + return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); + } + static double elapsedMs(std::uint64_t startNs, std::uint64_t endNs) { + return static_cast(endNs - startNs) / 1000000.0; + } + static QString jobStateText(RenderAble::JobState state) { + switch(state) { + case RenderAble::JobState::Idle: + return "Idle"; + case RenderAble::JobState::Queued: + return "Queued"; + case RenderAble::JobState::Running: + return "Running"; + } + return {}; + } + static QString renderAbleName(RenderAble* able) { + if(!able->mObjectName.isEmpty()) return able->mObjectName; + return QString::number(reinterpret_cast(able), 16); + } + static QString acquireModeText(Plot_Buffer_Acquire_Mode mode) { + return mode == Plot_Buffer_Acquire_Mode::Wait ? "Wait" : "Try"; + } + static void publishTripleBufferStats(PerformanceShower* shower, const QString& name, const Triple_Buffer_Stats& stats) { + if(!shower || !shower->enabled()) return; + shower->setGauge("Buffer", name + ".markSuccess", static_cast(stats.mark_success.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".markBusy", static_cast(stats.mark_busy.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".markStateChanged", static_cast(stats.mark_state_changed.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".swapSuccess", static_cast(stats.swap_success.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".swapBusy", static_cast(stats.swap_busy.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".swapStateChanged", static_cast(stats.swap_state_changed.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".swapNoNeed", static_cast(stats.swap_condition_failed.load(std::memory_order_relaxed))); + shower->setGauge("Buffer", name + ".wait", static_cast(stats.wait_count.load(std::memory_order_relaxed))); + } + static void publishRenderAbleBufferStats(PerformanceShower* shower, RenderAble* able) { + if(!shower || !shower->enabled()) return; + if(able == shower) return; + QString name = renderAbleName(able); + shower->setState("RenderAble", name + ".bufferAcquireMode", acquireModeText(able->bufferAcquireMode())); + publishTripleBufferStats(shower, name + ".State", able->dPtr->mStateBufferStats); + publishTripleBufferStats(shower, name + ".Input", able->dPtr->mInputBufferStats); + } + static void publishPipelineStats(PlotPrivate* data) { + PerformanceShower* shower = data->mShower; + if(!shower || !shower->enabled()) return; + RenderAble::RenderPipeline& pipeline = data->mPipeline; + shower->setState("Pipeline", "jobState", jobStateText(pipeline.jobState)); + shower->setState("Pipeline", "paintBufferAcquireMode", acquireModeText(data->mPaintBufferAcquireMode.load(std::memory_order_acquire))); + shower->setState("Pipeline", "renderAbleDefaultAcquireMode", acquireModeText(data->mRenderAbleBufferAcquireMode.load(std::memory_order_acquire))); + shower->setGauge("Pipeline", "activeRenderTasks", data->mActiveRenderTasks.load(std::memory_order_acquire)); + shower->setGauge("Pipeline", "renderEnabled", data->mRenderEnabled.load(std::memory_order_acquire) ? 1.0 : 0.0); + shower->setGauge("Pipeline", "paintRequestPending", pipeline.paintRequestPending.load(std::memory_order_acquire) ? 1.0 : 0.0); + shower->setGauge("Pipeline", "editStateVersion", static_cast(pipeline.editStateVersion.load(std::memory_order_acquire))); + shower->setGauge("Pipeline", "renderStateVersion", static_cast(pipeline.renderStateVersion)); + publishTripleBufferStats(shower, "Color", pipeline.colorBufferStats); + } static void notifyRenderTaskDone(PlotPrivate* data) { if(data->mActiveRenderTasks.fetch_sub(1, std::memory_order_acq_rel) == 1) data->mRenderTaskDone.notify_all(); } @@ -92,15 +149,36 @@ namespace YSG { void Plot::paintEvent(QPaintEvent* event) { Q_UNUSED(event) + Cacl paintData("Pipeline", "paintEvent", d->mShower); + std::uint64_t paintNs = steadyNowNs(); + std::uint64_t updateRequestNs = d->mLastUpdateRequestNs.exchange(0, std::memory_order_acq_rel); + if(updateRequestNs && d->mShower) d->mShower->recordDuration("Pipeline", "updateToPaint", elapsedMs(updateRequestNs, paintNs)); QPainter painter(this); - auto mode = d->mPaintBufferAcquireMode.load(std::memory_order_acquire) == Plot_Buffer_Acquire_Mode::Wait ? Triple_Buffer_Mode::Wait : Triple_Buffer_Mode::Try; - auto colorLease = d->mPipeline.consumeReadyColor(mode); + auto mode = to_triple_buffer_mode(d->mPaintBufferAcquireMode.load(std::memory_order_acquire)); + RenderAble::ColorBufferLease colorLease; + { + Cacl consumeData("Buffer", "Color.consumeReady", d->mShower); + colorLease = d->mPipeline.consumeReadyColor(mode); + } RenderAble::ColorBuffer* color = colorLease.buffer; if(color && !color->image.isNull()) painter.drawImage(QPoint(0, 0), color->image); else painter.fillRect(rect(), d->mBackground); if(colorLease) d->mPipeline.unmarkColor(colorLease); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + if(!able->mVisable || !able->dPtr->independentPixelCache()) continue; + auto pixelLease = able->dPtr->consumeReadyPixel(Triple_Buffer_Mode::Try); + if(pixelLease && !pixelLease.buffer->image.isNull()) painter.drawImage(QPoint(0, 0), pixelLease.buffer->image); + if(pixelLease) able->dPtr->unmarkPixel(pixelLease); + } + } painter.end(); - if(d->mShower) d->mShower->counterIncrease("plot paint"); + if(d->mShower) { + if(colorLease) d->mShower->incrementCounter("Buffer", "Color.frontLeaseSuccess"); + else d->mShower->incrementCounter("Buffer", "Color.frontLeaseMiss"); + d->mShower->incrementCounter("Pipeline", "paint"); + publishPipelineStats(d); + } } void Plot::render() { submitRender(); @@ -111,18 +189,29 @@ namespace YSG { void Plot::setPaintBufferAcquireMode(Plot_Buffer_Acquire_Mode mode) { d->mPaintBufferAcquireMode.store(mode, std::memory_order_release); } + Plot_Buffer_Acquire_Mode Plot::renderAbleBufferAcquireMode() const { + return d->mRenderAbleBufferAcquireMode.load(std::memory_order_acquire); + } + void Plot::setRenderAbleBufferAcquireMode(Plot_Buffer_Acquire_Mode mode) { + d->mRenderAbleBufferAcquireMode.store(mode, std::memory_order_release); + } void Plot::submitRender() { RenderScheduler& scheduler = Global::instance()->renderScheduler(); if(!scheduler.isSchedulerThread()) { PlotPrivate* privateData = d; + std::uint64_t postNs = steadyNowNs(); privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel); - scheduler.post([this, privateData]() { + scheduler.post([this, privateData, postNs]() { + if(privateData->mShower) privateData->mShower->recordDuration("Pipeline", "schedulerQueueWait", elapsedMs(postNs, steadyNowNs())); if(!privateData->mDestroying.load(std::memory_order_acquire)) submitRender(); notifyRenderTaskDone(privateData); }); return; } if(d->mDestroying.load(std::memory_order_acquire)) return; + std::uint64_t submitNs = steadyNowNs(); + std::uint64_t requestNs = d->mLastRequestNs.exchange(0, std::memory_order_acq_rel); + if(requestNs && d->mShower) d->mShower->recordDuration("Pipeline", "requestToSubmit", elapsedMs(requestNs, submitNs)); if(mFirstResize) return; if(!d->mRenderEnabled.load(std::memory_order_acquire)) return; QSize renderSize = d->mRenderSize; @@ -133,24 +222,32 @@ namespace YSG { pipeline.jobState = RenderAble::JobState::Queued; pipeline.renderStateVersion = pipeline.editStateVersion.load(std::memory_order_acquire); if(d->mShower) { - d->mShower->counterIncrease("渲染帧率"); + d->mShower->incrementCounter("Pipeline", "renderFrame"); d->mShower->setInfo("interval", QString("渲染:%1, global:%2").arg(qRound(1000.0 / (double)d->mRefreshTimesPreSecond)).arg(Global::instance()->mInterval)); + publishPipelineStats(d); } { - Cacl prepareData("准备数据耗时:%1 SRTT:%2 RTTVAR:%3", d->mShower); + Cacl prepareData("Pipeline", "prepareData", d->mShower); for(Layer* layer : mLayerList) { for(RenderAble *cur : layer->mRenderAbles) { - //qDebug() << "info: "<< layer->mLayerName << " " << cur->mObjectName; for(auto rely : cur->mBeRelyList) { + if(rely->dPtr->independentPixelCache()) continue; if(!rely->mPrepared){ - // qDebug() << cur->mObjectName << "rely: " << mObjectName; - rely->prepareData(); + { + Cacl renderAblePrepare("RenderAble", renderAbleName(rely) + ".prepareData", d->mShower); + rely->prepareData(); + } + publishRenderAbleBufferStats(d->mShower, rely); rely->mPrepared = true; } } + if(cur->dPtr->independentPixelCache()) continue; if(!cur->mPrepared){ - cur->prepareData(); - //qDebug() << cur->mObjectName << " prepareData"; + { + Cacl renderAblePrepare("RenderAble", renderAbleName(cur) + ".prepareData", d->mShower); + cur->prepareData(); + } + publishRenderAbleBufferStats(d->mShower, cur); cur->mPrepared = true; } } @@ -162,14 +259,21 @@ namespace YSG { able->mPrepared = false; } } - auto colorLease = pipeline.waitRenderColor(); + RenderAble::ColorBufferLease colorLease; + { + Cacl waitRenderColor("Buffer", "Color.waitRenderLease", d->mShower); + colorLease = pipeline.waitRenderColor(); + } RenderAble::ColorBuffer* color = colorLease.buffer; QColor background = d->mBackground; std::uint64_t version = pipeline.renderStateVersion; pipeline.jobState = RenderAble::JobState::Running; + if(d->mShower) publishPipelineStats(d); PlotPrivate* privateData = d; + std::uint64_t cpuPostNs = steadyNowNs(); privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel); - scheduler.postCpu([this, privateData, colorLease, color, renderSize, background, version]() { + scheduler.postCpu([this, privateData, colorLease, color, renderSize, background, version, cpuPostNs]() { + if(privateData->mShower) privateData->mShower->recordDuration("Pipeline", "cpuQueueWait", elapsedMs(cpuPostNs, steadyNowNs())); if(privateData->mDestroying.load(std::memory_order_acquire)) { privateData->mPipeline.unmarkColor(colorLease); notifyRenderTaskDone(privateData); @@ -190,13 +294,18 @@ namespace YSG { void Plot::finishRender() { if(d->mDestroying.load(std::memory_order_acquire)) return; RenderAble::RenderPipeline& pipeline = d->mPipeline; - QRegion updateRegion = pipeline.publishRenderColor(); + QRegion updateRegion; + { + Cacl publishData("Pipeline", "publishRenderColor", d->mShower); + updateRegion = pipeline.publishRenderColor(); + } pipeline.jobState = RenderAble::JobState::Idle; + if(d->mShower) publishPipelineStats(d); if(!updateRegion.isEmpty()) requestUpdate(updateRegion); if(pipeline.hasNewState()) submitRender(); } void Plot::renderColor(RenderAble::ColorBuffer* color, QSize size, QColor background, std::uint64_t version) { - Cacl renderData("整体渲染耗时:%1 SRTT:%2 RTTVAR:%3", d->mShower); + Cacl renderData("Pipeline", "renderColor", d->mShower); if(color->image.size() != size || color->image.format() != QImage::Format_ARGB32) { color->image = QImage(size, QImage::Format_ARGB32); } @@ -206,17 +315,21 @@ namespace YSG { QVector stateLeases; QPainter painter(&color->image); { - Cacl drawData("draw耗时:%1 SRTT:%2 RTTVAR:%3", d->mShower); + Cacl drawData("Pipeline", "draw", d->mShower); for(Layer* layer : mLayerList) { for(RenderAble* cur : layer->mRenderAbles) { - if(!cur->mVisable) continue; + if(!cur->mVisable || cur->dPtr->independentPixelCache()) continue; auto lease = cur->dPtr->markStateRole(State_Render); - if(!lease) continue; + if(!lease) { + if(d->mShower) d->mShower->incrementCounter("RenderAble", renderAbleName(cur) + ".stateLeaseMiss"); + continue; + } leasedAbles.append(cur); stateLeases.append(lease); } } for(RenderAble* able : leasedAbles) { + Cacl renderAbleDraw("RenderAble", renderAbleName(able) + ".draw", d->mShower); able->draw(&painter); } } @@ -227,6 +340,76 @@ namespace YSG { color->version.store(version, std::memory_order_release); color->dirtyRegion = QRegion(QRect(QPoint(0, 0), size)); } + void Plot::markIndependentRenderDirty(RenderAble* able) { + if(d->mDestroying.load(std::memory_order_acquire)) return; + PlotPrivate* privateData = d; + privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel); + Global::instance()->renderScheduler().post([this, privateData, able]() { + if(!privateData->mDestroying.load(std::memory_order_acquire)) submitIndependentRender(able); + notifyRenderTaskDone(privateData); + }); + } + void Plot::submitIndependentRender(RenderAble* able) { + RenderScheduler& scheduler = Global::instance()->renderScheduler(); + if(!scheduler.isSchedulerThread()) { + markIndependentRenderDirty(able); + return; + } + if(d->mDestroying.load(std::memory_order_acquire)) return; + if(!able || !able->mVisable || !able->dPtr->independentPixelCache()) return; + if(able->dPtr->mIndependentRenderQueued.exchange(true, std::memory_order_acq_rel)) return; + QSize renderSize = d->mRenderSize; + if(renderSize.width() <= 0 || renderSize.height() <= 0) { + able->dPtr->mIndependentRenderQueued.store(false, std::memory_order_release); + return; + } + if(!able->dPtr->hasNewPixelState()) { + able->dPtr->mIndependentRenderQueued.store(false, std::memory_order_release); + return; + } + able->prepareData(); + auto pixelLease = able->dPtr->waitRenderPixel(); + PixelBufferSlot* pixel = pixelLease.buffer; + std::uint64_t version = able->dPtr->mPixelEditVersion.load(std::memory_order_acquire); + PlotPrivate* privateData = d; + privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel); + scheduler.postCpu([this, privateData, able, pixelLease, pixel, renderSize, version]() { + if(privateData->mDestroying.load(std::memory_order_acquire)) { + able->dPtr->unmarkPixel(pixelLease); + notifyRenderTaskDone(privateData); + return; + } + renderIndependentColor(able, pixel, renderSize, version); + able->dPtr->unmarkPixel(pixelLease); + Global::instance()->renderScheduler().post([this, privateData, able]() { + if(!privateData->mDestroying.load(std::memory_order_acquire)) finishIndependentRender(able); + notifyRenderTaskDone(privateData); + }); + }); + } + void Plot::renderIndependentColor(RenderAble* able, PixelBufferSlot* pixel, QSize size, std::uint64_t version) { + if(pixel->image.size() != size || pixel->image.format() != QImage::Format_ARGB32) { + pixel->image = QImage(size, QImage::Format_ARGB32); + } + pixel->image.detach(); + pixel->image.fill(Qt::transparent); + QPainter painter(&pixel->image); + auto lease = able->dPtr->markStateRole(State_Render); + if(lease) { + able->draw(&painter); + able->dPtr->unmarkState(lease); + } + painter.end(); + pixel->version.store(version, std::memory_order_release); + pixel->dirtyRegion = QRegion(QRect(QPoint(0, 0), size)); + able->dPtr->mPixelRenderVersion = version; + } + void Plot::finishIndependentRender(RenderAble* able) { + QRegion updateRegion = able->dPtr->publishRenderPixel(); + able->dPtr->mIndependentRenderQueued.store(false, std::memory_order_release); + if(!updateRegion.isEmpty()) requestUpdate(updateRegion); + if(able->dPtr->hasNewPixelState()) submitIndependentRender(able); + } void Plot::scheduleRenderTimer() { if(d->mDestroying.load(std::memory_order_acquire) || !d->mRenderTimer || !d->mRenderEnabled.load(std::memory_order_acquire)) return; int interval = std::max(1, qRound(1000.0 / (double)d->mRefreshTimesPreSecond)); @@ -235,6 +418,7 @@ namespace YSG { privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel); d->mRenderTimer->async_wait([this, privateData](const asio::error_code& error) { if(!error && !d->mDestroying.load(std::memory_order_acquire)) { + if(d->mShower) d->mShower->incrementCounter("Pipeline", "timerTick"); submitRender(); scheduleRenderTimer(); } @@ -248,6 +432,7 @@ namespace YSG { } void Plot::requestRender() { if(d->mDestroying.load(std::memory_order_acquire)) return; + d->mLastRequestNs.store(steadyNowNs(), std::memory_order_release); PlotPrivate* privateData = d; privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel); Global::instance()->renderScheduler().post([this, privateData]() { @@ -256,6 +441,7 @@ namespace YSG { }); } void Plot::requestUpdate(const QRegion& region) { + d->mLastUpdateRequestNs.store(steadyNowNs(), std::memory_order_release); QTimer::singleShot(0, this, [this, region]() { if(d->mDestroying.load(std::memory_order_acquire)) return; if(region.isEmpty()) update(); diff --git a/YSGraphic_Core/base/Plot.h b/YSGraphic_Core/base/Plot.h index 972284e..850b03c 100644 --- a/YSGraphic_Core/base/Plot.h +++ b/YSGraphic_Core/base/Plot.h @@ -7,10 +7,6 @@ namespace YSG { class RenderScheduler; struct PlotPrivate; class PerformanceShower; - enum class Plot_Buffer_Acquire_Mode : std::uint8_t { - Try, - Wait - }; class LIB_DECL Plot : public QWidget { Q_OBJECT public: @@ -26,6 +22,8 @@ namespace YSG { void setBackgroundColor(const QColor& color) const; Plot_Buffer_Acquire_Mode paintBufferAcquireMode() const; void setPaintBufferAcquireMode(Plot_Buffer_Acquire_Mode mode); + Plot_Buffer_Acquire_Mode renderAbleBufferAcquireMode() const; + void setRenderAbleBufferAcquireMode(Plot_Buffer_Acquire_Mode mode); void markRenderStateDirty(); void requestRender(); struct Layer { @@ -64,6 +62,10 @@ namespace YSG { void finishRender(); void requestUpdate(const QRegion& region); void renderColor(RenderAble::ColorBuffer* color, QSize size, QColor background, std::uint64_t version); + void markIndependentRenderDirty(RenderAble* able); + void submitIndependentRender(RenderAble* able); + void renderIndependentColor(RenderAble* able, PixelBufferSlot* pixel, QSize size, std::uint64_t version); + void finishIndependentRender(RenderAble* able); friend class RenderScheduler; friend struct RenderAble; }; diff --git a/YSGraphic_Core/base/Plot_p.h b/YSGraphic_Core/base/Plot_p.h index 237a16c..4dbd113 100644 --- a/YSGraphic_Core/base/Plot_p.h +++ b/YSGraphic_Core/base/Plot_p.h @@ -20,9 +20,12 @@ namespace YSG { std::mutex mRenderTaskMutex; std::atomic_int mPendingRenderWidth{0}; std::atomic_int mPendingRenderHeight{0}; + std::atomic mLastRequestNs{0}; + std::atomic mLastUpdateRequestNs{0}; QSize mRenderSize; RenderAble::RenderPipeline mPipeline; std::atomic mPaintBufferAcquireMode{Plot_Buffer_Acquire_Mode::Try}; + std::atomic mRenderAbleBufferAcquireMode{Plot_Buffer_Acquire_Mode::Wait}; QColor mBackground = Qt::black; PerformanceShower *mShower{}; };