mirror of
https://github.com/overte-org/overte.git
synced 2025-04-17 03:01:59 +02:00
Merge pull request #8743 from jherico/qml_threading
Remove QML threaded rendering
This commit is contained in:
commit
b0c2bda898
4 changed files with 289 additions and 452 deletions
|
@ -32,11 +32,13 @@
|
|||
#include <AbstractUriHandler.h>
|
||||
#include <AccountManager.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
#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,8 @@ 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);
|
||||
|
||||
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<GLuint, TexInfo>;
|
||||
using Queue = std::queue<TexturePtr>;
|
||||
|
||||
Map _allTextures;
|
||||
Queue _readyTextures;
|
||||
uvec2 _size { 1920, 1080 };
|
||||
bool _useMipmaps;
|
||||
};
|
||||
|
||||
|
||||
void RawTextureRecycler::setSize(const uvec2& size) {
|
||||
if (size == _size) {
|
||||
return;
|
||||
}
|
||||
_size = size;
|
||||
while (!_readyTextures.empty()) {
|
||||
_readyTextures.pop();
|
||||
}
|
||||
std::set<Map::key_type> 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);
|
||||
}
|
||||
|
||||
|
||||
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<QEvent*> {
|
||||
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<bool> _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<OffscreenQmlSurface::TextureAndFence> _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::WindowFlags>(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 +135,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 +151,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 +183,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<std::mutex> 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<std::mutex> lock(_textureMutex);
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureAndFence.first) {
|
||||
_textures.recycleTexture(_latestTextureAndFence.first);
|
||||
glDeleteSync(static_cast<GLsync>(_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<std::mutex> 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<std::mutex> 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<std::mutex> lock(_textureMutex);
|
||||
if (0 != _latestTexture) {
|
||||
if (0 != _latestTextureAndFence.first) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -588,33 +255,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::WindowFlags>(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 +305,26 @@ 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", _glData);
|
||||
_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;
|
||||
}
|
||||
_glData = ::getGLContextData();
|
||||
_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 +339,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
|
||||
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
||||
|
||||
if (!_renderer || !_renderer->_quickWindow) {
|
||||
if (!_quickWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -662,7 +355,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
|||
std::max(static_cast<int>(scale * newSize.height()), 10));
|
||||
}
|
||||
|
||||
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
||||
QSize currentSize = _quickWindow->geometry().size();
|
||||
if (newSize == currentSize && !forceResize) {
|
||||
return;
|
||||
}
|
||||
|
@ -673,12 +366,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 +417,6 @@ void OffscreenQmlSurface::clearCache() {
|
|||
getRootContext()->engine()->clearComponentCache();
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::requestUpdate() {
|
||||
_polish = true;
|
||||
_render = true;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::requestRender() {
|
||||
_render = true;
|
||||
}
|
||||
|
||||
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
||||
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
||||
if (_qmlComponent->isError()) {
|
||||
|
@ -770,8 +468,8 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
}
|
||||
// The root item is ready. Associate it with the window.
|
||||
_rootItem = newItem;
|
||||
_rootItem->setParentItem(_renderer->_quickWindow->contentItem());
|
||||
_rootItem->setSize(_renderer->_quickWindow->renderTargetSize());
|
||||
_rootItem->setParentItem(_quickWindow->contentItem());
|
||||
_rootItem->setSize(_quickWindow->renderTargetSize());
|
||||
return _rootItem;
|
||||
}
|
||||
|
||||
|
@ -781,34 +479,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<QWidget*>(sourceObject)) {
|
||||
|
@ -818,7 +504,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 +518,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 +536,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 +555,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 +569,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 +590,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 +609,7 @@ void OffscreenQmlSurface::pause() {
|
|||
|
||||
void OffscreenQmlSurface::resume() {
|
||||
_paused = false;
|
||||
requestRender();
|
||||
_render = true;
|
||||
}
|
||||
|
||||
bool OffscreenQmlSurface::isPaused() const {
|
||||
|
@ -932,8 +618,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 +628,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() {
|
||||
|
|
|
@ -9,16 +9,21 @@
|
|||
#ifndef hifi_OffscreenQmlSurface_h
|
||||
#define hifi_OffscreenQmlSurface_h
|
||||
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <ThreadHelpers.h>
|
||||
#include "TextureRecycler.h"
|
||||
|
||||
class QWindow;
|
||||
class QMyQuickRenderControl;
|
||||
class OffscreenGLCanvas;
|
||||
class QOpenGLContext;
|
||||
class QQmlEngine;
|
||||
class QQmlContext;
|
||||
|
@ -26,8 +31,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 +89,6 @@ signals:
|
|||
void focusTextChanged(bool focusText);
|
||||
|
||||
public slots:
|
||||
void requestUpdate();
|
||||
void requestRender();
|
||||
void onAboutToQuit();
|
||||
|
||||
protected:
|
||||
|
@ -97,24 +98,44 @@ protected:
|
|||
private:
|
||||
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
||||
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
|
||||
void setupFbo();
|
||||
bool allowNewFrame(uint8_t fps);
|
||||
void render();
|
||||
void cleanup();
|
||||
QJsonObject getGLContextData();
|
||||
|
||||
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 };
|
||||
QJsonObject _glData;
|
||||
|
||||
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<TextureAndFence> _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 };
|
||||
};
|
||||
|
||||
|
|
83
libraries/gl/src/gl/TextureRecycler.cpp
Normal file
83
libraries/gl/src/gl/TextureRecycler.cpp
Normal file
|
@ -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 <set>
|
||||
|
||||
|
||||
void TextureRecycler::setSize(const uvec2& size) {
|
||||
if (size == _size) {
|
||||
return;
|
||||
}
|
||||
_size = size;
|
||||
while (!_readyTextures.empty()) {
|
||||
_readyTextures.pop();
|
||||
}
|
||||
std::set<Map::key_type> 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);
|
||||
}
|
||||
|
47
libraries/gl/src/gl/TextureRecycler.h
Normal file
47
libraries/gl/src/gl/TextureRecycler.h
Normal file
|
@ -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 <atomic>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
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<uint32_t, TexInfo>;
|
||||
using Queue = std::queue<uint32_t>;
|
||||
|
||||
Map _allTextures;
|
||||
Queue _readyTextures;
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
bool _useMipmaps;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue