diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 797f297488..bcfaaffd08 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -32,11 +32,13 @@ #include #include #include +#include #include "OffscreenGLCanvas.h" #include "GLHelpers.h" #include "GLLogging.h" - +#include "TextureRecycler.h" +#include "Context.h" QString fixupHifiUrl(const QString& urlString) { static const QString ACCESS_TOKEN_PARAMETER = "access_token"; @@ -114,257 +116,23 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) { Q_DECLARE_LOGGING_CATEGORY(offscreenFocus) Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") -static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1); -static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2); -static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3); -static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4); +#if 0 +QJsonObject getGLContextData(); +QJsonObject _glData; -class RawTextureRecycler { -public: - using TexturePtr = GLuint; - RawTextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {} - void setSize(const uvec2& size); - void clear(); - TexturePtr getNextTexture(); - void recycleTexture(GLuint texture); - -private: - - struct TexInfo { - TexturePtr _tex { 0 }; - uvec2 _size; - bool _active { false }; - - TexInfo() {} - TexInfo(TexturePtr tex, const uvec2& size) : _tex(tex), _size(size) {} - }; - - using Map = std::map; - using Queue = std::queue; - - Map _allTextures; - Queue _readyTextures; - uvec2 _size { 1920, 1080 }; - bool _useMipmaps; -}; - - -void RawTextureRecycler::setSize(const uvec2& size) { - if (size == _size) { - return; +QJsonObject OffscreenQmlSurface::getGLContextData() { + _glMutex.lock(); + if (_glData.isEmpty()) { + _glWait.wait(&_glMutex); } - _size = size; - while (!_readyTextures.empty()) { - _readyTextures.pop(); - } - std::set toDelete; - std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) { - if (!item.second._active && item.second._size != _size) { - toDelete.insert(item.first); - } - }); - std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) { - _allTextures.erase(key); - }); -} - -void RawTextureRecycler::clear() { - while (!_readyTextures.empty()) { - _readyTextures.pop(); - } - _allTextures.clear(); -} - -RawTextureRecycler::TexturePtr RawTextureRecycler::getNextTexture() { - if (_readyTextures.empty()) { - TexturePtr newTexture; - glGenTextures(1, &newTexture); - - glBindTexture(GL_TEXTURE_2D, newTexture); - if (_useMipmaps) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - _allTextures[newTexture] = TexInfo { newTexture, _size }; - _readyTextures.push(newTexture); - } - - TexturePtr result = _readyTextures.front(); - _readyTextures.pop(); - auto& item = _allTextures[result]; - item._active = true; - return result; -} - -void RawTextureRecycler::recycleTexture(GLuint texture) { - Q_ASSERT(_allTextures.count(texture)); - auto& item = _allTextures[texture]; - Q_ASSERT(item._active); - item._active = false; - if (item._size != _size) { - // Buh-bye - _allTextures.erase(texture); - return; - } - - _readyTextures.push(item._tex); + _glMutex.unlock(); + return _glData; } +#endif -class OffscreenQmlRenderThread : public QThread { -public: - OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext); - virtual ~OffscreenQmlRenderThread() = default; - - virtual void run() override; - virtual bool event(QEvent *e) override; - -protected: - class Queue : private QQueue { - public: - void add(QEvent::Type type); - QEvent* take(); - - private: - QMutex _mutex; - QWaitCondition _waitCondition; - bool _isWaiting{ false }; - }; - - friend class OffscreenQmlSurface; - - QJsonObject getGLContextData(); - - Queue _queue; - QMutex _mutex; - QWaitCondition _waitCondition; - std::atomic _rendering { false }; - - QJsonObject _glData; - QMutex _glMutex; - QWaitCondition _glWait; - -private: - // Event-driven methods - void init(); - void render(); - void resize(); - void cleanup(); - - // Helper methods - void setupFbo(); - bool allowNewFrame(uint8_t fps); - bool fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence); - void releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence); - - // Texture management - std::mutex _textureMutex; - GLuint _latestTexture { 0 }; - GLsync _latestTextureFence { 0 }; - std::list _returnedTextures; - - // Rendering members - OffscreenGLCanvas _canvas; - OffscreenQmlSurface* _surface{ nullptr }; - QQuickWindow* _quickWindow{ nullptr }; - QMyQuickRenderControl* _renderControl{ nullptr }; - GLuint _fbo { 0 }; - GLuint _depthStencil { 0 }; - RawTextureRecycler _textures { true }; - - uint64_t _lastRenderTime{ 0 }; - uvec2 _size{ 1920, 1080 }; - QSize _newSize; - bool _quit{ false }; -}; - -void OffscreenQmlRenderThread::Queue::add(QEvent::Type type) { - QMutexLocker locker(&_mutex); - enqueue(new QEvent(type)); - if (_isWaiting) { - _waitCondition.wakeOne(); - } -} - -QEvent* OffscreenQmlRenderThread::Queue::take() { - QMutexLocker locker(&_mutex); - while (isEmpty()) { - _isWaiting = true; - _waitCondition.wait(&_mutex); - _isWaiting = false; - } - QEvent* e = dequeue(); - return e; -} - -OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) { - _canvas.setObjectName("OffscreenQmlRenderCanvas"); - qCDebug(glLogging) << "Building QML Renderer"; - if (!_canvas.create(shareContext)) { - qWarning("Failed to create OffscreenGLCanvas"); - _quit = true; - return; - }; - - _renderControl = new QMyQuickRenderControl(); - QQuickWindow::setDefaultAlphaBuffer(true); - // Create a QQuickWindow that is associated with our render control. - // This window never gets created or shown, meaning that it will never get an underlying native (platform) window. - // NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events - // NOTE: Must be created on the rendering thread or it will refuse to render, - // so we wait until after its ctor to move object/context to this thread. - _quickWindow = new QQuickWindow(_renderControl); - _quickWindow->setColor(QColor(255, 255, 255, 0)); - _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); - - // We can prepare, but we must wait to start() the thread until after the ctor - _renderControl->prepareThread(this); - _canvas.getContextObject()->moveToThread(this); - moveToThread(this); - - _queue.add(INIT); -} - -void OffscreenQmlRenderThread::run() { - qCDebug(glLogging) << "Starting QML Renderer thread"; - - while (!_quit) { - QEvent* e = _queue.take(); - event(e); - delete e; - } -} - -bool OffscreenQmlRenderThread::event(QEvent *e) { - switch (int(e->type())) { - case INIT: - init(); - return true; - case RENDER: - render(); - return true; - case RESIZE: - resize(); - return true; - case STOP: - cleanup(); - return true; - default: - return QObject::event(e); - } -} - -void OffscreenQmlRenderThread::setupFbo() { +void OffscreenQmlSurface::setupFbo() { + _canvas->makeCurrent(); _textures.setSize(_size); if (_depthStencil) { glDeleteRenderbuffers(1, &_depthStencil); @@ -382,41 +150,12 @@ void OffscreenQmlRenderThread::setupFbo() { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + _canvas->doneCurrent(); } -QJsonObject OffscreenQmlRenderThread::getGLContextData() { - _glMutex.lock(); - if (_glData.isEmpty()) { - _glWait.wait(&_glMutex); - } - _glMutex.unlock(); - return _glData; -} - -void OffscreenQmlRenderThread::init() { - qCDebug(glLogging) << "Initializing QML Renderer"; - - if (!_canvas.makeCurrent()) { - qWarning("Failed to make context current on QML Renderer Thread"); - _quit = true; - return; - } - - _glMutex.lock(); - _glData = ::getGLContextData(); - _glMutex.unlock(); - _glWait.wakeAll(); - - connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender); - connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate); - - _renderControl->initialize(_canvas.getContext()); - setupFbo(); -} - -void OffscreenQmlRenderThread::cleanup() { +void OffscreenQmlSurface::cleanup() { + _canvas->makeCurrent(); _renderControl->invalidate(); - if (_depthStencil) { glDeleteRenderbuffers(1, &_depthStencil); _depthStencil = 0; @@ -427,65 +166,17 @@ void OffscreenQmlRenderThread::cleanup() { } _textures.clear(); - - _canvas.doneCurrent(); - _canvas.getContextObject()->moveToThread(QCoreApplication::instance()->thread()); - - _quit = true; + _canvas->doneCurrent(); } -void OffscreenQmlRenderThread::resize() { - // Lock _newSize changes - { - QMutexLocker locker(&_mutex); - - // Update our members - if (_quickWindow) { - _quickWindow->setGeometry(QRect(QPoint(), _newSize)); - _quickWindow->contentItem()->setSize(_newSize); - } - - // Qt bug in 5.4 forces this check of pixel ratio, - // even though we're rendering offscreen. - qreal pixelRatio = 1.0; - if (_renderControl && _renderControl->_renderWindow) { - pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); - } - - uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio); - if (newOffscreenSize == _size) { - return; - } - - qCDebug(glLogging) << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio; - _size = newOffscreenSize; - } - - _textures.setSize(_size); - setupFbo(); -} - -void OffscreenQmlRenderThread::render() { - // Ensure we always release the main thread - Finally releaseMainThread([this] { - _waitCondition.wakeOne(); - }); - - if (_surface->_paused) { +void OffscreenQmlSurface::render() { + if (_paused) { return; } - _rendering = true; - Finally unmarkRenderingFlag([this] { - _rendering = false; - }); - - { - QMutexLocker locker(&_mutex); - _renderControl->sync(); - releaseMainThread.trigger(); - } + _canvas->makeCurrent(); + _renderControl->sync(); _quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y)); // Clear out any pending textures to be returned @@ -507,70 +198,61 @@ void OffscreenQmlRenderThread::render() { } } - try { - GLuint texture = _textures.getNextTexture(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); - PROFILE_RANGE("qml_render->rendercontrol") - _renderControl->render(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, texture); - glGenerateMipmap(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); + GLuint texture = _textures.getNextTexture(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); + PROFILE_RANGE("qml_render->rendercontrol") + _renderControl->render(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, texture); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); - { - std::unique_lock lock(_textureMutex); - // If the most recent texture was unused, we can directly recycle it - if (_latestTextureFence) { - } - if (_latestTexture) { - _textures.recycleTexture(_latestTexture); - glDeleteSync(_latestTextureFence); - _latestTexture = 0; - _latestTextureFence = 0; - } - - _latestTextureFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _latestTexture = texture; - // Fence will be used in another thread / context, so a flush is required - glFlush(); + { + std::unique_lock lock(_textureMutex); + // If the most recent texture was unused, we can directly recycle it + if (_latestTextureAndFence.first) { + _textures.recycleTexture(_latestTextureAndFence.first); + glDeleteSync(static_cast(_latestTextureAndFence.second)); + _latestTextureAndFence = { 0, 0 }; } - _quickWindow->resetOpenGLState(); - _lastRenderTime = usecTimestampNow(); - } catch (std::runtime_error& error) { - qWarning() << "Failed to render QML: " << error.what(); + _latestTextureAndFence = { texture, glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) }; + // Fence will be used in another thread / context, so a flush is required + glFlush(); } + + _quickWindow->resetOpenGLState(); + _lastRenderTime = usecTimestampNow(); + _canvas->doneCurrent(); } -bool OffscreenQmlRenderThread::fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence) { +bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) { textureAndFence = { 0, 0 }; std::unique_lock lock(_textureMutex); - if (0 == _latestTexture) { + if (0 == _latestTextureAndFence.first) { return false; } // Ensure writes to the latest texture are complete before before returning it for reading - Q_ASSERT(0 != _latestTextureFence); - textureAndFence = { _latestTexture, _latestTextureFence }; - _latestTextureFence = 0; - _latestTexture = 0; + textureAndFence = _latestTextureAndFence; + _latestTextureAndFence = { 0, 0 }; return true; } -void OffscreenQmlRenderThread::releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence) { +void OffscreenQmlSurface::releaseTexture(const TextureAndFence& textureAndFence) { std::unique_lock lock(_textureMutex); _returnedTextures.push_back(textureAndFence); } -bool OffscreenQmlRenderThread::allowNewFrame(uint8_t fps) { +bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) { // If we already have a pending texture, don't render another one // i.e. don't render faster than the consumer context, since it wastes // GPU cycles on producing output that will never be seen { std::unique_lock lock(_textureMutex); - if (0 != _latestTexture) { + if (0 != _latestTextureAndFence.first) { return false; } } @@ -588,33 +270,46 @@ OffscreenQmlSurface::~OffscreenQmlSurface() { QObject::disconnect(&_updateTimer); QObject::disconnect(qApp); - qCDebug(glLogging) << "Stopping QML Renderer Thread " << _renderer->currentThreadId(); - _renderer->_queue.add(STOP); - if (!_renderer->wait(MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) { - qWarning() << "Failed to shut down the QML Renderer Thread"; - } - delete _rootItem; - delete _renderer; - delete _qmlComponent; - delete _qmlEngine; + cleanup(); + + _canvas->deleteLater(); + _rootItem->deleteLater(); + _qmlComponent->deleteLater(); + _qmlEngine->deleteLater(); + _quickWindow->deleteLater(); } void OffscreenQmlSurface::onAboutToQuit() { + _paused = true; QObject::disconnect(&_updateTimer); } void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { qCDebug(glLogging) << "Building QML surface"; - _renderer = new OffscreenQmlRenderThread(this, shareContext); - _renderer->moveToThread(_renderer); - _renderer->setObjectName("QML Renderer Thread"); - _renderer->start(); + _renderControl = new QMyQuickRenderControl(); - _renderer->_renderControl->_renderWindow = _proxyWindow; + QQuickWindow::setDefaultAlphaBuffer(true); - connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // Create a QQuickWindow that is associated with our render control. + // This window never gets created or shown, meaning that it will never get an underlying native (platform) window. + // NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events + // NOTE: Must be created on the rendering thread or it will refuse to render, + // so we wait until after its ctor to move object/context to this thread. + _quickWindow = new QQuickWindow(_renderControl); + _quickWindow->setColor(QColor(255, 255, 255, 0)); + _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground)); + + _renderControl->_renderWindow = _proxyWindow; + + _canvas = new OffscreenGLCanvas(); + if (!_canvas->create(shareContext)) { + qFatal("Failed to create OffscreenGLCanvas"); + return; + }; + + connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); // Create a QML engine. _qmlEngine = new QQmlEngine; @@ -625,13 +320,25 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { importList.insert(importList.begin(), PathUtils::resourcesPath()); _qmlEngine->setImportPathList(importList); if (!_qmlEngine->incubationController()) { - _qmlEngine->setIncubationController(_renderer->_quickWindow->incubationController()); + _qmlEngine->setIncubationController(_quickWindow->incubationController()); } - _qmlEngine->rootContext()->setContextProperty("GL", _renderer->getGLContextData()); + // FIXME +// _qmlEngine->rootContext()->setContextProperty("GL", _renderer->getGLContextData()); _qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlComponent = new QQmlComponent(_qmlEngine); + + connect(_renderControl, &QQuickRenderControl::renderRequested, [this] { _render = true; }); + connect(_renderControl, &QQuickRenderControl::sceneChanged, [this] { _render = _polish = true; }); + + if (!_canvas->makeCurrent()) { + qWarning("Failed to make context current for QML Renderer"); + return; + } + _renderControl->initialize(_canvas->getContext()); + setupFbo(); + // 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. QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick); @@ -646,7 +353,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { - if (!_renderer || !_renderer->_quickWindow) { + if (!_quickWindow) { return; } @@ -662,7 +369,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { std::max(static_cast(scale * newSize.height()), 10)); } - QSize currentSize = _renderer->_quickWindow->geometry().size(); + QSize currentSize = _quickWindow->geometry().size(); if (newSize == currentSize && !forceResize) { return; } @@ -673,12 +380,26 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { _rootItem->setSize(newSize); } - { - QMutexLocker locker(&(_renderer->_mutex)); - _renderer->_newSize = newSize; + // Update our members + _quickWindow->setGeometry(QRect(QPoint(), newSize)); + _quickWindow->contentItem()->setSize(newSize); + + // Qt bug in 5.4 forces this check of pixel ratio, + // even though we're rendering offscreen. + qreal pixelRatio = 1.0; + if (_renderControl && _renderControl->_renderWindow) { + pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); } - _renderer->_queue.add(RESIZE); + uvec2 newOffscreenSize = toGlm(newSize * pixelRatio); + if (newOffscreenSize == _size) { + return; + } + + qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio; + _size = newOffscreenSize; + _textures.setSize(_size); + setupFbo(); } QQuickItem* OffscreenQmlSurface::getRootItem() { @@ -710,15 +431,6 @@ void OffscreenQmlSurface::clearCache() { getRootContext()->engine()->clearComponentCache(); } -void OffscreenQmlSurface::requestUpdate() { - _polish = true; - _render = true; -} - -void OffscreenQmlSurface::requestRender() { - _render = true; -} - QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) { disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); if (_qmlComponent->isError()) { @@ -770,8 +482,8 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionsetParentItem(_renderer->_quickWindow->contentItem()); - _rootItem->setSize(_renderer->_quickWindow->renderTargetSize()); + _rootItem->setParentItem(_quickWindow->contentItem()); + _rootItem->setSize(_quickWindow->renderTargetSize()); return _rootItem; } @@ -781,34 +493,22 @@ void OffscreenQmlSurface::updateQuick() { // b) already rendering a frame // c) rendering too fast // then skip this - if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) { + if (!allowNewFrame(_maxFps)) { return; } if (_polish) { - _renderer->_renderControl->polishItems(); + _renderControl->polishItems(); _polish = false; } if (_render) { PROFILE_RANGE(__FUNCTION__); - // Lock the GUI size while syncing - QMutexLocker locker(&(_renderer->_mutex)); - _renderer->_queue.add(RENDER); - // FIXME need to find a better way to handle the render lockout than this locking of the main thread - _renderer->_waitCondition.wait(&(_renderer->_mutex)); + render(); _render = false; } } -bool OffscreenQmlSurface::fetchTexture(TextureAndFence& texture) { - return _renderer->fetchTexture(texture); -} - -void OffscreenQmlSurface::releaseTexture(const TextureAndFence& texture) { - _renderer->releaseTexture(texture); -} - QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) { vec2 sourceSize; if (dynamic_cast(sourceObject)) { @@ -818,7 +518,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec } vec2 offscreenPosition = toGlm(sourcePosition); offscreenPosition /= sourceSize; - offscreenPosition *= vec2(toGlm(_renderer->_quickWindow->size())); + offscreenPosition *= vec2(toGlm(_quickWindow->size())); return QPointF(offscreenPosition.x, offscreenPosition.y); } @@ -832,7 +532,7 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO // bool OffscreenQmlSurface::filterEnabled(QObject* originalDestination, QEvent* event) const { - if (_renderer->_quickWindow == originalDestination) { + if (_quickWindow == originalDestination) { return false; } // Only intercept events while we're in an active state @@ -850,7 +550,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even // Don't intercept our own events, or we enter an infinite recursion QObject* recurseTest = originalDestination; while (recurseTest) { - Q_ASSERT(recurseTest != _rootItem && recurseTest != _renderer->_quickWindow); + Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow); recurseTest = recurseTest->parent(); } #endif @@ -869,7 +569,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even case QEvent::KeyPress: case QEvent::KeyRelease: { event->ignore(); - if (QCoreApplication::sendEvent(_renderer->_quickWindow, event)) { + if (QCoreApplication::sendEvent(_quickWindow, event)) { return event->isAccepted(); } break; @@ -883,7 +583,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even wheelEvent->delta(), wheelEvent->buttons(), wheelEvent->modifiers(), wheelEvent->orientation()); mappedEvent.ignore(); - if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) { + if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { return mappedEvent.isAccepted(); } break; @@ -904,7 +604,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos); } mappedEvent.ignore(); - if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) { + if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { return mappedEvent.isAccepted(); } break; @@ -923,7 +623,7 @@ void OffscreenQmlSurface::pause() { void OffscreenQmlSurface::resume() { _paused = false; - requestRender(); + _render = true; } bool OffscreenQmlSurface::isPaused() const { @@ -932,8 +632,8 @@ bool OffscreenQmlSurface::isPaused() const { void OffscreenQmlSurface::setProxyWindow(QWindow* window) { _proxyWindow = window; - if (_renderer && _renderer->_renderControl) { - _renderer->_renderControl->_renderWindow = window; + if (_renderControl) { + _renderControl->_renderWindow = window; } } @@ -942,11 +642,11 @@ QObject* OffscreenQmlSurface::getEventHandler() { } QQuickWindow* OffscreenQmlSurface::getWindow() { - return _renderer->_quickWindow; + return _quickWindow; } QSize OffscreenQmlSurface::size() const { - return _renderer->_quickWindow->geometry().size(); + return _quickWindow->geometry().size(); } QQmlContext* OffscreenQmlSurface::getRootContext() { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index fa2346dd2f..7232e410ea 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -16,9 +16,11 @@ #include #include +#include "TextureRecycler.h" class QWindow; class QMyQuickRenderControl; +class OffscreenGLCanvas; class QOpenGLContext; class QQmlEngine; class QQmlContext; @@ -26,8 +28,6 @@ class QQmlComponent; class QQuickWindow; class QQuickItem; -class OffscreenQmlRenderThread; - class OffscreenQmlSurface : public QObject { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) @@ -86,8 +86,6 @@ signals: void focusTextChanged(bool focusText); public slots: - void requestUpdate(); - void requestRender(); void onAboutToQuit(); protected: @@ -97,24 +95,42 @@ protected: private: QObject* finishQmlLoad(std::function f); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); + void setupFbo(); + bool allowNewFrame(uint8_t fps); + void render(); + void resize(); + void cleanup(); private slots: void updateQuick(); void onFocusObjectChanged(QObject* newFocus); private: - friend class OffscreenQmlRenderThread; - OffscreenQmlRenderThread* _renderer{ nullptr }; - QQmlEngine* _qmlEngine{ nullptr }; - QQmlComponent* _qmlComponent{ nullptr }; - QQuickItem* _rootItem{ nullptr }; + QQuickWindow* _quickWindow { nullptr }; + QMyQuickRenderControl* _renderControl{ nullptr }; + QQmlEngine* _qmlEngine { nullptr }; + QQmlComponent* _qmlComponent { nullptr }; + QQuickItem* _rootItem { nullptr }; + OffscreenGLCanvas* _canvas { nullptr }; QTimer _updateTimer; - bool _render{ false }; - bool _polish{ true }; - bool _paused{ true }; + uint32_t _fbo { 0 }; + uint32_t _depthStencil { 0 }; + uint64_t _lastRenderTime { 0 }; + uvec2 _size { 1920, 1080 }; + TextureRecycler _textures { true }; + + // Texture management + std::mutex _textureMutex; + TextureAndFence _latestTextureAndFence { 0, 0 }; + std::list _returnedTextures; + + + bool _render { false }; + bool _polish { true }; + bool _paused { true }; bool _focusText { false }; - uint8_t _maxFps{ 60 }; - MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; + uint8_t _maxFps { 60 }; + MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } }; QWindow* _proxyWindow { nullptr }; }; diff --git a/libraries/gl/src/gl/TextureRecycler.cpp b/libraries/gl/src/gl/TextureRecycler.cpp new file mode 100644 index 0000000000..4438e158c5 --- /dev/null +++ b/libraries/gl/src/gl/TextureRecycler.cpp @@ -0,0 +1,83 @@ +// +// Created by Bradley Austin Davis on 2016-10-05 +// 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 "TextureRecycler.h" +#include "Config.h" + +#include + + +void TextureRecycler::setSize(const uvec2& size) { + if (size == _size) { + return; + } + _size = size; + while (!_readyTextures.empty()) { + _readyTextures.pop(); + } + std::set toDelete; + std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) { + if (!item.second._active && item.second._size != _size) { + toDelete.insert(item.first); + } + }); + std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) { + _allTextures.erase(key); + }); +} + +void TextureRecycler::clear() { + while (!_readyTextures.empty()) { + _readyTextures.pop(); + } + _allTextures.clear(); +} + +uint32_t TextureRecycler::getNextTexture() { + if (_readyTextures.empty()) { + uint32_t newTexture; + glGenTextures(1, &newTexture); + glBindTexture(GL_TEXTURE_2D, newTexture); + if (_useMipmaps) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + _allTextures.emplace(std::piecewise_construct, std::forward_as_tuple(newTexture), std::forward_as_tuple(newTexture, _size)); + _readyTextures.push(newTexture); + } + + uint32_t result = _readyTextures.front(); + _readyTextures.pop(); + auto& item = _allTextures[result]; + item._active = true; + return result; +} + +void TextureRecycler::recycleTexture(GLuint texture) { + Q_ASSERT(_allTextures.count(texture)); + auto& item = _allTextures[texture]; + Q_ASSERT(item._active); + item._active = false; + if (item._size != _size) { + // Buh-bye + _allTextures.erase(texture); + return; + } + + _readyTextures.push(item._tex); +} + diff --git a/libraries/gl/src/gl/TextureRecycler.h b/libraries/gl/src/gl/TextureRecycler.h new file mode 100644 index 0000000000..46cbcad219 --- /dev/null +++ b/libraries/gl/src/gl/TextureRecycler.h @@ -0,0 +1,47 @@ +// +// 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 +// +#pragma once +#ifndef hifi_TextureRecycler_h +#define hifi_TextureRecycler_h + +#include +#include +#include + +#include + +class TextureRecycler { +public: + TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {} + void setSize(const uvec2& size); + void clear(); + uint32_t getNextTexture(); + void recycleTexture(uint32_t texture); + +private: + + struct TexInfo { + const uint32_t _tex{ 0 }; + const uvec2 _size; + bool _active { false }; + + TexInfo() {} + TexInfo(uint32_t tex, const uvec2& size) : _tex(tex), _size(size) {} + TexInfo(const TexInfo& other) : _tex(other._tex), _size(other._size) {} + }; + + using Map = std::map; + using Queue = std::queue; + + Map _allTextures; + Queue _readyTextures; + uvec2 _size{ 1920, 1080 }; + bool _useMipmaps; +}; + +#endif