mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 21:15:07 +02:00
Merge branch 'master' into 21055
Conflicts: libraries/gl/src/gl/OffscreenQmlSurface.cpp libraries/gl/src/gl/OffscreenQmlSurface.h
This commit is contained in:
commit
a1458a26fd
22 changed files with 792 additions and 516 deletions
|
@ -61,6 +61,7 @@ Agent::Agent(ReceivedMessage& message) :
|
||||||
DependencyManager::set<recording::Deck>();
|
DependencyManager::set<recording::Deck>();
|
||||||
DependencyManager::set<recording::Recorder>();
|
DependencyManager::set<recording::Recorder>();
|
||||||
DependencyManager::set<RecordingScriptingInterface>();
|
DependencyManager::set<RecordingScriptingInterface>();
|
||||||
|
DependencyManager::set<ScriptCache>();
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,13 @@
|
||||||
#include <AbstractUriHandler.h>
|
#include <AbstractUriHandler.h>
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
|
||||||
#include "OffscreenGLCanvas.h"
|
#include "OffscreenGLCanvas.h"
|
||||||
#include "GLHelpers.h"
|
#include "GLHelpers.h"
|
||||||
#include "GLLogging.h"
|
#include "GLLogging.h"
|
||||||
|
#include "TextureRecycler.h"
|
||||||
|
#include "Context.h"
|
||||||
|
|
||||||
QString fixupHifiUrl(const QString& urlString) {
|
QString fixupHifiUrl(const QString& urlString) {
|
||||||
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
||||||
|
@ -114,257 +116,8 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) {
|
||||||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||||
|
|
||||||
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
|
void OffscreenQmlSurface::setupFbo() {
|
||||||
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
|
_canvas->makeCurrent();
|
||||||
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() {
|
|
||||||
_textures.setSize(_size);
|
_textures.setSize(_size);
|
||||||
if (_depthStencil) {
|
if (_depthStencil) {
|
||||||
glDeleteRenderbuffers(1, &_depthStencil);
|
glDeleteRenderbuffers(1, &_depthStencil);
|
||||||
|
@ -382,41 +135,12 @@ void OffscreenQmlRenderThread::setupFbo() {
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
_canvas->doneCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject OffscreenQmlRenderThread::getGLContextData() {
|
void OffscreenQmlSurface::cleanup() {
|
||||||
_glMutex.lock();
|
_canvas->makeCurrent();
|
||||||
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() {
|
|
||||||
_renderControl->invalidate();
|
_renderControl->invalidate();
|
||||||
|
|
||||||
if (_depthStencil) {
|
if (_depthStencil) {
|
||||||
glDeleteRenderbuffers(1, &_depthStencil);
|
glDeleteRenderbuffers(1, &_depthStencil);
|
||||||
_depthStencil = 0;
|
_depthStencil = 0;
|
||||||
|
@ -427,65 +151,17 @@ void OffscreenQmlRenderThread::cleanup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_textures.clear();
|
_textures.clear();
|
||||||
|
_canvas->doneCurrent();
|
||||||
_canvas.doneCurrent();
|
|
||||||
_canvas.getContextObject()->moveToThread(QCoreApplication::instance()->thread());
|
|
||||||
|
|
||||||
_quit = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlRenderThread::resize() {
|
void OffscreenQmlSurface::render() {
|
||||||
// Lock _newSize changes
|
if (_paused) {
|
||||||
{
|
|
||||||
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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_rendering = true;
|
_canvas->makeCurrent();
|
||||||
Finally unmarkRenderingFlag([this] {
|
|
||||||
_rendering = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&_mutex);
|
|
||||||
_renderControl->sync();
|
|
||||||
releaseMainThread.trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_renderControl->sync();
|
||||||
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
||||||
|
|
||||||
// Clear out any pending textures to be returned
|
// Clear out any pending textures to be returned
|
||||||
|
@ -507,70 +183,61 @@ void OffscreenQmlRenderThread::render() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
GLuint texture = _textures.getNextTexture();
|
||||||
GLuint texture = _textures.getNextTexture();
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
PROFILE_RANGE("qml_render->rendercontrol")
|
||||||
PROFILE_RANGE("qml_render->rendercontrol")
|
_renderControl->render();
|
||||||
_renderControl->render();
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||||
// If the most recent texture was unused, we can directly recycle it
|
// If the most recent texture was unused, we can directly recycle it
|
||||||
if (_latestTextureFence) {
|
if (_latestTextureAndFence.first) {
|
||||||
}
|
_textures.recycleTexture(_latestTextureAndFence.first);
|
||||||
if (_latestTexture) {
|
glDeleteSync(static_cast<GLsync>(_latestTextureAndFence.second));
|
||||||
_textures.recycleTexture(_latestTexture);
|
_latestTextureAndFence = { 0, 0 };
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_quickWindow->resetOpenGLState();
|
_latestTextureAndFence = { texture, glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) };
|
||||||
_lastRenderTime = usecTimestampNow();
|
// Fence will be used in another thread / context, so a flush is required
|
||||||
} catch (std::runtime_error& error) {
|
glFlush();
|
||||||
qWarning() << "Failed to render QML: " << error.what();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_quickWindow->resetOpenGLState();
|
||||||
|
_lastRenderTime = usecTimestampNow();
|
||||||
|
_canvas->doneCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OffscreenQmlRenderThread::fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) {
|
||||||
textureAndFence = { 0, 0 };
|
textureAndFence = { 0, 0 };
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||||
if (0 == _latestTexture) {
|
if (0 == _latestTextureAndFence.first) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure writes to the latest texture are complete before before returning it for reading
|
// Ensure writes to the latest texture are complete before before returning it for reading
|
||||||
Q_ASSERT(0 != _latestTextureFence);
|
textureAndFence = _latestTextureAndFence;
|
||||||
textureAndFence = { _latestTexture, _latestTextureFence };
|
_latestTextureAndFence = { 0, 0 };
|
||||||
_latestTextureFence = 0;
|
|
||||||
_latestTexture = 0;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlRenderThread::releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& textureAndFence) {
|
||||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||||
_returnedTextures.push_back(textureAndFence);
|
_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
|
// 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
|
// i.e. don't render faster than the consumer context, since it wastes
|
||||||
// GPU cycles on producing output that will never be seen
|
// GPU cycles on producing output that will never be seen
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||||
if (0 != _latestTexture) {
|
if (0 != _latestTextureAndFence.first) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -588,33 +255,46 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
|
||||||
QObject::disconnect(&_updateTimer);
|
QObject::disconnect(&_updateTimer);
|
||||||
QObject::disconnect(qApp);
|
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;
|
cleanup();
|
||||||
delete _renderer;
|
|
||||||
delete _qmlComponent;
|
_canvas->deleteLater();
|
||||||
delete _qmlEngine;
|
_rootItem->deleteLater();
|
||||||
|
_qmlComponent->deleteLater();
|
||||||
|
_qmlEngine->deleteLater();
|
||||||
|
_quickWindow->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlSurface::onAboutToQuit() {
|
void OffscreenQmlSurface::onAboutToQuit() {
|
||||||
|
_paused = true;
|
||||||
QObject::disconnect(&_updateTimer);
|
QObject::disconnect(&_updateTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||||
qCDebug(glLogging) << "Building QML surface";
|
qCDebug(glLogging) << "Building QML surface";
|
||||||
|
|
||||||
_renderer = new OffscreenQmlRenderThread(this, shareContext);
|
_renderControl = new QMyQuickRenderControl();
|
||||||
_renderer->moveToThread(_renderer);
|
|
||||||
_renderer->setObjectName("QML Renderer Thread");
|
|
||||||
_renderer->start();
|
|
||||||
|
|
||||||
_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.
|
// Create a QML engine.
|
||||||
_qmlEngine = new QQmlEngine;
|
_qmlEngine = new QQmlEngine;
|
||||||
|
@ -625,13 +305,26 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||||
importList.insert(importList.begin(), PathUtils::resourcesPath());
|
importList.insert(importList.begin(), PathUtils::resourcesPath());
|
||||||
_qmlEngine->setImportPathList(importList);
|
_qmlEngine->setImportPathList(importList);
|
||||||
if (!_qmlEngine->incubationController()) {
|
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()));
|
_qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
|
||||||
_qmlComponent = new QQmlComponent(_qmlEngine);
|
_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,
|
// 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.
|
// a timer with a small interval is used to get better performance.
|
||||||
QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
|
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) {
|
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
||||||
|
|
||||||
if (!_renderer || !_renderer->_quickWindow) {
|
if (!_quickWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,7 +355,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
||||||
std::max(static_cast<int>(scale * newSize.height()), 10));
|
std::max(static_cast<int>(scale * newSize.height()), 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
QSize currentSize = _quickWindow->geometry().size();
|
||||||
if (newSize == currentSize && !forceResize) {
|
if (newSize == currentSize && !forceResize) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -673,12 +366,26 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
||||||
_rootItem->setSize(newSize);
|
_rootItem->setSize(newSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// Update our members
|
||||||
QMutexLocker locker(&(_renderer->_mutex));
|
_quickWindow->setGeometry(QRect(QPoint(), newSize));
|
||||||
_renderer->_newSize = 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() {
|
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||||
|
@ -710,15 +417,6 @@ void OffscreenQmlSurface::clearCache() {
|
||||||
getRootContext()->engine()->clearComponentCache();
|
getRootContext()->engine()->clearComponentCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlSurface::requestUpdate() {
|
|
||||||
_polish = true;
|
|
||||||
_render = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OffscreenQmlSurface::requestRender() {
|
|
||||||
_render = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
||||||
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
||||||
if (_qmlComponent->isError()) {
|
if (_qmlComponent->isError()) {
|
||||||
|
@ -786,8 +484,8 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
||||||
}
|
}
|
||||||
// The root item is ready. Associate it with the window.
|
// The root item is ready. Associate it with the window.
|
||||||
_rootItem = newItem;
|
_rootItem = newItem;
|
||||||
_rootItem->setParentItem(_renderer->_quickWindow->contentItem());
|
_rootItem->setParentItem(_quickWindow->contentItem());
|
||||||
_rootItem->setSize(_renderer->_quickWindow->renderTargetSize());
|
_rootItem->setSize(_quickWindow->renderTargetSize());
|
||||||
return _rootItem;
|
return _rootItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,34 +495,22 @@ void OffscreenQmlSurface::updateQuick() {
|
||||||
// b) already rendering a frame
|
// b) already rendering a frame
|
||||||
// c) rendering too fast
|
// c) rendering too fast
|
||||||
// then skip this
|
// then skip this
|
||||||
if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) {
|
if (!allowNewFrame(_maxFps)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_polish) {
|
if (_polish) {
|
||||||
_renderer->_renderControl->polishItems();
|
_renderControl->polishItems();
|
||||||
_polish = false;
|
_polish = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_render) {
|
if (_render) {
|
||||||
PROFILE_RANGE(__FUNCTION__);
|
PROFILE_RANGE(__FUNCTION__);
|
||||||
// Lock the GUI size while syncing
|
render();
|
||||||
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 = false;
|
_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) {
|
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||||
vec2 sourceSize;
|
vec2 sourceSize;
|
||||||
if (dynamic_cast<QWidget*>(sourceObject)) {
|
if (dynamic_cast<QWidget*>(sourceObject)) {
|
||||||
|
@ -834,7 +520,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
|
||||||
}
|
}
|
||||||
vec2 offscreenPosition = toGlm(sourcePosition);
|
vec2 offscreenPosition = toGlm(sourcePosition);
|
||||||
offscreenPosition /= sourceSize;
|
offscreenPosition /= sourceSize;
|
||||||
offscreenPosition *= vec2(toGlm(_renderer->_quickWindow->size()));
|
offscreenPosition *= vec2(toGlm(_quickWindow->size()));
|
||||||
return QPointF(offscreenPosition.x, offscreenPosition.y);
|
return QPointF(offscreenPosition.x, offscreenPosition.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,7 +534,7 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO
|
||||||
//
|
//
|
||||||
|
|
||||||
bool OffscreenQmlSurface::filterEnabled(QObject* originalDestination, QEvent* event) const {
|
bool OffscreenQmlSurface::filterEnabled(QObject* originalDestination, QEvent* event) const {
|
||||||
if (_renderer->_quickWindow == originalDestination) {
|
if (_quickWindow == originalDestination) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Only intercept events while we're in an active state
|
// Only intercept events while we're in an active state
|
||||||
|
@ -866,7 +552,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
||||||
// Don't intercept our own events, or we enter an infinite recursion
|
// Don't intercept our own events, or we enter an infinite recursion
|
||||||
QObject* recurseTest = originalDestination;
|
QObject* recurseTest = originalDestination;
|
||||||
while (recurseTest) {
|
while (recurseTest) {
|
||||||
Q_ASSERT(recurseTest != _rootItem && recurseTest != _renderer->_quickWindow);
|
Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
|
||||||
recurseTest = recurseTest->parent();
|
recurseTest = recurseTest->parent();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -884,7 +570,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
||||||
case QEvent::KeyPress:
|
case QEvent::KeyPress:
|
||||||
case QEvent::KeyRelease: {
|
case QEvent::KeyRelease: {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, event)) {
|
if (QCoreApplication::sendEvent(_quickWindow, event)) {
|
||||||
return event->isAccepted();
|
return event->isAccepted();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -898,7 +584,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
||||||
wheelEvent->delta(), wheelEvent->buttons(),
|
wheelEvent->delta(), wheelEvent->buttons(),
|
||||||
wheelEvent->modifiers(), wheelEvent->orientation());
|
wheelEvent->modifiers(), wheelEvent->orientation());
|
||||||
mappedEvent.ignore();
|
mappedEvent.ignore();
|
||||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) {
|
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||||
return mappedEvent.isAccepted();
|
return mappedEvent.isAccepted();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -919,7 +605,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
||||||
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
|
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
|
||||||
}
|
}
|
||||||
mappedEvent.ignore();
|
mappedEvent.ignore();
|
||||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) {
|
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||||
return mappedEvent.isAccepted();
|
return mappedEvent.isAccepted();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -938,7 +624,7 @@ void OffscreenQmlSurface::pause() {
|
||||||
|
|
||||||
void OffscreenQmlSurface::resume() {
|
void OffscreenQmlSurface::resume() {
|
||||||
_paused = false;
|
_paused = false;
|
||||||
requestRender();
|
_render = true;
|
||||||
|
|
||||||
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
|
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
|
||||||
getRootContext()->setContextProperty("webEntity", this);
|
getRootContext()->setContextProperty("webEntity", this);
|
||||||
|
@ -950,8 +636,8 @@ bool OffscreenQmlSurface::isPaused() const {
|
||||||
|
|
||||||
void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
|
void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
|
||||||
_proxyWindow = window;
|
_proxyWindow = window;
|
||||||
if (_renderer && _renderer->_renderControl) {
|
if (_renderControl) {
|
||||||
_renderer->_renderControl->_renderWindow = window;
|
_renderControl->_renderWindow = window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -960,11 +646,11 @@ QObject* OffscreenQmlSurface::getEventHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickWindow* OffscreenQmlSurface::getWindow() {
|
QQuickWindow* OffscreenQmlSurface::getWindow() {
|
||||||
return _renderer->_quickWindow;
|
return _quickWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize OffscreenQmlSurface::size() const {
|
QSize OffscreenQmlSurface::size() const {
|
||||||
return _renderer->_quickWindow->geometry().size();
|
return _quickWindow->geometry().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQmlContext* OffscreenQmlSurface::getRootContext() {
|
QQmlContext* OffscreenQmlSurface::getRootContext() {
|
||||||
|
|
|
@ -9,24 +9,27 @@
|
||||||
#ifndef hifi_OffscreenQmlSurface_h
|
#ifndef hifi_OffscreenQmlSurface_h
|
||||||
#define hifi_OffscreenQmlSurface_h
|
#define hifi_OffscreenQmlSurface_h
|
||||||
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
#include <ThreadHelpers.h>
|
#include <ThreadHelpers.h>
|
||||||
|
#include "TextureRecycler.h"
|
||||||
|
|
||||||
class QWindow;
|
class QWindow;
|
||||||
class QMyQuickRenderControl;
|
class QMyQuickRenderControl;
|
||||||
|
class OffscreenGLCanvas;
|
||||||
class QOpenGLContext;
|
class QOpenGLContext;
|
||||||
class QQmlEngine;
|
class QQmlEngine;
|
||||||
class QQmlContext;
|
class QQmlContext;
|
||||||
class QQmlComponent;
|
class QQmlComponent;
|
||||||
class QQuickWindow;
|
class QQuickWindow;
|
||||||
class QQuickItem;
|
class QQuickItem;
|
||||||
class OffscreenQmlRenderThread;
|
|
||||||
|
|
||||||
class OffscreenQmlSurface : public QObject {
|
class OffscreenQmlSurface : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
|
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
|
||||||
|
@ -88,8 +91,6 @@ signals:
|
||||||
void focusTextChanged(bool focusText);
|
void focusTextChanged(bool focusText);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void requestUpdate();
|
|
||||||
void requestRender();
|
|
||||||
void onAboutToQuit();
|
void onAboutToQuit();
|
||||||
void focusDestroyed(QObject *obj);
|
void focusDestroyed(QObject *obj);
|
||||||
|
|
||||||
|
@ -109,24 +110,44 @@ protected:
|
||||||
private:
|
private:
|
||||||
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
||||||
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
|
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
|
||||||
|
void setupFbo();
|
||||||
|
bool allowNewFrame(uint8_t fps);
|
||||||
|
void render();
|
||||||
|
void cleanup();
|
||||||
|
QJsonObject getGLContextData();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateQuick();
|
void updateQuick();
|
||||||
void onFocusObjectChanged(QObject* newFocus);
|
void onFocusObjectChanged(QObject* newFocus);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class OffscreenQmlRenderThread;
|
QQuickWindow* _quickWindow { nullptr };
|
||||||
OffscreenQmlRenderThread* _renderer{ nullptr };
|
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||||
QQmlEngine* _qmlEngine{ nullptr };
|
QQmlEngine* _qmlEngine { nullptr };
|
||||||
QQmlComponent* _qmlComponent{ nullptr };
|
QQmlComponent* _qmlComponent { nullptr };
|
||||||
QQuickItem* _rootItem{ nullptr };
|
QQuickItem* _rootItem { nullptr };
|
||||||
|
OffscreenGLCanvas* _canvas { nullptr };
|
||||||
|
QJsonObject _glData;
|
||||||
|
|
||||||
QTimer _updateTimer;
|
QTimer _updateTimer;
|
||||||
bool _render{ false };
|
uint32_t _fbo { 0 };
|
||||||
bool _polish{ true };
|
uint32_t _depthStencil { 0 };
|
||||||
bool _paused{ true };
|
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 };
|
bool _focusText { false };
|
||||||
uint8_t _maxFps{ 60 };
|
uint8_t _maxFps { 60 };
|
||||||
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
|
MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
|
||||||
QWindow* _proxyWindow { nullptr };
|
QWindow* _proxyWindow { nullptr };
|
||||||
|
|
||||||
QQuickItem* _currentFocusItem { nullptr };
|
QQuickItem* _currentFocusItem { 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
|
|
@ -394,12 +394,14 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
||||||
auto senderNode = node.toStrongRef();
|
auto senderNode = node.toStrongRef();
|
||||||
|
|
||||||
if (!senderNode) {
|
if (!senderNode) {
|
||||||
|
qCWarning(asset_client) << "Got completed asset for node that no longer exists";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have any pending requests for this node
|
// Check if we have any pending requests for this node
|
||||||
auto messageMapIt = _pendingRequests.find(senderNode);
|
auto messageMapIt = _pendingRequests.find(senderNode);
|
||||||
if (messageMapIt == _pendingRequests.end()) {
|
if (messageMapIt == _pendingRequests.end()) {
|
||||||
|
qCWarning(asset_client) << "Got completed asset for a node that doesn't have any pending requests";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,6 +411,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
||||||
// Check if we have this pending request
|
// Check if we have this pending request
|
||||||
auto requestIt = messageCallbackMap.find(messageID);
|
auto requestIt = messageCallbackMap.find(messageID);
|
||||||
if (requestIt == messageCallbackMap.end()) {
|
if (requestIt == messageCallbackMap.end()) {
|
||||||
|
qCWarning(asset_client) << "Got completed asset for a request that doesn't exist";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +419,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
||||||
auto& message = callbacks.message;
|
auto& message = callbacks.message;
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
|
qCWarning(asset_client) << "Got completed asset for a message that doesn't exist";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,9 +107,11 @@ void AssetRequest::start() {
|
||||||
|
|
||||||
auto assetClient = DependencyManager::get<AssetClient>();
|
auto assetClient = DependencyManager::get<AssetClient>();
|
||||||
auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
|
auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
|
||||||
|
auto hash = _hash;
|
||||||
_assetRequestID = assetClient->getAsset(_hash, start, end,
|
_assetRequestID = assetClient->getAsset(_hash, start, end,
|
||||||
[this, that, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
|
[this, that, hash, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
|
||||||
if (!that) {
|
if (!that) {
|
||||||
|
qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
|
||||||
// If the request is dead, return
|
// If the request is dead, return
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ public:
|
||||||
const State& getState() const { return _state; }
|
const State& getState() const { return _state; }
|
||||||
const Error& getError() const { return _error; }
|
const Error& getError() const { return _error; }
|
||||||
QUrl getUrl() const { return ::getATPUrl(_hash); }
|
QUrl getUrl() const { return ::getATPUrl(_hash); }
|
||||||
|
QString getHash() const { return _hash; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished(AssetRequest* thisRequest);
|
void finished(AssetRequest* thisRequest);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "AssetClient.h"
|
#include "AssetClient.h"
|
||||||
#include "AssetUtils.h"
|
#include "AssetUtils.h"
|
||||||
#include "MappingRequest.h"
|
#include "MappingRequest.h"
|
||||||
|
#include <QtCore/qloggingcategory.h>
|
||||||
|
|
||||||
AssetResourceRequest::~AssetResourceRequest() {
|
AssetResourceRequest::~AssetResourceRequest() {
|
||||||
if (_assetMappingRequest) {
|
if (_assetMappingRequest) {
|
||||||
|
@ -23,6 +24,10 @@ AssetResourceRequest::~AssetResourceRequest() {
|
||||||
if (_assetRequest) {
|
if (_assetRequest) {
|
||||||
_assetRequest->deleteLater();
|
_assetRequest->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_sendTimer) {
|
||||||
|
cleanupTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AssetResourceRequest::urlIsAssetHash() const {
|
bool AssetResourceRequest::urlIsAssetHash() const {
|
||||||
|
@ -32,6 +37,24 @@ bool AssetResourceRequest::urlIsAssetHash() const {
|
||||||
return hashRegex.exactMatch(_url.toString());
|
return hashRegex.exactMatch(_url.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssetResourceRequest::setupTimer() {
|
||||||
|
Q_ASSERT(!_sendTimer);
|
||||||
|
static const int TIMEOUT_MS = 2000;
|
||||||
|
|
||||||
|
_sendTimer = new QTimer(this);
|
||||||
|
connect(_sendTimer, &QTimer::timeout, this, &AssetResourceRequest::onTimeout);
|
||||||
|
|
||||||
|
_sendTimer->setSingleShot(true);
|
||||||
|
_sendTimer->start(TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetResourceRequest::cleanupTimer() {
|
||||||
|
Q_ASSERT(_sendTimer);
|
||||||
|
disconnect(_sendTimer, 0, this, 0);
|
||||||
|
_sendTimer->deleteLater();
|
||||||
|
_sendTimer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void AssetResourceRequest::doSend() {
|
void AssetResourceRequest::doSend() {
|
||||||
// We'll either have a hash or an ATP path to a file (that maps to a hash)
|
// We'll either have a hash or an ATP path to a file (that maps to a hash)
|
||||||
if (urlIsAssetHash()) {
|
if (urlIsAssetHash()) {
|
||||||
|
@ -58,6 +81,8 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
||||||
Q_ASSERT(_state == InProgress);
|
Q_ASSERT(_state == InProgress);
|
||||||
Q_ASSERT(request == _assetMappingRequest);
|
Q_ASSERT(request == _assetMappingRequest);
|
||||||
|
|
||||||
|
cleanupTimer();
|
||||||
|
|
||||||
switch (request->getError()) {
|
switch (request->getError()) {
|
||||||
case MappingRequest::NoError:
|
case MappingRequest::NoError:
|
||||||
// we have no error, we should have a resulting hash - use that to send of a request for that asset
|
// we have no error, we should have a resulting hash - use that to send of a request for that asset
|
||||||
|
@ -93,6 +118,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
||||||
_assetMappingRequest = nullptr;
|
_assetMappingRequest = nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setupTimer();
|
||||||
_assetMappingRequest->start();
|
_assetMappingRequest->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,11 +128,13 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
||||||
auto assetClient = DependencyManager::get<AssetClient>();
|
auto assetClient = DependencyManager::get<AssetClient>();
|
||||||
_assetRequest = assetClient->createRequest(hash);
|
_assetRequest = assetClient->createRequest(hash);
|
||||||
|
|
||||||
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::progress);
|
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::onDownloadProgress);
|
||||||
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
|
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
|
||||||
Q_ASSERT(_state == InProgress);
|
Q_ASSERT(_state == InProgress);
|
||||||
Q_ASSERT(req == _assetRequest);
|
Q_ASSERT(req == _assetRequest);
|
||||||
Q_ASSERT(req->getState() == AssetRequest::Finished);
|
Q_ASSERT(req->getState() == AssetRequest::Finished);
|
||||||
|
|
||||||
|
cleanupTimer();
|
||||||
|
|
||||||
switch (req->getError()) {
|
switch (req->getError()) {
|
||||||
case AssetRequest::Error::NoError:
|
case AssetRequest::Error::NoError:
|
||||||
|
@ -134,9 +162,35 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
||||||
_assetRequest = nullptr;
|
_assetRequest = nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setupTimer();
|
||||||
_assetRequest->start();
|
_assetRequest->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||||
|
Q_ASSERT(_state == InProgress);
|
||||||
|
|
||||||
|
// We've received data, so reset the timer
|
||||||
|
_sendTimer->start();
|
||||||
|
|
||||||
emit progress(bytesReceived, bytesTotal);
|
emit progress(bytesReceived, bytesTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssetResourceRequest::onTimeout() {
|
||||||
|
if (_state == InProgress) {
|
||||||
|
qWarning() << "Asset request timed out: " << _url;
|
||||||
|
if (_assetRequest) {
|
||||||
|
disconnect(_assetRequest, 0, this, 0);
|
||||||
|
_assetRequest->deleteLater();
|
||||||
|
_assetRequest = nullptr;
|
||||||
|
}
|
||||||
|
if (_assetMappingRequest) {
|
||||||
|
disconnect(_assetMappingRequest, 0, this, 0);
|
||||||
|
_assetMappingRequest->deleteLater();
|
||||||
|
_assetMappingRequest = nullptr;
|
||||||
|
}
|
||||||
|
_result = Timeout;
|
||||||
|
_state = Finished;
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
cleanupTimer();
|
||||||
|
}
|
||||||
|
|
|
@ -28,13 +28,19 @@ protected:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
|
void onTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setupTimer();
|
||||||
|
void cleanupTimer();
|
||||||
|
|
||||||
bool urlIsAssetHash() const;
|
bool urlIsAssetHash() const;
|
||||||
|
|
||||||
void requestMappingForPath(const AssetPath& path);
|
void requestMappingForPath(const AssetPath& path);
|
||||||
void requestHash(const AssetHash& hash);
|
void requestHash(const AssetHash& hash);
|
||||||
|
|
||||||
|
QTimer* _sendTimer { nullptr };
|
||||||
|
|
||||||
GetMappingRequest* _assetMappingRequest { nullptr };
|
GetMappingRequest* _assetMappingRequest { nullptr };
|
||||||
AssetRequest* _assetRequest { nullptr };
|
AssetRequest* _assetRequest { nullptr };
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
|
AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
|
||||||
_engine(engine)
|
_engine(engine)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
|
void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
|
||||||
|
|
|
@ -13,12 +13,14 @@
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QPointer>
|
||||||
#include "ScriptEngineLogging.h"
|
#include "ScriptEngineLogging.h"
|
||||||
#include "BatchLoader.h"
|
#include "BatchLoader.h"
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include "ResourceManager.h"
|
#include "ResourceManager.h"
|
||||||
#include "ScriptEngines.h"
|
#include "ScriptEngines.h"
|
||||||
|
#include "ScriptCache.h"
|
||||||
|
|
||||||
BatchLoader::BatchLoader(const QList<QUrl>& urls)
|
BatchLoader::BatchLoader(const QList<QUrl>& urls)
|
||||||
: QObject(),
|
: QObject(),
|
||||||
|
@ -38,30 +40,25 @@ void BatchLoader::start() {
|
||||||
|
|
||||||
for (const auto& rawURL : _urls) {
|
for (const auto& rawURL : _urls) {
|
||||||
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
|
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
|
||||||
auto request = ResourceManager::createResourceRequest(this, url);
|
|
||||||
if (!request) {
|
qCDebug(scriptengine) << "Loading script at " << url;
|
||||||
_data.insert(url, QString());
|
|
||||||
qCDebug(scriptengine) << "Could not load" << url;
|
QPointer<BatchLoader> self = this;
|
||||||
continue;
|
DependencyManager::get<ScriptCache>()->getScriptContents(url.toString(), [this, self](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||||
}
|
if (!self) {
|
||||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
return;
|
||||||
if (request->getResult() == ResourceRequest::Success) {
|
}
|
||||||
_data.insert(url, request->getData());
|
if (isURL && success) {
|
||||||
|
_data.insert(url, contents);
|
||||||
|
qCDebug(scriptengine) << "Loaded: " << url;
|
||||||
} else {
|
} else {
|
||||||
_data.insert(url, QString());
|
_data.insert(url, QString());
|
||||||
qCDebug(scriptengine) << "Could not load" << url;
|
qCDebug(scriptengine) << "Could not load" << url;
|
||||||
}
|
}
|
||||||
request->deleteLater();
|
|
||||||
checkFinished();
|
checkFinished();
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
// If we end up being destroyed before the reply finishes, clean it up
|
|
||||||
connect(this, &QObject::destroyed, request, &QObject::deleteLater);
|
|
||||||
|
|
||||||
qCDebug(scriptengine) << "Loading script at " << url;
|
|
||||||
|
|
||||||
request->send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkFinished();
|
checkFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#include "ScriptEngines.h"
|
#include "ScriptEngines.h"
|
||||||
#include "ScriptEngineLogging.h"
|
#include "ScriptEngineLogging.h"
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
|
||||||
ScriptCache::ScriptCache(QObject* parent) {
|
ScriptCache::ScriptCache(QObject* parent) {
|
||||||
// nothing to do here...
|
// nothing to do here...
|
||||||
|
@ -133,8 +134,11 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
||||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||||
contentAvailable(url.toString(), scriptContent, true, true);
|
contentAvailable(url.toString(), scriptContent, true, true);
|
||||||
} else {
|
} else {
|
||||||
bool alreadyWaiting = _contentCallbacks.contains(url);
|
auto& scriptRequest = _activeScriptRequests[url];
|
||||||
_contentCallbacks.insert(url, contentAvailable);
|
|
||||||
|
bool alreadyWaiting = scriptRequest.scriptUsers.size() > 0;
|
||||||
|
scriptRequest.scriptUsers.push_back(contentAvailable);
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
if (alreadyWaiting) {
|
if (alreadyWaiting) {
|
||||||
|
@ -152,6 +156,9 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const int MAX_RETRIES = 5;
|
||||||
|
static int START_DELAY_BETWEEN_RETRIES = 200;
|
||||||
|
|
||||||
void ScriptCache::scriptContentAvailable() {
|
void ScriptCache::scriptContentAvailable() {
|
||||||
#ifdef THREAD_DEBUGGING
|
#ifdef THREAD_DEBUGGING
|
||||||
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||||
|
@ -160,29 +167,59 @@ void ScriptCache::scriptContentAvailable() {
|
||||||
QUrl url = req->getUrl();
|
QUrl url = req->getUrl();
|
||||||
|
|
||||||
QString scriptContent;
|
QString scriptContent;
|
||||||
QList<contentAvailableCallback> allCallbacks;
|
std::vector<contentAvailableCallback> allCallbacks;
|
||||||
|
bool finished { false };
|
||||||
bool success { false };
|
bool success { false };
|
||||||
|
|
||||||
{
|
{
|
||||||
Lock lock(_containerLock);
|
|
||||||
allCallbacks = _contentCallbacks.values(url);
|
|
||||||
_contentCallbacks.remove(url);
|
|
||||||
Q_ASSERT(req->getState() == ResourceRequest::Finished);
|
Q_ASSERT(req->getState() == ResourceRequest::Finished);
|
||||||
success = req->getResult() == ResourceRequest::Success;
|
success = req->getResult() == ResourceRequest::Success;
|
||||||
|
|
||||||
if (success) {
|
Lock lock(_containerLock);
|
||||||
_scriptCache[url] = scriptContent = req->getData();
|
|
||||||
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
if (_activeScriptRequests.contains(url)) {
|
||||||
} else {
|
auto& scriptRequest = _activeScriptRequests[url];
|
||||||
// Dubious, but retained here because it matches the behavior before fixing the threading
|
|
||||||
scriptContent = _scriptCache[url];
|
if (success) {
|
||||||
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
allCallbacks = scriptRequest.scriptUsers;
|
||||||
|
|
||||||
|
_activeScriptRequests.remove(url);
|
||||||
|
|
||||||
|
_scriptCache[url] = scriptContent = req->getData();
|
||||||
|
finished = true;
|
||||||
|
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
||||||
|
} else {
|
||||||
|
if (scriptRequest.numRetries < MAX_RETRIES) {
|
||||||
|
++scriptRequest.numRetries;
|
||||||
|
|
||||||
|
qDebug() << "Script request failed: " << url;
|
||||||
|
|
||||||
|
int timeout = exp(scriptRequest.numRetries) * START_DELAY_BETWEEN_RETRIES;
|
||||||
|
QTimer::singleShot(timeout, this, [this, url]() {
|
||||||
|
qDebug() << "Retrying script request: " << url;
|
||||||
|
|
||||||
|
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||||
|
Q_ASSERT(request);
|
||||||
|
|
||||||
|
// We've already made a request, so the cache must be disabled or it wasn't there, so enabling
|
||||||
|
// it will do nothing.
|
||||||
|
request->setCacheEnabled(false);
|
||||||
|
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||||
|
request->send();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Dubious, but retained here because it matches the behavior before fixing the threading
|
||||||
|
scriptContent = _scriptCache[url];
|
||||||
|
finished = true;
|
||||||
|
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req->deleteLater();
|
req->deleteLater();
|
||||||
|
|
||||||
if (!DependencyManager::get<ScriptEngines>()->isStopped()) {
|
if (finished && !DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||||
foreach(contentAvailableCallback thisCallback, allCallbacks) {
|
foreach(contentAvailableCallback thisCallback, allCallbacks) {
|
||||||
thisCallback(url.toString(), scriptContent, true, success);
|
thisCallback(url.toString(), scriptContent, true, success);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,19 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
|
|
||||||
|
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
||||||
|
|
||||||
class ScriptUser {
|
class ScriptUser {
|
||||||
public:
|
public:
|
||||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0;
|
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0;
|
||||||
virtual void errorInLoadingScript(const QUrl& url) = 0;
|
virtual void errorInLoadingScript(const QUrl& url) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
class ScriptRequest {
|
||||||
|
public:
|
||||||
|
std::vector<contentAvailableCallback> scriptUsers { };
|
||||||
|
int numRetries { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
/// Interface for loading scripts
|
/// Interface for loading scripts
|
||||||
class ScriptCache : public QObject, public Dependency {
|
class ScriptCache : public QObject, public Dependency {
|
||||||
|
@ -51,11 +57,11 @@ private:
|
||||||
ScriptCache(QObject* parent = NULL);
|
ScriptCache(QObject* parent = NULL);
|
||||||
|
|
||||||
Mutex _containerLock;
|
Mutex _containerLock;
|
||||||
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
|
QMap<QUrl, ScriptRequest> _activeScriptRequests;
|
||||||
|
|
||||||
QHash<QUrl, QString> _scriptCache;
|
QHash<QUrl, QString> _scriptCache;
|
||||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||||
QSet<QUrl> _badScripts;
|
QSet<QUrl> _badScripts;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptCache_h
|
#endif // hifi_ScriptCache_h
|
||||||
|
|
|
@ -34,7 +34,6 @@ namespace Setting {
|
||||||
DependencyManager::destroy<Manager>();
|
DependencyManager::destroy<Manager>();
|
||||||
|
|
||||||
//
|
//
|
||||||
globalManager->deleteLater();
|
|
||||||
globalManager.reset();
|
globalManager.reset();
|
||||||
|
|
||||||
// quit the settings manager thread and wait on it to make sure it's gone
|
// quit the settings manager thread and wait on it to make sure it's gone
|
||||||
|
@ -72,9 +71,9 @@ namespace Setting {
|
||||||
|
|
||||||
globalManager = DependencyManager::set<Manager>();
|
globalManager = DependencyManager::set<Manager>();
|
||||||
|
|
||||||
QObject::connect(globalManager.data(), SIGNAL(destroyed()), thread, SLOT(quit()));
|
|
||||||
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
|
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
|
||||||
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||||
|
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
|
||||||
globalManager->moveToThread(thread);
|
globalManager->moveToThread(thread);
|
||||||
thread->start();
|
thread->start();
|
||||||
qCDebug(shared) << "Settings thread started.";
|
qCDebug(shared) << "Settings thread started.";
|
||||||
|
|
|
@ -10,3 +10,6 @@ set_target_properties(vhacd-util PROPERTIES FOLDER "Tools")
|
||||||
|
|
||||||
add_subdirectory(ice-client)
|
add_subdirectory(ice-client)
|
||||||
set_target_properties(ice-client PROPERTIES FOLDER "Tools")
|
set_target_properties(ice-client PROPERTIES FOLDER "Tools")
|
||||||
|
|
||||||
|
add_subdirectory(ac-client)
|
||||||
|
set_target_properties(ac-client PROPERTIES FOLDER "Tools")
|
||||||
|
|
3
tools/ac-client/CMakeLists.txt
Normal file
3
tools/ac-client/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
set(TARGET_NAME ac-client)
|
||||||
|
setup_hifi_project(Core Widgets)
|
||||||
|
link_hifi_libraries(shared networking)
|
252
tools/ac-client/src/ACClientApp.cpp
Normal file
252
tools/ac-client/src/ACClientApp.cpp
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
//
|
||||||
|
// ACClientApp.cpp
|
||||||
|
// tools/ac-client/src
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2016-10-5
|
||||||
|
// 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 <QDataStream>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <NetworkLogging.h>
|
||||||
|
#include <SharedLogging.h>
|
||||||
|
#include <AddressManager.h>
|
||||||
|
#include <DependencyManager.h>
|
||||||
|
#include <SettingHandle.h>
|
||||||
|
|
||||||
|
#include "ACClientApp.h"
|
||||||
|
|
||||||
|
ACClientApp::ACClientApp(int argc, char* argv[]) :
|
||||||
|
QCoreApplication(argc, argv)
|
||||||
|
{
|
||||||
|
// parse command-line
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setApplicationDescription("High Fidelity AC client");
|
||||||
|
|
||||||
|
const QCommandLineOption helpOption = parser.addHelpOption();
|
||||||
|
|
||||||
|
const QCommandLineOption verboseOutput("v", "verbose output");
|
||||||
|
parser.addOption(verboseOutput);
|
||||||
|
|
||||||
|
const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1");
|
||||||
|
parser.addOption(domainAddressOption);
|
||||||
|
|
||||||
|
const QCommandLineOption cacheSTUNOption("s", "cache stun-server response");
|
||||||
|
parser.addOption(cacheSTUNOption);
|
||||||
|
|
||||||
|
const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT));
|
||||||
|
parser.addOption(listenPortOption);
|
||||||
|
|
||||||
|
|
||||||
|
if (!parser.parse(QCoreApplication::arguments())) {
|
||||||
|
qCritical() << parser.errorText() << endl;
|
||||||
|
parser.showHelp();
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.isSet(helpOption)) {
|
||||||
|
parser.showHelp();
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
_verbose = parser.isSet(verboseOutput);
|
||||||
|
if (!_verbose) {
|
||||||
|
QLoggingCategory::setFilterRules("qt.network.ssl.warning=false");
|
||||||
|
|
||||||
|
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtDebugMsg, false);
|
||||||
|
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtInfoMsg, false);
|
||||||
|
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
|
||||||
|
|
||||||
|
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtDebugMsg, false);
|
||||||
|
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtInfoMsg, false);
|
||||||
|
const_cast<QLoggingCategory*>(&shared())->setEnabled(QtWarningMsg, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString domainServerAddress = "127.0.0.1:40103";
|
||||||
|
if (parser.isSet(domainAddressOption)) {
|
||||||
|
domainServerAddress = parser.value(domainAddressOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "domain-server address is" << domainServerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
int listenPort = INVALID_PORT;
|
||||||
|
if (parser.isSet(listenPortOption)) {
|
||||||
|
listenPort = parser.value(listenPortOption).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Setting::preInit();
|
||||||
|
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||||
|
Setting::init();
|
||||||
|
|
||||||
|
DependencyManager::set<AccountManager>([&]{ return QString("Mozilla/5.0 (HighFidelityACClient)"); });
|
||||||
|
DependencyManager::set<AddressManager>();
|
||||||
|
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
|
||||||
|
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
// start the nodeThread so its event loop is running
|
||||||
|
QThread* nodeThread = new QThread(this);
|
||||||
|
nodeThread->setObjectName("NodeList Thread");
|
||||||
|
nodeThread->start();
|
||||||
|
|
||||||
|
// make sure the node thread is given highest priority
|
||||||
|
nodeThread->setPriority(QThread::TimeCriticalPriority);
|
||||||
|
|
||||||
|
// setup a timer for domain-server check ins
|
||||||
|
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
|
||||||
|
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
||||||
|
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||||
|
|
||||||
|
// put the NodeList and datagram processing on the node thread
|
||||||
|
nodeList->moveToThread(nodeThread);
|
||||||
|
|
||||||
|
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||||
|
|
||||||
|
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||||
|
// connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
|
||||||
|
// connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
||||||
|
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ACClientApp::domainConnectionRefused);
|
||||||
|
|
||||||
|
connect(nodeList.data(), &NodeList::nodeAdded, this, &ACClientApp::nodeAdded);
|
||||||
|
connect(nodeList.data(), &NodeList::nodeKilled, this, &ACClientApp::nodeKilled);
|
||||||
|
connect(nodeList.data(), &NodeList::nodeActivated, this, &ACClientApp::nodeActivated);
|
||||||
|
// connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
|
||||||
|
// connect(nodeList.data(), &NodeList::uuidChanged, this, &ACClientApp::setSessionUUID);
|
||||||
|
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ACClientApp::notifyPacketVersionMismatch);
|
||||||
|
|
||||||
|
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
||||||
|
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer);
|
||||||
|
|
||||||
|
DependencyManager::get<AddressManager>()->handleLookupString(domainServerAddress, false);
|
||||||
|
|
||||||
|
QTimer* doTimer = new QTimer(this);
|
||||||
|
doTimer->setSingleShot(true);
|
||||||
|
connect(doTimer, &QTimer::timeout, this, &ACClientApp::timedOut);
|
||||||
|
doTimer->start(4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ACClientApp::~ACClientApp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ACClientApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||||
|
qDebug() << "domainConnectionRefused";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::domainChanged(const QString& domainHostname) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "domainChanged";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::nodeAdded(SharedNodePointer node) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "node added: " << node->getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::nodeActivated(SharedNodePointer node) {
|
||||||
|
if (node->getType() == NodeType::EntityServer) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "saw EntityServer";
|
||||||
|
}
|
||||||
|
_sawEntityServer = true;
|
||||||
|
}
|
||||||
|
else if (node->getType() == NodeType::AudioMixer) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "saw AudioMixer";
|
||||||
|
}
|
||||||
|
_sawAudioMixer = true;
|
||||||
|
}
|
||||||
|
else if (node->getType() == NodeType::AvatarMixer) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "saw AvatarMixer";
|
||||||
|
}
|
||||||
|
_sawAvatarMixer = true;
|
||||||
|
}
|
||||||
|
else if (node->getType() == NodeType::AssetServer) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "saw AssetServer";
|
||||||
|
}
|
||||||
|
_sawAssetServer = true;
|
||||||
|
}
|
||||||
|
else if (node->getType() == NodeType::MessagesMixer) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "saw MessagesMixer";
|
||||||
|
}
|
||||||
|
_sawMessagesMixer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sawEntityServer && _sawAudioMixer && _sawAvatarMixer && _sawAssetServer && _sawMessagesMixer) {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "success";
|
||||||
|
}
|
||||||
|
finish(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::nodeKilled(SharedNodePointer node) {
|
||||||
|
qDebug() << "nodeKilled";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::timedOut() {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "timed out: " << _sawEntityServer << _sawAudioMixer <<
|
||||||
|
_sawAvatarMixer << _sawAssetServer << _sawMessagesMixer;
|
||||||
|
}
|
||||||
|
finish(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::notifyPacketVersionMismatch() {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "packet version mismatch";
|
||||||
|
}
|
||||||
|
finish(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::printFailedServers() {
|
||||||
|
if (!_sawEntityServer) {
|
||||||
|
qDebug() << "EntityServer";
|
||||||
|
}
|
||||||
|
if (!_sawAudioMixer) {
|
||||||
|
qDebug() << "AudioMixer";
|
||||||
|
}
|
||||||
|
if (!_sawAvatarMixer) {
|
||||||
|
qDebug() << "AvatarMixer";
|
||||||
|
}
|
||||||
|
if (!_sawAssetServer) {
|
||||||
|
qDebug() << "AssetServer";
|
||||||
|
}
|
||||||
|
if (!_sawMessagesMixer) {
|
||||||
|
qDebug() << "MessagesMixer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACClientApp::finish(int exitCode) {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
// send the domain a disconnect packet, force stoppage of domain-server check-ins
|
||||||
|
nodeList->getDomainHandler().disconnect();
|
||||||
|
nodeList->setIsShuttingDown(true);
|
||||||
|
|
||||||
|
// tell the packet receiver we're shutting down, so it can drop packets
|
||||||
|
nodeList->getPacketReceiver().setShouldDropPackets(true);
|
||||||
|
|
||||||
|
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
|
||||||
|
// remove the NodeList from the DependencyManager
|
||||||
|
DependencyManager::destroy<NodeList>();
|
||||||
|
// ask the node thread to quit and wait until it is done
|
||||||
|
nodeThread->quit();
|
||||||
|
nodeThread->wait();
|
||||||
|
|
||||||
|
printFailedServers();
|
||||||
|
QCoreApplication::exit(exitCode);
|
||||||
|
}
|
52
tools/ac-client/src/ACClientApp.h
Normal file
52
tools/ac-client/src/ACClientApp.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// ACClientApp.h
|
||||||
|
// tools/ac-client/src
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2016-10-5
|
||||||
|
// Copyright 2016 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
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef hifi_ACClientApp_h
|
||||||
|
#define hifi_ACClientApp_h
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <udt/Constants.h>
|
||||||
|
#include <udt/Socket.h>
|
||||||
|
#include <ReceivedMessage.h>
|
||||||
|
#include <NetworkPeer.h>
|
||||||
|
#include <NodeList.h>
|
||||||
|
|
||||||
|
|
||||||
|
class ACClientApp : public QCoreApplication {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ACClientApp(int argc, char* argv[]);
|
||||||
|
~ACClientApp();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo);
|
||||||
|
void domainChanged(const QString& domainHostname);
|
||||||
|
void nodeAdded(SharedNodePointer node);
|
||||||
|
void nodeActivated(SharedNodePointer node);
|
||||||
|
void nodeKilled(SharedNodePointer node);
|
||||||
|
void notifyPacketVersionMismatch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
NodeList* _nodeList;
|
||||||
|
void timedOut();
|
||||||
|
void printFailedServers();
|
||||||
|
void finish(int exitCode);
|
||||||
|
bool _verbose;
|
||||||
|
|
||||||
|
bool _sawEntityServer { false };
|
||||||
|
bool _sawAudioMixer { false };
|
||||||
|
bool _sawAvatarMixer { false };
|
||||||
|
bool _sawAssetServer { false };
|
||||||
|
bool _sawMessagesMixer { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //hifi_ACClientApp_h
|
23
tools/ac-client/src/main.cpp
Normal file
23
tools/ac-client/src/main.cpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// main.cpp
|
||||||
|
// tools/ice-client/src
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2016-10-5
|
||||||
|
// Copyright 2016 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 <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ACClientApp.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
ACClientApp app(argc, argv);
|
||||||
|
return app.exec();
|
||||||
|
}
|
|
@ -178,16 +178,9 @@ void ICEClientApp::doSomething() {
|
||||||
qDebug() << "sending STUN request";
|
qDebug() << "sending STUN request";
|
||||||
}
|
}
|
||||||
_socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
|
_socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
|
||||||
_stunResponseTimerCanceled = false;
|
_stunResponseTimer.setSingleShot(true);
|
||||||
_stunResponseTimer.singleShot(stunResponseTimeoutMilliSeconds, this, [&] {
|
connect(&_stunResponseTimer, SIGNAL(timeout()), this, SLOT(stunResponseTimeout()));
|
||||||
if (_stunResponseTimerCanceled) {
|
_stunResponseTimer.start(stunResponseTimeoutMilliSeconds);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_verbose) {
|
|
||||||
qDebug() << "timeout waiting for stun-server response";
|
|
||||||
}
|
|
||||||
QCoreApplication::exit(stunFailureExitStatus);
|
|
||||||
});
|
|
||||||
|
|
||||||
setState(waitForStunResponse);
|
setState(waitForStunResponse);
|
||||||
} else {
|
} else {
|
||||||
|
@ -215,16 +208,9 @@ void ICEClientApp::doSomething() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID);
|
sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID);
|
||||||
_iceResponseTimerCanceled = false;
|
_iceResponseTimer.setSingleShot(true);
|
||||||
_iceResponseTimer.singleShot(iceResponseTimeoutMilliSeconds, this, [=] {
|
connect(&_iceResponseTimer, SIGNAL(timeout()), this, SLOT(iceResponseTimeout()));
|
||||||
if (_iceResponseTimerCanceled) {
|
_iceResponseTimer.start(iceResponseTimeoutMilliSeconds);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_verbose) {
|
|
||||||
qDebug() << "timeout waiting for ice-server response";
|
|
||||||
}
|
|
||||||
QCoreApplication::exit(iceFailureExitStatus);
|
|
||||||
});
|
|
||||||
} else if (_state == pause0) {
|
} else if (_state == pause0) {
|
||||||
setState(pause1);
|
setState(pause1);
|
||||||
} else if (_state == pause1) {
|
} else if (_state == pause1) {
|
||||||
|
@ -237,6 +223,20 @@ void ICEClientApp::doSomething() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ICEClientApp::iceResponseTimeout() {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "timeout waiting for ice-server response";
|
||||||
|
}
|
||||||
|
QCoreApplication::exit(iceFailureExitStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICEClientApp::stunResponseTimeout() {
|
||||||
|
if (_verbose) {
|
||||||
|
qDebug() << "timeout waiting for stun-server response";
|
||||||
|
}
|
||||||
|
QCoreApplication::exit(stunFailureExitStatus);
|
||||||
|
}
|
||||||
|
|
||||||
void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
|
void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr,
|
||||||
const QUuid& clientID, const QUuid& peerID) {
|
const QUuid& clientID, const QUuid& peerID) {
|
||||||
std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType);
|
std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType);
|
||||||
|
@ -298,7 +298,6 @@ void ICEClientApp::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
_stunResponseTimer.stop();
|
_stunResponseTimer.stop();
|
||||||
_stunResponseTimerCanceled = true;
|
|
||||||
|
|
||||||
uint16_t newPublicPort;
|
uint16_t newPublicPort;
|
||||||
QHostAddress newPublicAddress;
|
QHostAddress newPublicAddress;
|
||||||
|
@ -331,7 +330,6 @@ void ICEClientApp::processPacket(std::unique_ptr<udt::Packet> packet) {
|
||||||
if (nlPacket->getType() == PacketType::ICEServerPeerInformation) {
|
if (nlPacket->getType() == PacketType::ICEServerPeerInformation) {
|
||||||
// cancel the timeout timer
|
// cancel the timeout timer
|
||||||
_iceResponseTimer.stop();
|
_iceResponseTimer.stop();
|
||||||
_iceResponseTimerCanceled = true;
|
|
||||||
|
|
||||||
QDataStream iceResponseStream(message->getMessage());
|
QDataStream iceResponseStream(message->getMessage());
|
||||||
if (!_domainServerPeerSet) {
|
if (!_domainServerPeerSet) {
|
||||||
|
|
|
@ -33,6 +33,10 @@ public:
|
||||||
const int stunResponseTimeoutMilliSeconds { 2000 };
|
const int stunResponseTimeoutMilliSeconds { 2000 };
|
||||||
const int iceResponseTimeoutMilliSeconds { 2000 };
|
const int iceResponseTimeoutMilliSeconds { 2000 };
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void iceResponseTimeout();
|
||||||
|
void stunResponseTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum State {
|
enum State {
|
||||||
lookUpStunServer, // 0
|
lookUpStunServer, // 0
|
||||||
|
@ -83,9 +87,7 @@ private:
|
||||||
int _state { 0 };
|
int _state { 0 };
|
||||||
|
|
||||||
QTimer _stunResponseTimer;
|
QTimer _stunResponseTimer;
|
||||||
bool _stunResponseTimerCanceled { false };
|
|
||||||
QTimer _iceResponseTimer;
|
QTimer _iceResponseTimer;
|
||||||
bool _iceResponseTimerCanceled { false };
|
|
||||||
int _domainPingCount { 0 };
|
int _domainPingCount { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue