mirror of
https://github.com/overte-org/overte.git
synced 2025-04-13 18:42:11 +02:00
QML framerate improvments
This commit is contained in:
parent
acf9933925
commit
0fa3044231
13 changed files with 714 additions and 198 deletions
|
@ -77,6 +77,7 @@
|
|||
#include <NetworkAccessManager.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <ObjectMotionState.h>
|
||||
#include <OffscreenGlCanvas.h>
|
||||
#include <OctalCode.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
|
|
@ -46,12 +46,7 @@ ApplicationOverlay::ApplicationOverlay()
|
|||
// then release it back to the UI for re-use
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->lockTexture(textureId);
|
||||
std::swap(_uiTexture, textureId);
|
||||
if (textureId) {
|
||||
offscreenUi->releaseTexture(textureId);
|
||||
}
|
||||
_uiTexture = textureId;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -43,20 +43,12 @@ RenderableWebEntityItem::~RenderableWebEntityItem() {
|
|||
if (_webSurface) {
|
||||
_webSurface->pause();
|
||||
_webSurface->disconnect(_connection);
|
||||
// After the disconnect, ensure that we have the latest texture by acquiring the
|
||||
// lock used when updating the _texture value
|
||||
_textureLock.lock();
|
||||
_textureLock.unlock();
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// is no longer valid
|
||||
auto webSurface = _webSurface;
|
||||
auto texture = _texture;
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface, texture] {
|
||||
if (texture) {
|
||||
webSurface->releaseTexture(texture);
|
||||
}
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
|
||||
webSurface->deleteLater();
|
||||
});
|
||||
}
|
||||
|
@ -74,23 +66,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_webSurface->lockTexture(textureId);
|
||||
assert(!glGetError());
|
||||
// TODO change to atomic<GLuint>?
|
||||
withLock(_textureLock, [&] {
|
||||
std::swap(_texture, textureId);
|
||||
});
|
||||
if (textureId) {
|
||||
_webSurface->releaseTexture(textureId);
|
||||
}
|
||||
if (_texture) {
|
||||
_webSurface->makeCurrent();
|
||||
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
_webSurface->doneCurrent();
|
||||
}
|
||||
_texture = textureId;
|
||||
});
|
||||
|
||||
auto forwardMouseEvent = [=](const RayToEntityIntersectionResult& intersection, const QMouseEvent* event, unsigned int deviceId) {
|
||||
|
@ -145,6 +121,19 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
point += 0.5f;
|
||||
point.y = 1.0f - point.y;
|
||||
point *= getDimensions() * METERS_TO_INCHES * DPI;
|
||||
|
||||
if (event->button() == Qt::MouseButton::LeftButton) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
this->_pressed = true;
|
||||
this->_lastMove = ivec2((int)point.x, (int)point.y);
|
||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
this->_pressed = false;
|
||||
}
|
||||
}
|
||||
if (event->type() == QEvent::MouseMove) {
|
||||
this->_lastMove = ivec2((int)point.x, (int)point.y);
|
||||
}
|
||||
|
||||
// Forward the mouse event.
|
||||
QMouseEvent mappedEvent(event->type(),
|
||||
QPoint((int)point.x, (int)point.y),
|
||||
|
@ -158,6 +147,16 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent);
|
||||
QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent);
|
||||
QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent);
|
||||
QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
||||
if (this->_pressed && this->getID() == entityItemID) {
|
||||
// If the user mouses off the entity while the button is down, simulate a mouse release
|
||||
QMouseEvent mappedEvent(QEvent::MouseButtonRelease,
|
||||
QPoint(_lastMove.x, _lastMove.y),
|
||||
Qt::MouseButton::LeftButton,
|
||||
Qt::MouseButtons(), Qt::KeyboardModifiers());
|
||||
QCoreApplication::sendEvent(_webSurface->getWindow(), &mappedEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
glm::vec2 dims = glm::vec2(getDimensions());
|
||||
|
|
|
@ -34,7 +34,8 @@ private:
|
|||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
ivec2 _lastPress{ INT_MIN };
|
||||
QMutex _textureLock;
|
||||
bool _pressed{ false };
|
||||
ivec2 _lastMove{ INT_MIN };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -32,4 +32,12 @@ if (WIN32)
|
|||
endif()
|
||||
endif (WIN32)
|
||||
|
||||
add_dependency_external_projects(boostconfig)
|
||||
find_package(BoostConfig REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${BOOSTCONFIG_INCLUDE_DIRS})
|
||||
|
||||
add_dependency_external_projects(oglplus)
|
||||
find_package(OGLPLUS REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS})
|
||||
|
||||
link_hifi_libraries(animation fbx shared gpu model render environment)
|
||||
|
|
9
libraries/render-utils/src/GLEscrow.cpp
Normal file
9
libraries/render-utils/src/GLEscrow.cpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/06.
|
||||
// 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 "GLEscrow.h"
|
222
libraries/render-utils/src/GLEscrow.h
Normal file
222
libraries/render-utils/src/GLEscrow.h
Normal file
|
@ -0,0 +1,222 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/06.
|
||||
// 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_GLEscrow_h
|
||||
#define hifi_GLEscrow_h
|
||||
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
#include <GL/glew.h>
|
||||
#include <mutex>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
// The GLEscrow class provides a simple mechanism for producer GL contexts to provide
|
||||
// content to a consumer where the consumer is assumed to be connected to a display and
|
||||
// therefore must never be blocked.
|
||||
//
|
||||
// So we need to accomplish a few things.
|
||||
//
|
||||
// First the producer context needs to be able to supply content to the primary thread
|
||||
// in such a way that the consumer only gets it when it's actually valid for reading
|
||||
// (meaning that the async writing operations have been completed)
|
||||
//
|
||||
// Second, the client thread should be able to release the resource when it's finished
|
||||
// using it (but again the reading of the resource is likely asyncronous)
|
||||
//
|
||||
// Finally, blocking operations need to be minimal, and any potentially blocking operations
|
||||
// that can't be avoided need to be pushed to the submission context to avoid impacting
|
||||
// the framerate of the consumer
|
||||
//
|
||||
// This class acts as a kind of border guard and holding pen between the two contexts
|
||||
// to hold resources which the CPU is no longer using, but which might still be
|
||||
// in use by the GPU. Fence sync objects are used to moderate the actual release of
|
||||
// resources in either direction.
|
||||
template <
|
||||
typename T,
|
||||
// Only accept numeric types
|
||||
typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type
|
||||
>
|
||||
class GLEscrow {
|
||||
public:
|
||||
|
||||
struct Item {
|
||||
T _value;
|
||||
GLsync _sync;
|
||||
uint64_t _created;
|
||||
|
||||
Item(T value, GLsync sync) :
|
||||
_value(value), _sync(sync), _created(usecTimestampNow())
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t age() {
|
||||
return usecTimestampNow() - _created;
|
||||
}
|
||||
|
||||
bool signaled() {
|
||||
auto result = glClientWaitSync(_sync, 0, 0);
|
||||
if (GL_TIMEOUT_EXPIRED != result && GL_WAIT_FAILED != result) {
|
||||
return true;
|
||||
}
|
||||
if (age() > (USECS_PER_SECOND / 2)) {
|
||||
qWarning() << "Long unsignaled sync";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
using Mutex = std::mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
using Recycler = std::function<void(T t)>;
|
||||
// deque gives us random access, double ended push & pop and size, all in constant time
|
||||
using Deque = std::deque<Item>;
|
||||
using List = std::forward_list<Item>;
|
||||
|
||||
void setRecycler(Recycler recycler) {
|
||||
_recycler = recycler;
|
||||
}
|
||||
|
||||
// Submit a new resource from the producer context
|
||||
// returns the number of prior submissions that were
|
||||
// never consumed before becoming available.
|
||||
// producers should self-limit if they start producing more
|
||||
// work than is being consumed;
|
||||
size_t submit(T t, GLsync writeSync = 0) {
|
||||
if (!writeSync) {
|
||||
// FIXME should the release and submit actually force the creation of a fence?
|
||||
writeSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
}
|
||||
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
_submits.push_back(Item(t, writeSync));
|
||||
}
|
||||
|
||||
return cleanTrash();
|
||||
}
|
||||
|
||||
// Returns the next available resource provided by the submitter,
|
||||
// or if none is available (which could mean either the submission
|
||||
// list is empty or that the first item on the list isn't yet signaled
|
||||
T fetch() {
|
||||
T result{0};
|
||||
// On the one hand using try_lock() reduces the chance of blocking the consumer thread,
|
||||
// but if the produce thread is going fast enough, it could effectively
|
||||
// starve the consumer out of ever actually getting resources.
|
||||
if (_mutex.try_lock()) {
|
||||
if (signaled(_submits, 0)) {
|
||||
result = _submits.at(0)._value;
|
||||
_submits.pop_front();
|
||||
}
|
||||
_mutex.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// If fetch returns a non-zero value, it's the responsibility of the
|
||||
// client to release it at some point
|
||||
void release(T t, GLsync readSync = 0) {
|
||||
if (!readSync) {
|
||||
// FIXME should the release and submit actually force the creation of a fence?
|
||||
readSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
}
|
||||
|
||||
Lock lock(_mutex);
|
||||
_releases.push_back(Item(t, readSync));
|
||||
}
|
||||
|
||||
private:
|
||||
size_t cleanTrash() {
|
||||
size_t wastedWork{ 0 };
|
||||
List trash;
|
||||
{
|
||||
// We only ever need one ready item available in the list, so if the
|
||||
// second item is signaled (implying the first is as well, remove the first
|
||||
// item. Iterate until the SECOND item in the list is not in the ready state
|
||||
// The signaled function takes care of checking against the deque size
|
||||
while (signaled(_submits, 1)) {
|
||||
pop(_submits);
|
||||
++wastedWork;
|
||||
}
|
||||
|
||||
// Stuff in the release queue can be cleared out as soon as it's signaled
|
||||
while (signaled(_releases, 0)) {
|
||||
pop(_releases);
|
||||
}
|
||||
|
||||
trash.swap(_trash);
|
||||
}
|
||||
|
||||
// FIXME maybe doing a timing on the deleters and warn if it's taking excessive time?
|
||||
// although we are out of the lock, so it shouldn't be blocking anything
|
||||
std::for_each(trash.begin(), trash.end(), [&](typename List::const_reference item) {
|
||||
if (item._value) {
|
||||
_recycler(item._value);
|
||||
}
|
||||
if (item._sync) {
|
||||
glDeleteSync(item._sync);
|
||||
}
|
||||
});
|
||||
return wastedWork;
|
||||
}
|
||||
|
||||
// May be called on any thread, but must be inside a locked section
|
||||
void pop(Deque& deque) {
|
||||
auto& item = deque.front();
|
||||
_trash.push_front(item);
|
||||
deque.pop_front();
|
||||
}
|
||||
|
||||
// May be called on any thread, but must be inside a locked section
|
||||
bool signaled(Deque& deque, size_t i) {
|
||||
if (i >= deque.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& item = deque.at(i);
|
||||
// If there's no sync object, either it's not required or it's already been found to be signaled
|
||||
if (!item._sync) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the sync value using a zero timeout to ensure we don't block
|
||||
// This is critically important as this is the only GL function we'll call
|
||||
// inside the locked sections, so it cannot have any latency
|
||||
if (item.signaled()) {
|
||||
// if the sync is signaled, queue it for deletion
|
||||
_trash.push_front(Item(0, item._sync));
|
||||
// And change the stored value to 0 so we don't check it again
|
||||
item._sync = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Mutex _mutex;
|
||||
Recycler _recycler;
|
||||
// Items coming from the submission / writer context
|
||||
Deque _submits;
|
||||
// Items coming from the client context.
|
||||
Deque _releases;
|
||||
// Items which are no longer in use.
|
||||
List _trash;
|
||||
};
|
||||
|
||||
using GLTextureEscrow = GLEscrow<GLuint>;
|
||||
|
||||
#endif
|
||||
|
|
@ -6,21 +6,34 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "OffscreenQmlSurface.h"
|
||||
#include "OglplusHelpers.h"
|
||||
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QOpenGLDebugLogger>
|
||||
#include <QGLWidget>
|
||||
#include <QWidget>
|
||||
#include <QtQml>
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlComponent>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWindow>
|
||||
#include <QQuickRenderControl>
|
||||
#include <QWaitCondition>
|
||||
#include <QMutex>
|
||||
|
||||
#include "FboCache.h"
|
||||
#include <PerfStat.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "GLEscrow.h"
|
||||
#include "OffscreenGlCanvas.h"
|
||||
#include "AbstractViewStateInterface.h"
|
||||
|
||||
// FIXME move to threaded rendering with Qt 5.5
|
||||
// #define QML_THREADED
|
||||
|
||||
// Time between receiving a request to render the offscreen UI actually triggering
|
||||
// the render. Could possibly be increased depending on the framerate we expect to
|
||||
// achieve.
|
||||
// This has the effect of capping the framerate at 200
|
||||
static const int MIN_TIMER_MS = 5;
|
||||
|
||||
class QMyQuickRenderControl : public QQuickRenderControl {
|
||||
protected:
|
||||
QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{
|
||||
|
@ -35,119 +48,324 @@ protected:
|
|||
|
||||
private:
|
||||
QWindow* _renderWindow{ nullptr };
|
||||
friend class OffscreenQmlRenderer;
|
||||
friend class OffscreenQmlSurface;
|
||||
};
|
||||
#include "AbstractViewStateInterface.h"
|
||||
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||
|
||||
// Time between receiving a request to render the offscreen UI actually triggering
|
||||
// the render. Could possibly be increased depending on the framerate we expect to
|
||||
// achieve.
|
||||
static const int MAX_QML_FRAMERATE = 10;
|
||||
static const int MIN_RENDER_INTERVAL_US = USECS_PER_SECOND / MAX_QML_FRAMERATE;
|
||||
static const int MIN_TIMER_MS = 5;
|
||||
#ifdef QML_THREADED
|
||||
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);
|
||||
static const QEvent::Type UPDATE = QEvent::Type(QEvent::User + 5);
|
||||
#endif
|
||||
|
||||
class OffscreenQmlRenderer : public OffscreenGlCanvas {
|
||||
friend class OffscreenQmlSurface;
|
||||
public:
|
||||
|
||||
OffscreenQmlRenderer(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
||||
OffscreenGlCanvas::create(shareContext);
|
||||
#ifdef QML_THREADED
|
||||
// Qt 5.5
|
||||
// _renderControl->prepareThread(_renderThread);
|
||||
_context->moveToThread(&_thread);
|
||||
moveToThread(&_thread);
|
||||
_thread.setObjectName("QML Thread");
|
||||
_thread.start();
|
||||
post(INIT);
|
||||
#else
|
||||
init();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef QML_THREADED
|
||||
bool event(QEvent *e)
|
||||
{
|
||||
switch (int(e->type())) {
|
||||
case INIT:
|
||||
{
|
||||
QMutexLocker lock(&_mutex);
|
||||
init();
|
||||
}
|
||||
return true;
|
||||
case RENDER:
|
||||
{
|
||||
QMutexLocker lock(&_mutex);
|
||||
render(&lock);
|
||||
}
|
||||
return true;
|
||||
case RESIZE:
|
||||
{
|
||||
QMutexLocker lock(&_mutex);
|
||||
resize();
|
||||
}
|
||||
return true;
|
||||
case STOP:
|
||||
{
|
||||
QMutexLocker lock(&_mutex);
|
||||
cleanup();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return QObject::event(e);
|
||||
}
|
||||
}
|
||||
|
||||
void post(const QEvent::Type& type) {
|
||||
QCoreApplication::postEvent(this, new QEvent(type));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
void setupFbo() {
|
||||
using namespace oglplus;
|
||||
_textures.setSize(_size);
|
||||
_depthStencil.reset(new Renderbuffer());
|
||||
Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil)
|
||||
.Storage(
|
||||
PixelDataInternalFormat::DepthComponent,
|
||||
_size.x, _size.y);
|
||||
|
||||
_fbo.reset(new Framebuffer());
|
||||
_fbo->Bind(Framebuffer::Target::Draw);
|
||||
_fbo->AttachRenderbuffer(Framebuffer::Target::Draw,
|
||||
FramebufferAttachment::Depth, *_depthStencil);
|
||||
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
|
||||
}
|
||||
|
||||
|
||||
OffscreenQmlSurface::OffscreenQmlSurface() :
|
||||
_renderControl(new QMyQuickRenderControl), _fboCache(new FboCache) {
|
||||
|
||||
void init() {
|
||||
_renderControl = new QMyQuickRenderControl();
|
||||
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
|
||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
|
||||
|
||||
// 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);
|
||||
// Weirdness... QQuickWindow NEEDS to be created on the rendering thread, or it will refuse to render
|
||||
// because it retains an internal 'context' object that retains the thread it was created on,
|
||||
// regardless of whether you later move it to another thread.
|
||||
_quickWindow = new QQuickWindow(_renderControl);
|
||||
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
||||
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
||||
|
||||
#ifdef QML_THREADED
|
||||
// However, because we want to use synchronous events with the quickwindow, we need to move it back to the main
|
||||
// thread after it's created.
|
||||
_quickWindow->moveToThread(qApp->thread());
|
||||
#endif
|
||||
|
||||
if (!makeCurrent()) {
|
||||
qWarning("Failed to make context current on render thread");
|
||||
return;
|
||||
}
|
||||
_renderControl->initialize(_context);
|
||||
setupFbo();
|
||||
_escrow.setRecycler([this](GLuint texture){
|
||||
_textures.recycleTexture(texture);
|
||||
});
|
||||
doneCurrent();
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (!makeCurrent()) {
|
||||
qFatal("Failed to make context current on render thread");
|
||||
return;
|
||||
}
|
||||
_renderControl->invalidate();
|
||||
|
||||
_fbo.reset();
|
||||
_depthStencil.reset();
|
||||
_textures.clear();
|
||||
|
||||
doneCurrent();
|
||||
|
||||
#ifdef QML_THREADED
|
||||
_context->moveToThread(QCoreApplication::instance()->thread());
|
||||
_cond.wakeOne();
|
||||
#endif
|
||||
}
|
||||
|
||||
void resize(const QSize& newSize) {
|
||||
// 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();
|
||||
} else {
|
||||
pixelRatio = AbstractViewStateInterface::instance()->getDevicePixelRatio();
|
||||
}
|
||||
|
||||
uvec2 newOffscreenSize = toGlm(newSize * pixelRatio);
|
||||
_textures.setSize(newOffscreenSize);
|
||||
if (newOffscreenSize == _size) {
|
||||
return;
|
||||
}
|
||||
_size = newOffscreenSize;
|
||||
|
||||
// Clear out any fbos with the old size
|
||||
if (!makeCurrent()) {
|
||||
qWarning("Failed to make context current on render thread");
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
|
||||
setupFbo();
|
||||
doneCurrent();
|
||||
}
|
||||
|
||||
void render(QMutexLocker *lock) {
|
||||
if (_surface->_paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!makeCurrent()) {
|
||||
qWarning("Failed to make context current on render thread");
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _size);
|
||||
//Q_ASSERT(toGlm(_quickWindow->geometry().size()) == _textures._size);
|
||||
|
||||
_renderControl->sync();
|
||||
#ifdef QML_THREADED
|
||||
_cond.wakeOne();
|
||||
lock->unlock();
|
||||
#endif
|
||||
|
||||
|
||||
using namespace oglplus;
|
||||
|
||||
_quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y));
|
||||
|
||||
TexturePtr texture = _textures.getNextTexture();
|
||||
_fbo->Bind(Framebuffer::Target::Draw);
|
||||
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
|
||||
_fbo->Complete(Framebuffer::Target::Draw);
|
||||
//Context::Clear().ColorBuffer();
|
||||
{
|
||||
_renderControl->render();
|
||||
// FIXME The web browsers seem to be leaving GL in an error state.
|
||||
// Need a debug context with sync logging to figure out why.
|
||||
// for now just clear the errors
|
||||
glGetError();
|
||||
}
|
||||
// FIXME probably unecessary
|
||||
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
|
||||
_quickWindow->resetOpenGLState();
|
||||
_escrow.submit(GetName(*texture));
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
}
|
||||
|
||||
void aboutToQuit() {
|
||||
#ifdef QML_THREADED
|
||||
QMutexLocker lock(&_quitMutex);
|
||||
_quit = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void stop() {
|
||||
#ifdef QML_THREADED
|
||||
QMutexLocker lock(&_quitMutex);
|
||||
post(STOP);
|
||||
_cond.wait(&_mutex);
|
||||
#else
|
||||
cleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool allowNewFrame(uint8_t fps) {
|
||||
auto minRenderInterval = USECS_PER_SECOND / fps;
|
||||
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
||||
return (lastInterval > minRenderInterval);
|
||||
}
|
||||
|
||||
OffscreenQmlSurface* _surface{ nullptr };
|
||||
QQuickWindow* _quickWindow{ nullptr };
|
||||
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||
|
||||
#ifdef QML_THREADED
|
||||
QThread _thread;
|
||||
QMutex _mutex;
|
||||
QWaitCondition _cond;
|
||||
QMutex _quitMutex;
|
||||
#endif
|
||||
|
||||
bool _quit;
|
||||
FramebufferPtr _fbo;
|
||||
RenderbufferPtr _depthStencil;
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
TextureRecycler _textures;
|
||||
GLTextureEscrow _escrow;
|
||||
};
|
||||
|
||||
OffscreenQmlSurface::OffscreenQmlSurface() {
|
||||
}
|
||||
|
||||
OffscreenQmlSurface::~OffscreenQmlSurface() {
|
||||
// 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;
|
||||
_renderer->stop();
|
||||
|
||||
delete _renderer;
|
||||
delete _qmlComponent;
|
||||
delete _quickWindow;
|
||||
delete _qmlEngine;
|
||||
|
||||
doneCurrent();
|
||||
delete _fboCache;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||
OffscreenGlCanvas::create(shareContext);
|
||||
_renderer = new OffscreenQmlRenderer(this, 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());
|
||||
_qmlEngine->setIncubationController(_renderer->_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(MIN_TIMER_MS);
|
||||
connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::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, &OffscreenQmlSurface::requestRender);
|
||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenQmlSurface::requestUpdate);
|
||||
|
||||
#ifdef DEBUG
|
||||
connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
|
||||
qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
|
||||
});
|
||||
connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
|
||||
qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
|
||||
});
|
||||
#endif
|
||||
_updateTimer.start();
|
||||
|
||||
_qmlComponent = new QQmlComponent(_qmlEngine);
|
||||
// Initialize the render control and our OpenGL resources.
|
||||
makeCurrent();
|
||||
_renderControl->initialize(_context);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::resize(const QSize& newSize) {
|
||||
// Qt bug in 5.4 forces this check of pixel ratio,
|
||||
// even though we're rendering offscreen.
|
||||
qreal pixelRatio = 1.0;
|
||||
#ifdef QML_THREADED
|
||||
QMutexLocker _locker(&(_renderer->_mutex));
|
||||
#endif
|
||||
if (!_renderer || !_renderer->_quickWindow) {
|
||||
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
||||
if (newSize == currentSize) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize);
|
||||
if (_renderControl && _renderControl->_renderWindow) {
|
||||
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
|
||||
} else {
|
||||
pixelRatio = AbstractViewStateInterface::instance()->getDevicePixelRatio();
|
||||
}
|
||||
QSize newOffscreenSize = newSize * pixelRatio;
|
||||
if (newOffscreenSize == _fboCache->getSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear out any fbos with the old size
|
||||
makeCurrent();
|
||||
qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
|
||||
_fboCache->setSize(newSize * pixelRatio);
|
||||
|
||||
if (_quickWindow) {
|
||||
_quickWindow->setGeometry(QRect(QPoint(), newSize));
|
||||
_quickWindow->contentItem()->setSize(newSize);
|
||||
}
|
||||
|
||||
// Update our members
|
||||
if (_rootItem) {
|
||||
_rootItem->setSize(newSize);
|
||||
}
|
||||
|
||||
doneCurrent();
|
||||
#ifdef QML_THREADED
|
||||
_renderer->post(RESIZE);
|
||||
#else
|
||||
_renderer->resize(newSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||
|
@ -173,20 +391,11 @@ QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQm
|
|||
|
||||
void OffscreenQmlSurface::requestUpdate() {
|
||||
_polish = true;
|
||||
requestRender();
|
||||
_render = true;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::requestRender() {
|
||||
if (!_updateTimer.isActive()) {
|
||||
auto now = usecTimestampNow();
|
||||
auto lastInterval = now - _lastRenderTime;
|
||||
if (lastInterval > MIN_RENDER_INTERVAL_US) {
|
||||
_updateTimer.setInterval(MIN_TIMER_MS);
|
||||
} else {
|
||||
_updateTimer.setInterval((MIN_RENDER_INTERVAL_US - lastInterval) / USECS_PER_MSEC);
|
||||
}
|
||||
_updateTimer.start();
|
||||
}
|
||||
_render = true;
|
||||
}
|
||||
|
||||
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
||||
|
@ -240,54 +449,38 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
}
|
||||
// The root item is ready. Associate it with the window.
|
||||
_rootItem = newItem;
|
||||
_rootItem->setParentItem(_quickWindow->contentItem());
|
||||
_rootItem->setSize(_quickWindow->renderTargetSize());
|
||||
_rootItem->setParentItem(_renderer->_quickWindow->contentItem());
|
||||
_rootItem->setSize(_renderer->_quickWindow->renderTargetSize());
|
||||
return _rootItem;
|
||||
}
|
||||
|
||||
|
||||
void OffscreenQmlSurface::updateQuick() {
|
||||
PerformanceTimer perfTimer("qmlUpdate");
|
||||
if (_paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!makeCurrent()) {
|
||||
if (!_renderer || !_renderer->allowNewFrame(_maxFps)) {
|
||||
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();
|
||||
_renderer->_renderControl->polishItems();
|
||||
_polish = false;
|
||||
}
|
||||
|
||||
QOpenGLFramebufferObject* fbo = _fboCache->getReadyFbo();
|
||||
if (_render) {
|
||||
#ifdef QML_THREADED
|
||||
_renderer->post(RENDER);
|
||||
#else
|
||||
_renderer->render(nullptr);
|
||||
#endif
|
||||
_render = false;
|
||||
}
|
||||
|
||||
_quickWindow->setRenderTarget(fbo);
|
||||
fbo->bind();
|
||||
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
_renderControl->render();
|
||||
// FIXME The web browsers seem to be leaving GL in an error state.
|
||||
// Need a debug context with sync logging to figure out why.
|
||||
// for now just clear the errors
|
||||
glGetError();
|
||||
|
||||
_quickWindow->resetOpenGLState();
|
||||
|
||||
QOpenGLFramebufferObject::bindDefault();
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
// Force completion of all the operations before we emit the texture as being ready for use
|
||||
glFinish();
|
||||
|
||||
emit textureUpdated(fbo->texture());
|
||||
GLuint newTexture = _renderer->_escrow.fetch();
|
||||
if (newTexture) {
|
||||
if (_currentTexture) {
|
||||
_renderer->_escrow.release(_currentTexture);
|
||||
}
|
||||
_currentTexture = newTexture;
|
||||
emit textureUpdated(_currentTexture);
|
||||
}
|
||||
}
|
||||
|
||||
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||
|
@ -299,7 +492,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
|
|||
}
|
||||
vec2 offscreenPosition = toGlm(sourcePosition);
|
||||
offscreenPosition /= sourceSize;
|
||||
offscreenPosition *= vec2(toGlm(_quickWindow->size()));
|
||||
offscreenPosition *= vec2(toGlm(_renderer->_quickWindow->size()));
|
||||
return QPointF(offscreenPosition.x, offscreenPosition.y);
|
||||
}
|
||||
|
||||
|
@ -309,7 +502,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
|
|||
//
|
||||
|
||||
bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) {
|
||||
if (_quickWindow == originalDestination) {
|
||||
if (_renderer->_quickWindow == originalDestination) {
|
||||
return false;
|
||||
}
|
||||
// Only intercept events while we're in an active state
|
||||
|
@ -321,7 +514,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 != _quickWindow);
|
||||
Q_ASSERT(recurseTest != _rootItem && recurseTest != _renderer->_quickWindow);
|
||||
recurseTest = recurseTest->parent();
|
||||
}
|
||||
#endif
|
||||
|
@ -330,7 +523,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
switch (event->type()) {
|
||||
case QEvent::Resize: {
|
||||
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
|
||||
QGLWidget* widget = dynamic_cast<QGLWidget*>(originalDestination);
|
||||
QWidget* widget = dynamic_cast<QWidget*>(originalDestination);
|
||||
if (widget) {
|
||||
this->resize(resizeEvent->size());
|
||||
}
|
||||
|
@ -340,7 +533,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease: {
|
||||
event->ignore();
|
||||
if (QCoreApplication::sendEvent(_quickWindow, event)) {
|
||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, event)) {
|
||||
return event->isAccepted();
|
||||
}
|
||||
break;
|
||||
|
@ -353,7 +546,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
wheelEvent->delta(), wheelEvent->buttons(),
|
||||
wheelEvent->modifiers(), wheelEvent->orientation());
|
||||
mappedEvent.ignore();
|
||||
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) {
|
||||
return mappedEvent.isAccepted();
|
||||
}
|
||||
break;
|
||||
|
@ -376,7 +569,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
|
||||
}
|
||||
mappedEvent.ignore();
|
||||
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) {
|
||||
return mappedEvent.isAccepted();
|
||||
}
|
||||
break;
|
||||
|
@ -389,14 +582,6 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
return false;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::lockTexture(int texture) {
|
||||
_fboCache->lockTexture(texture);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::releaseTexture(int texture) {
|
||||
_fboCache->releaseTexture(texture);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::pause() {
|
||||
_paused = true;
|
||||
}
|
||||
|
@ -411,13 +596,13 @@ bool OffscreenQmlSurface::isPaused() const {
|
|||
}
|
||||
|
||||
void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
|
||||
_renderControl->_renderWindow = window;
|
||||
_renderer->_renderControl->_renderWindow = window;
|
||||
}
|
||||
|
||||
QQuickWindow* OffscreenQmlSurface::getWindow() {
|
||||
return _quickWindow;
|
||||
return _renderer->_quickWindow;
|
||||
}
|
||||
|
||||
QSize OffscreenQmlSurface::size() const {
|
||||
return _quickWindow->geometry().size();
|
||||
return _renderer->_quickWindow->geometry().size();
|
||||
}
|
||||
|
|
|
@ -17,18 +17,18 @@
|
|||
#include <GLMHelpers.h>
|
||||
#include <ThreadHelpers.h>
|
||||
|
||||
#include "OffscreenGlCanvas.h"
|
||||
|
||||
class QWindow;
|
||||
class QMyQuickRenderControl;
|
||||
class QOpenGLContext;
|
||||
class QQmlEngine;
|
||||
class QQmlContext;
|
||||
class QQmlComponent;
|
||||
class QQuickWindow;
|
||||
class QQuickItem;
|
||||
class FboCache;
|
||||
|
||||
class OffscreenQmlSurface : public OffscreenGlCanvas {
|
||||
class OffscreenQmlRenderer;
|
||||
|
||||
class OffscreenQmlSurface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -45,6 +45,7 @@ public:
|
|||
return load(QUrl(qmlSourceFile), f);
|
||||
}
|
||||
|
||||
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
|
||||
// Optional values for event handling
|
||||
void setProxyWindow(QWindow* window);
|
||||
void setMouseTranslator(MouseTranslator mouseTranslator) {
|
||||
|
@ -67,8 +68,6 @@ signals:
|
|||
public slots:
|
||||
void requestUpdate();
|
||||
void requestRender();
|
||||
void lockTexture(int texture);
|
||||
void releaseTexture(int texture);
|
||||
|
||||
private:
|
||||
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
||||
|
@ -77,20 +76,20 @@ private:
|
|||
private slots:
|
||||
void updateQuick();
|
||||
|
||||
protected:
|
||||
QQuickWindow* _quickWindow{ nullptr };
|
||||
|
||||
private:
|
||||
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||
friend class OffscreenQmlRenderer;
|
||||
OffscreenQmlRenderer* _renderer{ nullptr };
|
||||
QQmlEngine* _qmlEngine{ nullptr };
|
||||
QQmlComponent* _qmlComponent{ nullptr };
|
||||
QQuickItem* _rootItem{ nullptr };
|
||||
QTimer _updateTimer;
|
||||
FboCache* _fboCache;
|
||||
quint64 _lastRenderTime{ 0 };
|
||||
uint32_t _currentTexture{ 0 };
|
||||
bool _render{ false };
|
||||
bool _polish{ true };
|
||||
bool _paused{ true };
|
||||
uint8_t _maxFps{ 60 };
|
||||
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } };
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
#include "OglplusHelpers.h"
|
||||
#include <QSharedPointer>
|
||||
#include <set>
|
||||
|
||||
using namespace oglplus;
|
||||
using namespace oglplus::shapes;
|
||||
|
@ -317,3 +318,73 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i
|
|||
new shapes::ShapeWrapper({ "Position", "TexCoord" }, SphereSection(fov, aspect, slices, stacks), *program)
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
TexturePtr TextureRecycler::getNextTexture() {
|
||||
using namespace oglplus;
|
||||
if (_readyTextures.empty()) {
|
||||
TexturePtr newTexture(new Texture());
|
||||
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
|
||||
.MinFilter(TextureMinFilter::Linear)
|
||||
.MagFilter(TextureMagFilter::Linear)
|
||||
.WrapS(TextureWrap::ClampToEdge)
|
||||
.WrapT(TextureWrap::ClampToEdge)
|
||||
.Image2D(
|
||||
0, PixelDataInternalFormat::RGBA8,
|
||||
_size.x, _size.y,
|
||||
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr
|
||||
);
|
||||
GLuint texId = GetName(*newTexture);
|
||||
_allTextures[texId] = TexInfo{ newTexture, _size };
|
||||
_readyTextures.push(newTexture);
|
||||
}
|
||||
|
||||
TexturePtr result = _readyTextures.front();
|
||||
_readyTextures.pop();
|
||||
|
||||
GLuint texId = GetName(*result);
|
||||
auto& item = _allTextures[texId];
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
// FIXME support oglplus on all platforms
|
||||
// For now it's a convenient helper for Windows
|
||||
|
||||
#include <queue>
|
||||
#include <map>
|
||||
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "GLMHelpers.h"
|
||||
|
@ -33,6 +37,8 @@
|
|||
#include "NumericalConstants.h"
|
||||
|
||||
using FramebufferPtr = std::shared_ptr<oglplus::Framebuffer>;
|
||||
using RenderbufferPtr = std::shared_ptr<oglplus::Renderbuffer>;
|
||||
using TexturePtr = std::shared_ptr<oglplus::Texture>;
|
||||
using ShapeWrapperPtr = std::shared_ptr<oglplus::shapes::ShapeWrapper>;
|
||||
using BufferPtr = std::shared_ptr<oglplus::Buffer>;
|
||||
using VertexArrayPtr = std::shared_ptr<oglplus::VertexArray>;
|
||||
|
@ -151,3 +157,29 @@ protected:
|
|||
};
|
||||
|
||||
using BasicFramebufferWrapperPtr = std::shared_ptr<BasicFramebufferWrapper>;
|
||||
|
||||
class TextureRecycler {
|
||||
public:
|
||||
void setSize(const uvec2& size);
|
||||
void clear();
|
||||
TexturePtr getNextTexture();
|
||||
void recycleTexture(GLuint texture);
|
||||
|
||||
private:
|
||||
|
||||
struct TexInfo {
|
||||
TexturePtr _tex;
|
||||
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 };
|
||||
};
|
||||
|
|
|
@ -38,8 +38,8 @@ public:
|
|||
// so I think it's OK for the time being.
|
||||
bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {
|
||||
Q_ASSERT(event->type() == QEvent::ShortcutOverride);
|
||||
QObject* focusObject = _quickWindow->focusObject();
|
||||
if (focusObject != _quickWindow && focusObject != getRootItem()) {
|
||||
QObject* focusObject = getWindow()->focusObject();
|
||||
if (focusObject != getWindow() && focusObject != getRootItem()) {
|
||||
//qDebug() << "Swallowed shortcut " << static_cast<QKeyEvent*>(event)->key();
|
||||
event->accept();
|
||||
return true;
|
||||
|
|
|
@ -186,13 +186,7 @@ public:
|
|||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->create(_context);
|
||||
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
|
||||
offscreenUi->lockTexture(textureId);
|
||||
assert(!glGetError());
|
||||
GLuint oldTexture = testQmlTexture;
|
||||
testQmlTexture = textureId;
|
||||
if (oldTexture) {
|
||||
offscreenUi->releaseTexture(oldTexture);
|
||||
}
|
||||
});
|
||||
|
||||
makeCurrent();
|
||||
|
|
Loading…
Reference in a new issue