wasm: always use requestAnimationFrame for updates

The compositor was posting update events and flushing/redrawing
using a zero-timer. Change this to use the request_animation_frame
API from Emscripten, which makes sure we flush window
content at the next native paint event.

This has the additional benefit that hidden canvases
(e.g on hidden tabs) won’t get frame events, and then
stop painting.

We support both well-behaved QWindows, where the window
calls requestUpate() and then paints/flushes on the
following deliverUpdateRequest(), and also less well
behaved windows which paints at any point during event
processing.

Pick-to: 6.3
Change-Id: I747d6f7ace86ceddaa18ab86b6a0ee833f98991b
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
Morten Johan Sørvig 2019-05-08 09:23:52 +02:00
parent e3e2674100
commit eb62b6ac02
5 changed files with 116 additions and 65 deletions

View File

@ -90,7 +90,7 @@ void QWasmBackingStore::flush(QWindow *window, const QRegion &region, const QPoi
Q_UNUSED(offset);
m_dirty |= region;
m_compositor->requestRedraw();
m_compositor->handleBackingStoreFlush();
}
void QWasmBackingStore::updateTexture()

View File

@ -28,6 +28,7 @@
****************************************************************************/
#include "qwasmcompositor.h"
#include "qwasmeventdispatcher.h"
#include "qwasmwindow.h"
#include "qwasmstylepixmaps_p.h"
@ -69,6 +70,8 @@ QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
QWasmCompositor::~QWasmCompositor()
{
if (m_requestAnimationFrameId != -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
destroy();
}
@ -148,7 +151,7 @@ void QWasmCompositor::setVisible(QWasmWindow *window, bool visible)
else
m_globalDamage = compositedWindow.window->geometry(); // repaint previously covered area.
requestRedraw();
requestUpdateWindow(window, QWasmCompositor::ExposeEventDelivery);
}
void QWasmCompositor::raise(QWasmWindow *window)
@ -181,16 +184,7 @@ void QWasmCompositor::setParent(QWasmWindow *window, QWasmWindow *parent)
{
m_compositedWindows[window].parentWindow = parent;
requestRedraw();
}
void QWasmCompositor::flush(QWasmWindow *window, const QRegion &region)
{
QWasmCompositedWindow &compositedWindow = m_compositedWindows[window];
compositedWindow.flushPending = true;
compositedWindow.damage = region;
requestRedraw();
requestUpdate();
}
int QWasmCompositor::windowCount() const
@ -198,28 +192,6 @@ int QWasmCompositor::windowCount() const
return m_windowStack.count();
}
void QWasmCompositor::redrawWindowContent()
{
// Redraw window content by sending expose events. This redraw
// will cause a backing store flush, which will call requestRedraw()
// to composit.
for (QWasmWindow *platformWindow : m_windowStack) {
QWindow *window = platformWindow->window();
QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(
window, QRect(QPoint(0, 0), window->geometry().size()));
}
}
void QWasmCompositor::requestRedraw()
{
if (m_needComposit)
return;
m_needComposit = true;
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
QWindow *QWasmCompositor::windowAt(QPoint globalPoint, int padding) const
{
int index = m_windowStack.count() - 1;
@ -245,17 +217,6 @@ QWindow *QWasmCompositor::keyWindow() const
return m_windowStack.at(m_windowStack.count() - 1)->window();
}
bool QWasmCompositor::event(QEvent *ev)
{
if (ev->type() == QEvent::UpdateRequest) {
if (m_isEnabled)
frame();
return true;
}
return QObject::event(ev);
}
void QWasmCompositor::blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry)
{
QMatrix4x4 m;
@ -366,6 +327,97 @@ QRect QWasmCompositor::titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::Su
return rect;
}
void QWasmCompositor::requestUpdateAllWindows()
{
m_requestUpdateAllWindows = true;
requestUpdate();
}
void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType)
{
auto it = m_requestUpdateWindows.find(window);
if (it == m_requestUpdateWindows.end()) {
m_requestUpdateWindows.insert(window, updateType);
} else {
// Already registered, but upgrade ExposeEventDeliveryType to UpdateRequestDeliveryType.
// if needed, to make sure QWindow::updateRequest's are matched.
if (it.value() == ExposeEventDelivery && updateType == UpdateRequestDelivery)
it.value() = UpdateRequestDelivery;
}
requestUpdate();
}
// Requests an upate/new frame using RequestAnimationFrame
void QWasmCompositor::requestUpdate()
{
if (m_requestAnimationFrameId != -1)
return;
static auto frame = [](double frameTime, void *context) -> int {
Q_UNUSED(frameTime);
QWasmCompositor *compositor = reinterpret_cast<QWasmCompositor *>(context);
compositor->m_requestAnimationFrameId = -1;
compositor->deliverUpdateRequests();
return 0;
};
m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this);
}
void QWasmCompositor::deliverUpdateRequests()
{
// We may get new update requests during the window content update below:
// prepare for recording the new update set by setting aside the current
// update set.
auto requestUpdateWindows = m_requestUpdateWindows;
m_requestUpdateWindows.clear();
bool requestUpdateAllWindows = m_requestUpdateAllWindows;
m_requestUpdateAllWindows = false;
// Update window content, either all windows or a spesific set of windows. Use the correct update
// type: QWindow subclasses expect that requested and delivered updateRequests matches exactly.
m_inDeliverUpdateRequest = true;
if (requestUpdateAllWindows) {
for (QWasmWindow *window : m_windowStack) {
auto it = requestUpdateWindows.find(window);
UpdateRequestDeliveryType updateType =
(it == m_requestUpdateWindows.end() ? ExposeEventDelivery : it.value());
deliverUpdateRequest(window, updateType);
}
} else {
for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
auto *window = it.key();
UpdateRequestDeliveryType updateType = it.value();
deliverUpdateRequest(window, updateType);
}
}
m_inDeliverUpdateRequest = false;
// Compose window content
frame();
}
void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType)
{
// update by deliverUpdateRequest and expose event accordingly.
if (updateType == UpdateRequestDelivery) {
window->QPlatformWindow::deliverUpdateRequest();
} else {
QWindow *qwindow = window->window();
QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(
qwindow, QRect(QPoint(0, 0), qwindow->geometry().size()));
}
}
void QWasmCompositor::handleBackingStoreFlush()
{
// Request update to flush the updated backing store content,
// unless we are currently processing an update, in which case
// the new content will flushed as a part of that update.
if (!m_inDeliverUpdateRequest)
requestUpdate();
}
int dpiScaled(qreal value)
{
return value * (qreal(qt_defaultDpiX()) / 96.0);
@ -678,11 +730,6 @@ void QWasmCompositor::drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *sc
void QWasmCompositor::frame()
{
if (!m_needComposit)
return;
m_needComposit = false;
if (!m_isEnabled || m_windowStack.empty() || !screen())
return;
@ -748,8 +795,7 @@ void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window)
return;
}
requestRedraw();
requestUpdate();
}
QWasmScreen *QWasmCompositor::screen()

View File

@ -110,25 +110,24 @@ public:
void lower(QWasmWindow *window);
void setParent(QWasmWindow *window, QWasmWindow *parent);
void flush(QWasmWindow *surface, const QRegion &region);
int windowCount() const;
void redrawWindowContent();
void requestRedraw();
QWindow *windowAt(QPoint globalPoint, int padding = 0) const;
QWindow *keyWindow() const;
bool event(QEvent *event);
static QWasmTitleBarOptions makeTitleBarOptions(const QWasmWindow *window);
static QRect titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol);
QWasmScreen *screen();
QOpenGLContext *context();
private slots:
enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery };
void requestUpdateAllWindows();
void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
void requestUpdate();
void deliverUpdateRequests();
void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
void handleBackingStoreFlush();
void frame();
private:
@ -152,6 +151,10 @@ private:
bool m_isEnabled;
QSize m_targetSize;
qreal m_targetDevicePixelRatio;
QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows;
bool m_requestUpdateAllWindows = false;
int m_requestAnimationFrameId = -1;
bool m_inDeliverUpdateRequest = false;
static QPalette makeWindowPalette();

View File

@ -216,7 +216,7 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize()
QPoint position(rect["left"].as<int>() - offset.x(), rect["top"].as<int>() - offset.y());
setGeometry(QRect(position, cssSize.toSize()));
m_compositor->redrawWindowContent();
m_compositor->requestUpdateAllWindows();
}
void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscripten::val)

View File

@ -111,8 +111,6 @@ void QWasmWindow::setGeometry(const QRect &rect)
}
QWindowSystemInterface::handleGeometryChange(window(), r);
QPlatformWindow::setGeometry(r);
QWindowSystemInterface::flushWindowSystemEvents();
invalidate();
}
@ -128,7 +126,6 @@ void QWasmWindow::setVisible(bool visible)
else if (m_windowState & Qt::WindowMaximized)
newGeom = platformScreen()->availableGeometry();
}
QPlatformWindow::setVisible(visible);
m_compositor->setVisible(this, visible);
@ -366,7 +363,7 @@ QRegion QWasmWindow::titleControlRegion() const
void QWasmWindow::invalidate()
{
m_compositor->requestRedraw();
m_compositor->requestUpdateWindow(this);
}
QWasmCompositor::SubControls QWasmWindow::activeSubControl() const
@ -397,6 +394,11 @@ qreal QWasmWindow::devicePixelRatio() const
void QWasmWindow::requestUpdate()
{
if (m_compositor) {
m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
return;
}
static auto frame = [](double time, void *context) -> int {
Q_UNUSED(time);
QWasmWindow *window = static_cast<QWasmWindow *>(context);