Improve the framerate of Editor widgets that update in the TickBus (#7408)

Previously, with no level loaded in the main editor, and no assets loaded
in the EMotionFX editor, the AnimGraph viewport would only update at 15fps,
even on a 64 core machine.

Digging into this with Pix, I found that EMotionFX's UI would only get
updated for every other call to the ComponentApplication tick.

The Editor's QApplication instance controls how the
`ComponentApplication::OnTick` method is called. This is done in the
`maybeProcessIdle()` method. Generally, I found callstacks like this:

```
EditorQtApplication::maybeProcessIdle()
  CCryEditApp::OnIdle()
    CCryEditApp::IdleProcessing()
      CGameEngine::Update()                  # ~2.2ms
        PhysX::Update()                      # ~1.8ms
        EMotionFX::Update()                  # ~0.2ms
          calls QWidget::update() on all the widgets that change per
          frame, only puts update events on the event queue, doesn't
          paint anything
      AZ::ComponentApplication::TickSystem() # ~25ms
        renders the frame with Atom          # ~24ms
```

The `maybeProcessIdle()` method is invoked by a QTimer, with a timeout
that changes depending on the application state. If the Editor is in
game mode, it used a timeout of 0, which essentially forced the game to
run at as high an fps as possible. If the Editor application has focus,
it used a timeout of 1ms, asking for the idle processing to happen at
1000 fps.  Otherwise, it used a timeout of 10ms, asking for idle
processing at 100 fps.

Those fps targets are not realistic. What happened in this case is that
while the PhysX system was being updated, while the previous
`maybeProcessIdle()` call was still processing, the idle processing
timer would timeout again, and place a timer event on Qt's event queue,
before anything else had a chance to do anything. Only afterward would
EMotionFX's MainWindow::OnTick would be invoked, placing paint events on
Qt's event queue.

The fix for this is to use a single shot timer at the end of each
`maybeProcessIdle()` call. This ensures that any events that are
enqueued during the `maybeProcessIdle()` call are processed prior to the
next call to `maybeProcessIdle()`.

Signed-off-by: Chris Burel <burelc@amazon.com>
monroegm-disable-blank-issue-2
Chris Burel 4 years ago committed by GitHub
parent 0b317ee0f5
commit d2cdb511d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -32,15 +32,6 @@
#include "Settings.h"
#include "CryEdit.h"
enum
{
// in milliseconds
GameModeIdleFrequency = 0,
EditorModeIdleFrequency = 1,
InactiveModeFrequency = 10,
UninitializedFrequency = 9999,
};
Q_LOGGING_CATEGORY(InputDebugging, "o3de.editor.input")
// internal, private namespace:
@ -234,18 +225,12 @@ namespace Editor
EditorQtApplication::EditorQtApplication(int& argc, char** argv)
: AzQtApplication(argc, argv)
, m_stylesheet(new AzQtComponents::O3DEStylesheet(this))
, m_idleTimer(new QTimer(this))
{
m_idleTimer->setInterval(UninitializedFrequency);
setWindowIcon(QIcon(":/Application/res/o3de_editor.ico"));
// set the default key store for our preferences:
setApplicationName("O3DE Editor");
connect(m_idleTimer, &QTimer::timeout, this, &EditorQtApplication::maybeProcessIdle);
connect(this, &QGuiApplication::applicationStateChanged, this, [this] { ResetIdleTimerInterval(PollState); });
installEventFilter(this);
// Disable our debugging input helpers by default
@ -324,6 +309,10 @@ namespace Editor
winapp->OnIdle(0);
}
}
if (m_applicationActive)
{
QTimer::singleShot(1, this, &EditorQtApplication::maybeProcessIdle);
}
}
void EditorQtApplication::InstallQtLogHandler()
@ -376,14 +365,6 @@ namespace Editor
case eNotify_OnQuit:
GetIEditor()->UnregisterNotifyListener(this);
break;
case eNotify_OnBeginGameMode:
// GetIEditor()->IsInGameMode() Isn't reliable when called from within the notification handler
ResetIdleTimerInterval(GameMode);
break;
case eNotify_OnEndGameMode:
ResetIdleTimerInterval(EditorMode);
break;
}
}
@ -456,55 +437,16 @@ namespace Editor
void EditorQtApplication::EnableOnIdle(bool enable)
{
m_applicationActive = enable;
if (enable)
{
if (m_idleTimer->interval() == UninitializedFrequency)
{
ResetIdleTimerInterval();
}
m_idleTimer->start();
}
else
{
m_idleTimer->stop();
QTimer::singleShot(0, this, &EditorQtApplication::maybeProcessIdle);
}
}
bool EditorQtApplication::OnIdleEnabled() const
{
if (m_idleTimer->interval() == UninitializedFrequency)
{
return false;
}
return m_idleTimer->isActive();
}
void EditorQtApplication::ResetIdleTimerInterval(TimerResetFlag flag)
{
bool isInGameMode = flag == GameMode;
if (flag == PollState)
{
isInGameMode = GetIEditor() ? GetIEditor()->IsInGameMode() : false;
}
// Game mode takes precedence over anything else
if (isInGameMode)
{
m_idleTimer->setInterval(GameModeIdleFrequency);
}
else
{
if (applicationState() & Qt::ApplicationActive)
{
m_idleTimer->setInterval(EditorModeIdleFrequency);
}
else
{
m_idleTimer->setInterval(InactiveModeFrequency);
}
}
return m_applicationActive;
}
bool EditorQtApplication::eventFilter(QObject* object, QEvent* event)

@ -102,13 +102,6 @@ namespace Editor
bool m_isMovingOrResizing = false;
private:
enum TimerResetFlag
{
PollState,
GameMode,
EditorMode
};
void ResetIdleTimerInterval(TimerResetFlag = PollState);
static QColor InterpolateColors(QColor a, QColor b, float factor);
void RefreshStyleSheet();
void InstallFilters();
@ -125,7 +118,6 @@ namespace Editor
QTranslator* m_editorTranslator = nullptr;
QTranslator* m_assetBrowserTranslator = nullptr;
QTimer* const m_idleTimer = nullptr;
AZ::UserSettingsProvider m_localUserSettings;
@ -133,5 +125,6 @@ namespace Editor
QSet<int> m_pressedKeys;
bool m_activatedLocalUserSettings = false;
bool m_applicationActive = false;
};
} // namespace editor

Loading…
Cancel
Save