// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qwasmcompositor.h" #include "qwasmwindow.h" #include #include #include #include using namespace emscripten; bool QWasmCompositor::m_requestUpdateHoldEnabled = false; QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen) , m_animationFrameHandler(QWasmAnimationFrameHandler([this](double frametime){ Q_UNUSED(frametime); this->m_requestAnimationFrameId = -1; this->deliverUpdateRequests(); })) { QWindowSystemInterface::setSynchronousWindowSystemEvents(true); } QWasmCompositor::~QWasmCompositor() { if (m_requestAnimationFrameId != -1) m_animationFrameHandler.cancelAnimationFrame(m_requestAnimationFrameId); // TODO(mikolaj.boc): Investigate if m_isEnabled is needed at all. It seems like a frame should // not be generated after this instead. m_isEnabled = false; // prevent frame() from creating a new m_context } void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window) { auto allWindows = screen()->allWindows(); setEnabled(std::any_of(allWindows.begin(), allWindows.end(), [](QWasmWindow *element) { return !element->context2d().isUndefined(); })); if (changeType == QWasmWindowTreeNodeChangeType::NodeRemoval) m_requestUpdateWindows.remove(window); } void QWasmCompositor::setEnabled(bool enabled) { m_isEnabled = enabled; } // requestUpdate delivery is initially disabled at startup, while Qt completes // startup tasks such as font loading. This function enables requestUpdate delivery // again. bool QWasmCompositor::releaseRequestUpdateHold() { const bool wasEnabled = m_requestUpdateHoldEnabled; m_requestUpdateHoldEnabled = false; return wasEnabled; } void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, const QRect &updateRect, UpdateRequestDeliveryType updateType) { auto it = m_requestUpdateWindows.find(window); if (it == m_requestUpdateWindows.end()) { m_requestUpdateWindows.insert(window, std::make_tuple(updateRect, updateType)); } else { // Already registered, but upgrade ExposeEventDeliveryType to UpdateRequestDeliveryType. // if needed, to make sure QWindow::updateRequest's are matched. if (std::get<0>(it.value()) != updateRect) { QRegion region; region |= std::get<0>(it.value()); region |= updateRect; std::get<0>(it.value()) = region.boundingRect(); } if (std::get<1>(it.value()) == ExposeEventDelivery && updateType == UpdateRequestDelivery) std::get<1>(it.value()) = UpdateRequestDelivery; } requestUpdate(); } // Requests an update/new frame using RequestAnimationFrame void QWasmCompositor::requestUpdate() { if (m_requestAnimationFrameId != -1) return; if (m_requestUpdateHoldEnabled) return; m_requestAnimationFrameId = m_animationFrameHandler.requestAnimationFrame(); } 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(); // 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; for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) { auto *window = it.key(); const QRect updateRect = std::get<0>(it.value()); const UpdateRequestDeliveryType updateType = std::get<1>(it.value()); deliverUpdateRequest(window, updateRect, updateType); } m_inDeliverUpdateRequest = false; frame(requestUpdateWindows.keys()); } void QWasmCompositor::deliverUpdateRequest( QWasmWindow *window, const QRect &updateRect, UpdateRequestDeliveryType updateType) { QWindow *qwindow = window->window(); // Make sure the DPR value for the window is up to date on expose/repaint. // FIXME: listen to native DPR change events instead, if/when available. QWindowSystemInterface::handleWindowDevicePixelRatioChanged(qwindow); // Update by deliverUpdateRequest and expose event according to requested update // type. If the window has not yet been exposed then we must expose it first regardless // of update type. The deliverUpdateRequest must still be sent in this case in order // to maintain correct window update state. if (updateType == UpdateRequestDelivery) { if (qwindow->isExposed() == false) QWindowSystemInterface::handleExposeEvent(qwindow, updateRect); window->deliverUpdateRequest(); } else { QWindowSystemInterface::handleExposeEvent(qwindow, updateRect); } } void QWasmCompositor::handleBackingStoreFlush(QWindow *window, const QRect &updateRect) { // 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) requestUpdateWindow(static_cast(window->handle()), updateRect); } void QWasmCompositor::frame(const QList &windows) { if (!m_isEnabled || !screen()) return; for (QWasmWindow *window : windows) window->paint(); } QWasmScreen *QWasmCompositor::screen() { return static_cast(parent()); } QWasmAnimationFrameHandler::QWasmAnimationFrameHandler(std::function handler) { auto argCastWrapper = [handler](val arg){ handler(arg.as()); }; m_handlerIndex = QWasmSuspendResumeControl::get()->registerEventHandler(argCastWrapper); } QWasmAnimationFrameHandler::~QWasmAnimationFrameHandler() { QWasmSuspendResumeControl::get()->removeEventHandler(m_handlerIndex); } int64_t QWasmAnimationFrameHandler::requestAnimationFrame() { using ReturnType = double; // FIXME emscripten::val::call() does not support int64_t val handler = QWasmSuspendResumeControl::get()->jsEventHandlerAt(m_handlerIndex); return int64_t(val::global("window").call("requestAnimationFrame", handler)); } void QWasmAnimationFrameHandler::cancelAnimationFrame(int64_t id) { val::global("window").call("cancelAnimationFrame", double(id)); }