Files
Renderive/YSGraphic_Core/base/Plot.cpp
T
2026-07-23 15:15:26 +08:00

583 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Plot_p.h"
#include "PerformanceShower_p.h"
#include "Global.h"
#include <algorithm>
#include <chrono>
namespace YSG {
static std::uint64_t steadyNowNs() {
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
}
static double elapsedMs(std::uint64_t startNs, std::uint64_t endNs) {
return static_cast<double>(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<std::uintptr_t>(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<double>(stats.mark_success.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".markBusy", static_cast<double>(stats.mark_busy.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".markStateChanged", static_cast<double>(stats.mark_state_changed.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".swapSuccess", static_cast<double>(stats.swap_success.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".swapBusy", static_cast<double>(stats.swap_busy.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".swapStateChanged", static_cast<double>(stats.swap_state_changed.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".swapNoNeed", static_cast<double>(stats.swap_condition_failed.load(std::memory_order_relaxed)));
shower->setGauge("Buffer", name + ".wait", static_cast<double>(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<double>(pipeline.editStateVersion.load(std::memory_order_acquire)));
shower->setGauge("Pipeline", "renderStateVersion", static_cast<double>(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();
}
void startRenderScheduler() {
Global::instance()->startRenderScheduler();
}
Plot::Plot() {
d = new PlotPrivate(this);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setMouseTracking(true);
setFocusPolicy(Qt::NoFocus);
Global::instance()->renderScheduler().registerPlot(this);
}
bool Plot::usePerformanceShower() {
return d->mShower && d->mShower->enabled();
}
void Plot::set_usePerformanceShower(bool use) {
if(!d->mShower) {
d->mShower = new PerformanceShower;
d->mShower->init(this, "legend");
}
d->mShower->setEnabled(use);
}
Plot::~Plot() {
d->mDestroying.store(true, std::memory_order_release);
d->mRenderEnabled.store(false, std::memory_order_release);
Global::instance()->renderScheduler().removePlot(this);
{
std::unique_lock<std::mutex> lock(d->mRenderTaskMutex);
d->mRenderTaskDone.wait(lock, [this]() {
return d->mActiveRenderTasks.load(std::memory_order_acquire) == 0;
});
}
delete d->mShower;
d->mShower = nullptr;
delete d;
}
bool Plot::isRendering() {
return d->mRenderEnabled.load(std::memory_order_acquire);
}
void Plot::startRender(int refreshTimesPreSecond) {
d->mRefreshTimesPreSecond = refreshTimesPreSecond;
startRender();
}
void Plot::startRender() const {
if(d->mDestroying.load(std::memory_order_acquire)) return;
d->mRenderEnabled.store(true, std::memory_order_release);
Plot* self = const_cast<Plot*>(this);
PlotPrivate* privateData = d;
privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel);
Global::instance()->renderScheduler().post([self, privateData]() {
PlotPrivate* d = self->d;
if(!d->mDestroying.load(std::memory_order_acquire)) {
if(d->mRenderTimer) {
d->mRenderTimer->cancel();
self->scheduleRenderTimer();
}
self->submitRender();
}
notifyRenderTaskDone(privateData);
});
}
void Plot::pauseRender() const {
d->mRenderEnabled.store(false, std::memory_order_release);
if(d->mDestroying.load(std::memory_order_acquire)) return;
Plot* self = const_cast<Plot*>(this);
PlotPrivate* privateData = d;
privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel);
Global::instance()->renderScheduler().post([self, privateData]() {
PlotPrivate* d = self->d;
if(!d->mDestroying.load(std::memory_order_acquire)) {
if(d->mRenderTimer) {
d->mRenderTimer->cancel();
}
}
notifyRenderTaskDone(privateData);
});
}
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 = 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) {
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();
}
Plot_Buffer_Acquire_Mode Plot::paintBufferAcquireMode() const {
return d->mPaintBufferAcquireMode.load(std::memory_order_acquire);
}
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, 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;
if(renderSize.width() <= 0 || renderSize.height() <= 0) return;
RenderAble::RenderPipeline& pipeline = d->mPipeline;
if(pipeline.jobState != RenderAble::JobState::Idle) return;
if(!pipeline.hasNewState()) return;
pipeline.jobState = RenderAble::JobState::Queued;
pipeline.renderStateVersion = pipeline.editStateVersion.load(std::memory_order_acquire);
if(d->mShower) {
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("Pipeline", "prepareData", d->mShower);
for(Layer* layer : mLayerList) {
for(RenderAble *cur : layer->mRenderAbles) {
for(auto rely : cur->mBeRelyList) {
if(rely->dPtr->independentPixelCache()) continue;
if(!rely->mPrepared){
{
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){
{
Cacl renderAblePrepare("RenderAble", renderAbleName(cur) + ".prepareData", d->mShower);
cur->prepareData();
}
publishRenderAbleBufferStats(d->mShower, cur);
cur->mPrepared = true;
}
}
}
if(mFirstPrepareData) mFirstPrepareData = false;
}
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->mPrepared = false;
}
}
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, 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);
return;
}
renderColor(color, renderSize, background, version);
privateData->mPipeline.unmarkColor(colorLease);
if(privateData->mDestroying.load(std::memory_order_acquire)) {
notifyRenderTaskDone(privateData);
return;
}
Global::instance()->renderScheduler().post([this, privateData]() {
if(!privateData->mDestroying.load(std::memory_order_acquire)) finishRender();
notifyRenderTaskDone(privateData);
});
});
}
void Plot::finishRender() {
if(d->mDestroying.load(std::memory_order_acquire)) return;
RenderAble::RenderPipeline& pipeline = d->mPipeline;
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("Pipeline", "renderColor", d->mShower);
if(color->image.size() != size || color->image.format() != QImage::Format_ARGB32) {
color->image = QImage(size, QImage::Format_ARGB32);
}
color->image.detach();
color->image.fill(background);
QVector<RenderAble*> leasedAbles;
QVector<Triple_Buffer_Lease> stateLeases;
QPainter painter(&color->image);
{
Cacl drawData("Pipeline", "draw", d->mShower);
for(Layer* layer : mLayerList) {
for(RenderAble* cur : layer->mRenderAbles) {
if(!cur->mVisable || cur->dPtr->independentPixelCache()) continue;
auto lease = cur->dPtr->markStateRole(State_Render);
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);
}
}
for(int i = 0; i < leasedAbles.size(); ++i) {
leasedAbles[i]->dPtr->unmarkState(stateLeases[i]);
}
painter.end();
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));
d->mRenderTimer->expires_after(std::chrono::milliseconds(interval));
PlotPrivate* privateData = d;
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();
}
notifyRenderTaskDone(privateData);
});
}
void Plot::markRenderStateDirty() {
if(d->mDestroying.load(std::memory_order_acquire)) return;
d->mPipeline.markEditState();
requestRender();
}
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]() {
if(!d->mDestroying.load(std::memory_order_acquire)) submitRender();
notifyRenderTaskDone(privateData);
});
}
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();
else update(region);
});
}
QColor Plot::backgroundColor() {
return d->mBackground;
}
void Plot::setBackgroundColor(const QColor& color) const {
d->mBackground = color;
}
bool Plot::event(QEvent* event) {
bool ret = QWidget::event(event);
switch (event->type()){
case QEvent::Resize: {
auto e = reinterpret_cast<QResizeEvent*>(event);
QSize renderSize = e->size();
d->mPendingRenderWidth.store(renderSize.width(), std::memory_order_release);
d->mPendingRenderHeight.store(renderSize.height(), std::memory_order_release);
PlotPrivate* privateData = d;
privateData->mActiveRenderTasks.fetch_add(1, std::memory_order_acq_rel);
Global::instance()->renderScheduler().post([privateData, renderSize]() {
if(!privateData->mDestroying.load(std::memory_order_acquire)) privateData->mRenderSize = renderSize;
notifyRenderTaskDone(privateData);
});
if(mFirstResize) {
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->firstResizeEvent(e);
}
}
}
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->resizeEvent(e);
}
}
if(mFirstResize) mFirstResize = false;
markRenderStateDirty();
break;
}
case QEvent::Show: {
startRender();
break;
}
case QEvent::Hide: {
pauseRender();
break;
}
case QEvent::Enter: {
break;
}
case QEvent::Leave: {
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->setHoverState(QPoint(), false);
}
}
break;
}
case QEvent::Wheel: {
auto e = reinterpret_cast<QWheelEvent*>(event);
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->wheelEvent(e);
}
}
break;
}
case QEvent::MouseMove: {
auto e = reinterpret_cast<QMouseEvent*>(event);
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
bool selected = able->selectTest(e->pos());
able->setHoverState(e->pos(), selected);
if(selected) {
able->mouseMoveEvent(e);
}
}
}
break;
}
case QEvent::MouseButtonPress: {
auto e = reinterpret_cast<QMouseEvent*>(event);
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
if(able->selectTest(e->pos())) {
able->mousePressEvent(e);
}
}
}
break;
}
case QEvent::MouseButtonRelease: {
auto e = reinterpret_cast<QMouseEvent*>(event);
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
if(able->selectTest(e->pos())) {
able->mouseReleaseEvent(e);
}
}
}
break;
}
case QEvent::KeyPress: {
auto e = reinterpret_cast<QKeyEvent*>(event);
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->keyPressEvent(e);
}
}
break;
}
case QEvent::KeyRelease: {
auto e = reinterpret_cast<QKeyEvent*>(event);
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->keyReleaseEvent(e);
}
}
break;
}
default: break;
}
for(Layer* layer : mLayerList) {
for(RenderAble* able : layer->mRenderAbles) {
able->plotEvent(event);
}
}
return ret;
}
}