overte-HifiExperiments/libraries/render-utils/src/OffscreenUi.cpp
2015-04-22 19:18:09 -07:00

395 lines
12 KiB
C++

//
// OffscreenUi.cpp
// interface/src/render-utils
//
// Created by Bradley Austin Davis on 2015-04-04
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OffscreenUi.h"
#include <QOpenGLFramebufferObject>
#include <QOpenGLDebugLogger>
#include <QGLWidget>
#include <QtQml>
class OffscreenUiRoot : public QQuickItem {
Q_OBJECT
public:
OffscreenUiRoot(QQuickItem* parent = 0);
Q_INVOKABLE void information(const QString& title, const QString& text);
Q_INVOKABLE void loadChild(const QUrl& url) {
DependencyManager::get<OffscreenUi>()->load(url);
}
};
OffscreenUiRoot::OffscreenUiRoot(QQuickItem* parent) : QQuickItem(parent) {
}
void OffscreenUiRoot::information(const QString& title, const QString& text) {
OffscreenUi::information(title, text);
}
OffscreenUi::OffscreenUi() {
::qmlRegisterType<OffscreenUiRoot>("Hifi", 1, 0, "Root");
}
OffscreenUi::~OffscreenUi() {
// Make sure the context is current while doing cleanup. Note that we use the
// offscreen surface here because passing 'this' at this point is not safe: the
// underlying platform window may already be destroyed. To avoid all the trouble, use
// another surface that is valid for sure.
makeCurrent();
// Delete the render control first since it will free the scenegraph resources.
// Destroy the QQuickWindow only afterwards.
delete _renderControl;
delete _qmlComponent;
delete _quickWindow;
delete _qmlEngine;
doneCurrent();
}
void OffscreenUi::create(QOpenGLContext* shareContext) {
OffscreenGlCanvas::create(shareContext);
makeCurrent();
// Create a QQuickWindow that is associated with out render control. Note that this
// window never gets created or shown, meaning that it will never get an underlying
// native (platform) window.
QQuickWindow::setDefaultAlphaBuffer(true);
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
// Create a QML engine.
_qmlEngine = new QQmlEngine;
if (!_qmlEngine->incubationController()) {
_qmlEngine->setIncubationController(_quickWindow->incubationController());
}
// When Quick says there is a need to render, we will not render immediately. Instead,
// a timer with a small interval is used to get better performance.
_updateTimer.setSingleShot(true);
_updateTimer.setInterval(5);
connect(&_updateTimer, &QTimer::timeout, this, &OffscreenUi::updateQuick);
// Now hook up the signals. For simplicy we don't differentiate between
// renderRequested (only render is needed, no sync) and sceneChanged (polish and sync
// is needed too).
connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenUi::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate);
_quickWindow->focusObject();
_qmlComponent = new QQmlComponent(_qmlEngine);
// Initialize the render control and our OpenGL resources.
makeCurrent();
_renderControl->initialize(&_context);
}
void OffscreenUi::addImportPath(const QString& path) {
_qmlEngine->addImportPath(path);
}
void OffscreenUi::resize(const QSize& newSize) {
makeCurrent();
// Clear out any fbos with the old size
qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0;
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
_fboCache.setSize(newSize * pixelRatio);
// Update our members
if (_rootItem) {
_rootItem->setSize(newSize);
}
if (_quickWindow) {
_quickWindow->setGeometry(QRect(QPoint(), newSize));
}
doneCurrent();
}
QQmlContext* OffscreenUi::qmlContext() {
if (nullptr == _rootItem) {
return _qmlComponent->creationContext();
}
return QQmlEngine::contextForObject(_rootItem);
}
void OffscreenUi::setBaseUrl(const QUrl& baseUrl) {
_qmlEngine->setBaseUrl(baseUrl);
}
void OffscreenUi::load(const QUrl& qmlSource, std::function<void(QQmlContext*)> f) {
qDebug() << "Loading QML from URL " << qmlSource;
_qmlComponent->loadUrl(qmlSource);
if (_qmlComponent->isLoading()) {
connect(_qmlComponent, &QQmlComponent::statusChanged, this, []{});
} else {
finishQmlLoad();
}
}
void OffscreenUi::requestUpdate() {
_polish = true;
if (!_updateTimer.isActive()) {
_updateTimer.start();
}
}
void OffscreenUi::requestRender() {
if (!_updateTimer.isActive()) {
_updateTimer.start();
}
}
void OffscreenUi::finishQmlLoad() {
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad);
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList) {
qWarning() << error.url() << error.line() << error;
}
return;
}
QObject* newObject = _qmlComponent->create();
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList)
qWarning() << error.url() << error.line() << error;
if (!_rootItem) {
qFatal("Unable to finish loading QML root");
}
return;
}
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
if (!newItem) {
qWarning("run: Not a QQuickItem");
delete newObject;
if (!_rootItem) {
qFatal("Unable to find root QQuickItem");
}
return;
}
// Make sure we make items focusable (critical for
// supporting keyboard shortcuts)
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
if (!_rootItem) {
// The root item is ready. Associate it with the window.
_rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
} else {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
newItem->setParent(_rootItem);
newItem->setParentItem(_rootItem);
newItem->setEnabled(true);
}
}
void OffscreenUi::updateQuick() {
if (_paused) {
return;
}
if (!makeCurrent()) {
return;
}
// Polish, synchronize and render the next frame (into our fbo). In this example
// everything happens on the same thread and therefore all three steps are performed
// in succession from here. In a threaded setup the render() call would happen on a
// separate thread.
if (_polish) {
_renderControl->polishItems();
_renderControl->sync();
_polish = false;
}
QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo();
_quickWindow->setRenderTarget(fbo);
fbo->bind();
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_renderControl->render();
Q_ASSERT(!glGetError());
_quickWindow->resetOpenGLState();
QOpenGLFramebufferObject::bindDefault();
// Force completion of all the operations before we emit the texture as being ready for use
glFinish();
emit textureUpdated(fbo->texture());
}
QPointF OffscreenUi::mapWindowToUi(const QPointF& p, QObject* dest) {
vec2 sourceSize;
if (dynamic_cast<QWidget*>(dest)) {
sourceSize = toGlm(((QWidget*)dest)->size());
} else if (dynamic_cast<QWindow*>(dest)) {
sourceSize = toGlm(((QWindow*)dest)->size());
}
vec2 pos = toGlm(p);
pos /= sourceSize;
pos *= vec2(toGlm(_quickWindow->size()));
return QPointF(pos.x, pos.y);
}
///////////////////////////////////////////////////////
//
// Event handling customization
//
bool OffscreenUi::eventFilter(QObject* dest, QEvent* e) {
// Only intercept events while we're in an active state
if (_paused) {
return false;
}
// Don't intercept our own events, or we enter an infinite recursion
if (dest == _quickWindow) {
return false;
}
switch (e->type()) {
case QEvent::Resize: {
QResizeEvent* re = (QResizeEvent*)e;
QGLWidget* widget = dynamic_cast<QGLWidget*>(dest);
if (widget) {
this->resize(re->size());
}
return false;
}
case QEvent::KeyPress:
case QEvent::KeyRelease: {
e->ignore();
if (QCoreApplication::sendEvent(_quickWindow, e)) {
return e->isAccepted();
}
break;
}
case QEvent::Wheel: {
QWheelEvent* we = (QWheelEvent*)e;
QWheelEvent mappedEvent(mapWindowToUi(we->pos(), dest), we->delta(), we->buttons(), we->modifiers(), we->orientation());
QCoreApplication::sendEvent(_quickWindow, &mappedEvent);
return true;
}
// Fall through
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove: {
QMouseEvent* me = (QMouseEvent *)e;
QPointF originalPos = me->localPos();
QPointF transformedPos = _mouseTranslator(originalPos);
QMouseEvent mappedEvent(e->type(), mapWindowToUi(transformedPos, dest), me->screenPos(), me->button(), me->buttons(), me->modifiers());
QCoreApplication::sendEvent(_quickWindow, &mappedEvent);
return QObject::event(e);
}
default:
break;
}
return false;
}
void OffscreenUi::lockTexture(int texture) {
_fboCache.lockTexture(texture);
}
void OffscreenUi::releaseTexture(int texture) {
_fboCache.releaseTexture(texture);
}
void OffscreenUi::pause() {
_paused = true;
}
void OffscreenUi::resume() {
_paused = false;
requestRender();
}
bool OffscreenUi::isPaused() const {
return _paused;
}
void OffscreenUi::setProxyWindow(QWindow* window) {
_renderControl->_renderWindow = window;
}
void OffscreenUi::show(const QUrl& url, const QString& name) {
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
// First load?
if (!item) {
load(url);
return;
}
item->setEnabled(true);
}
void OffscreenUi::toggle(const QUrl& url, const QString& name) {
QQuickItem* item = _rootItem->findChild<QQuickItem*>(name);
// First load?
if (nullptr == item) {
load(url);
return;
}
item->setEnabled(!item->isEnabled());
}
void OffscreenUi::messageBox(const QString& title, const QString& text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
ButtonCallback f) {
}
void OffscreenUi::information(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::information(nullptr, title, text, buttons));
}
void OffscreenUi::question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::question(nullptr, title, text, buttons));
}
void OffscreenUi::warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::warning(nullptr, title, text, buttons));
}
void OffscreenUi::critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::critical(nullptr, title, text, buttons));
}
OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {};
#include "OffscreenUi.moc"