mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 16:55: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::Recorder>();
|
||||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
|
|
|
@ -32,11 +32,13 @@
|
|||
#include <AbstractUriHandler.h>
|
||||
#include <AccountManager.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "GLLogging.h"
|
||||
|
||||
#include "TextureRecycler.h"
|
||||
#include "Context.h"
|
||||
|
||||
QString fixupHifiUrl(const QString& urlString) {
|
||||
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
||||
|
@ -114,257 +116,8 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) {
|
|||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||
|
||||
static const QEvent::Type INIT = QEvent::Type(QEvent::User + 1);
|
||||
static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2);
|
||||
static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3);
|
||||
static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4);
|
||||
|
||||
class RawTextureRecycler {
|
||||
public:
|
||||
using TexturePtr = GLuint;
|
||||
RawTextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
|
||||
void setSize(const uvec2& size);
|
||||
void clear();
|
||||
TexturePtr getNextTexture();
|
||||
void recycleTexture(GLuint texture);
|
||||
|
||||
private:
|
||||
|
||||
struct TexInfo {
|
||||
TexturePtr _tex { 0 };
|
||||
uvec2 _size;
|
||||
bool _active { false };
|
||||
|
||||
TexInfo() {}
|
||||
TexInfo(TexturePtr tex, const uvec2& size) : _tex(tex), _size(size) {}
|
||||
};
|
||||
|
||||
using Map = std::map<GLuint, TexInfo>;
|
||||
using Queue = std::queue<TexturePtr>;
|
||||
|
||||
Map _allTextures;
|
||||
Queue _readyTextures;
|
||||
uvec2 _size { 1920, 1080 };
|
||||
bool _useMipmaps;
|
||||
};
|
||||
|
||||
|
||||
void RawTextureRecycler::setSize(const uvec2& size) {
|
||||
if (size == _size) {
|
||||
return;
|
||||
}
|
||||
_size = size;
|
||||
while (!_readyTextures.empty()) {
|
||||
_readyTextures.pop();
|
||||
}
|
||||
std::set<Map::key_type> toDelete;
|
||||
std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) {
|
||||
if (!item.second._active && item.second._size != _size) {
|
||||
toDelete.insert(item.first);
|
||||
}
|
||||
});
|
||||
std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) {
|
||||
_allTextures.erase(key);
|
||||
});
|
||||
}
|
||||
|
||||
void RawTextureRecycler::clear() {
|
||||
while (!_readyTextures.empty()) {
|
||||
_readyTextures.pop();
|
||||
}
|
||||
_allTextures.clear();
|
||||
}
|
||||
|
||||
RawTextureRecycler::TexturePtr RawTextureRecycler::getNextTexture() {
|
||||
if (_readyTextures.empty()) {
|
||||
TexturePtr newTexture;
|
||||
glGenTextures(1, &newTexture);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, newTexture);
|
||||
if (_useMipmaps) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
_allTextures[newTexture] = TexInfo { newTexture, _size };
|
||||
_readyTextures.push(newTexture);
|
||||
}
|
||||
|
||||
TexturePtr result = _readyTextures.front();
|
||||
_readyTextures.pop();
|
||||
auto& item = _allTextures[result];
|
||||
item._active = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void RawTextureRecycler::recycleTexture(GLuint texture) {
|
||||
Q_ASSERT(_allTextures.count(texture));
|
||||
auto& item = _allTextures[texture];
|
||||
Q_ASSERT(item._active);
|
||||
item._active = false;
|
||||
if (item._size != _size) {
|
||||
// Buh-bye
|
||||
_allTextures.erase(texture);
|
||||
return;
|
||||
}
|
||||
|
||||
_readyTextures.push(item._tex);
|
||||
}
|
||||
|
||||
|
||||
class OffscreenQmlRenderThread : public QThread {
|
||||
public:
|
||||
OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext);
|
||||
virtual ~OffscreenQmlRenderThread() = default;
|
||||
|
||||
virtual void run() override;
|
||||
virtual bool event(QEvent *e) override;
|
||||
|
||||
protected:
|
||||
class Queue : private QQueue<QEvent*> {
|
||||
public:
|
||||
void add(QEvent::Type type);
|
||||
QEvent* take();
|
||||
|
||||
private:
|
||||
QMutex _mutex;
|
||||
QWaitCondition _waitCondition;
|
||||
bool _isWaiting{ false };
|
||||
};
|
||||
|
||||
friend class OffscreenQmlSurface;
|
||||
|
||||
QJsonObject getGLContextData();
|
||||
|
||||
Queue _queue;
|
||||
QMutex _mutex;
|
||||
QWaitCondition _waitCondition;
|
||||
std::atomic<bool> _rendering { false };
|
||||
|
||||
QJsonObject _glData;
|
||||
QMutex _glMutex;
|
||||
QWaitCondition _glWait;
|
||||
|
||||
private:
|
||||
// Event-driven methods
|
||||
void init();
|
||||
void render();
|
||||
void resize();
|
||||
void cleanup();
|
||||
|
||||
// Helper methods
|
||||
void setupFbo();
|
||||
bool allowNewFrame(uint8_t fps);
|
||||
bool fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence);
|
||||
void releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence);
|
||||
|
||||
// Texture management
|
||||
std::mutex _textureMutex;
|
||||
GLuint _latestTexture { 0 };
|
||||
GLsync _latestTextureFence { 0 };
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> _returnedTextures;
|
||||
|
||||
// Rendering members
|
||||
OffscreenGLCanvas _canvas;
|
||||
OffscreenQmlSurface* _surface{ nullptr };
|
||||
QQuickWindow* _quickWindow{ nullptr };
|
||||
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||
GLuint _fbo { 0 };
|
||||
GLuint _depthStencil { 0 };
|
||||
RawTextureRecycler _textures { true };
|
||||
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
QSize _newSize;
|
||||
bool _quit{ false };
|
||||
};
|
||||
|
||||
void OffscreenQmlRenderThread::Queue::add(QEvent::Type type) {
|
||||
QMutexLocker locker(&_mutex);
|
||||
enqueue(new QEvent(type));
|
||||
if (_isWaiting) {
|
||||
_waitCondition.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
QEvent* OffscreenQmlRenderThread::Queue::take() {
|
||||
QMutexLocker locker(&_mutex);
|
||||
while (isEmpty()) {
|
||||
_isWaiting = true;
|
||||
_waitCondition.wait(&_mutex);
|
||||
_isWaiting = false;
|
||||
}
|
||||
QEvent* e = dequeue();
|
||||
return e;
|
||||
}
|
||||
|
||||
OffscreenQmlRenderThread::OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext) : _surface(surface) {
|
||||
_canvas.setObjectName("OffscreenQmlRenderCanvas");
|
||||
qCDebug(glLogging) << "Building QML Renderer";
|
||||
if (!_canvas.create(shareContext)) {
|
||||
qWarning("Failed to create OffscreenGLCanvas");
|
||||
_quit = true;
|
||||
return;
|
||||
};
|
||||
|
||||
_renderControl = new QMyQuickRenderControl();
|
||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||
// Create a QQuickWindow that is associated with our render control.
|
||||
// This window never gets created or shown, meaning that it will never get an underlying native (platform) window.
|
||||
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
|
||||
// NOTE: Must be created on the rendering thread or it will refuse to render,
|
||||
// so we wait until after its ctor to move object/context to this thread.
|
||||
_quickWindow = new QQuickWindow(_renderControl);
|
||||
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
||||
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
||||
|
||||
// We can prepare, but we must wait to start() the thread until after the ctor
|
||||
_renderControl->prepareThread(this);
|
||||
_canvas.getContextObject()->moveToThread(this);
|
||||
moveToThread(this);
|
||||
|
||||
_queue.add(INIT);
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::run() {
|
||||
qCDebug(glLogging) << "Starting QML Renderer thread";
|
||||
|
||||
while (!_quit) {
|
||||
QEvent* e = _queue.take();
|
||||
event(e);
|
||||
delete e;
|
||||
}
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::event(QEvent *e) {
|
||||
switch (int(e->type())) {
|
||||
case INIT:
|
||||
init();
|
||||
return true;
|
||||
case RENDER:
|
||||
render();
|
||||
return true;
|
||||
case RESIZE:
|
||||
resize();
|
||||
return true;
|
||||
case STOP:
|
||||
cleanup();
|
||||
return true;
|
||||
default:
|
||||
return QObject::event(e);
|
||||
}
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::setupFbo() {
|
||||
void OffscreenQmlSurface::setupFbo() {
|
||||
_canvas->makeCurrent();
|
||||
_textures.setSize(_size);
|
||||
if (_depthStencil) {
|
||||
glDeleteRenderbuffers(1, &_depthStencil);
|
||||
|
@ -382,41 +135,12 @@ void OffscreenQmlRenderThread::setupFbo() {
|
|||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
_canvas->doneCurrent();
|
||||
}
|
||||
|
||||
QJsonObject OffscreenQmlRenderThread::getGLContextData() {
|
||||
_glMutex.lock();
|
||||
if (_glData.isEmpty()) {
|
||||
_glWait.wait(&_glMutex);
|
||||
}
|
||||
_glMutex.unlock();
|
||||
return _glData;
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::init() {
|
||||
qCDebug(glLogging) << "Initializing QML Renderer";
|
||||
|
||||
if (!_canvas.makeCurrent()) {
|
||||
qWarning("Failed to make context current on QML Renderer Thread");
|
||||
_quit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_glMutex.lock();
|
||||
_glData = ::getGLContextData();
|
||||
_glMutex.unlock();
|
||||
_glWait.wakeAll();
|
||||
|
||||
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
|
||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
|
||||
|
||||
_renderControl->initialize(_canvas.getContext());
|
||||
setupFbo();
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::cleanup() {
|
||||
void OffscreenQmlSurface::cleanup() {
|
||||
_canvas->makeCurrent();
|
||||
_renderControl->invalidate();
|
||||
|
||||
if (_depthStencil) {
|
||||
glDeleteRenderbuffers(1, &_depthStencil);
|
||||
_depthStencil = 0;
|
||||
|
@ -427,65 +151,17 @@ void OffscreenQmlRenderThread::cleanup() {
|
|||
}
|
||||
|
||||
_textures.clear();
|
||||
|
||||
_canvas.doneCurrent();
|
||||
_canvas.getContextObject()->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
_quit = true;
|
||||
_canvas->doneCurrent();
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::resize() {
|
||||
// Lock _newSize changes
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
|
||||
// Update our members
|
||||
if (_quickWindow) {
|
||||
_quickWindow->setGeometry(QRect(QPoint(), _newSize));
|
||||
_quickWindow->contentItem()->setSize(_newSize);
|
||||
}
|
||||
|
||||
// Qt bug in 5.4 forces this check of pixel ratio,
|
||||
// even though we're rendering offscreen.
|
||||
qreal pixelRatio = 1.0;
|
||||
if (_renderControl && _renderControl->_renderWindow) {
|
||||
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
|
||||
}
|
||||
|
||||
uvec2 newOffscreenSize = toGlm(_newSize * pixelRatio);
|
||||
if (newOffscreenSize == _size) {
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(glLogging) << "Offscreen UI resizing to " << _newSize.width() << "x" << _newSize.height() << " with pixel ratio " << pixelRatio;
|
||||
_size = newOffscreenSize;
|
||||
}
|
||||
|
||||
_textures.setSize(_size);
|
||||
setupFbo();
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::render() {
|
||||
// Ensure we always release the main thread
|
||||
Finally releaseMainThread([this] {
|
||||
_waitCondition.wakeOne();
|
||||
});
|
||||
|
||||
if (_surface->_paused) {
|
||||
void OffscreenQmlSurface::render() {
|
||||
if (_paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
_rendering = true;
|
||||
Finally unmarkRenderingFlag([this] {
|
||||
_rendering = false;
|
||||
});
|
||||
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
_renderControl->sync();
|
||||
releaseMainThread.trigger();
|
||||
}
|
||||
_canvas->makeCurrent();
|
||||
|
||||
_renderControl->sync();
|
||||
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
||||
|
||||
// Clear out any pending textures to be returned
|
||||
|
@ -507,70 +183,61 @@ void OffscreenQmlRenderThread::render() {
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
GLuint texture = _textures.getNextTexture();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
_renderControl->render();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
GLuint texture = _textures.getNextTexture();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
_renderControl->render();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureFence) {
|
||||
}
|
||||
if (_latestTexture) {
|
||||
_textures.recycleTexture(_latestTexture);
|
||||
glDeleteSync(_latestTextureFence);
|
||||
_latestTexture = 0;
|
||||
_latestTextureFence = 0;
|
||||
}
|
||||
|
||||
_latestTextureFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
_latestTexture = texture;
|
||||
// Fence will be used in another thread / context, so a flush is required
|
||||
glFlush();
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureAndFence.first) {
|
||||
_textures.recycleTexture(_latestTextureAndFence.first);
|
||||
glDeleteSync(static_cast<GLsync>(_latestTextureAndFence.second));
|
||||
_latestTextureAndFence = { 0, 0 };
|
||||
}
|
||||
|
||||
_quickWindow->resetOpenGLState();
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
} catch (std::runtime_error& error) {
|
||||
qWarning() << "Failed to render QML: " << error.what();
|
||||
_latestTextureAndFence = { texture, glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) };
|
||||
// Fence will be used in another thread / context, so a flush is required
|
||||
glFlush();
|
||||
}
|
||||
|
||||
_quickWindow->resetOpenGLState();
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
_canvas->doneCurrent();
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) {
|
||||
textureAndFence = { 0, 0 };
|
||||
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 == _latestTexture) {
|
||||
if (0 == _latestTextureAndFence.first) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure writes to the latest texture are complete before before returning it for reading
|
||||
Q_ASSERT(0 != _latestTextureFence);
|
||||
textureAndFence = { _latestTexture, _latestTextureFence };
|
||||
_latestTextureFence = 0;
|
||||
_latestTexture = 0;
|
||||
textureAndFence = _latestTextureAndFence;
|
||||
_latestTextureAndFence = { 0, 0 };
|
||||
return true;
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& textureAndFence) {
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
_returnedTextures.push_back(textureAndFence);
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::allowNewFrame(uint8_t fps) {
|
||||
bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) {
|
||||
// If we already have a pending texture, don't render another one
|
||||
// i.e. don't render faster than the consumer context, since it wastes
|
||||
// GPU cycles on producing output that will never be seen
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 != _latestTexture) {
|
||||
if (0 != _latestTextureAndFence.first) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -588,33 +255,46 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
|
|||
QObject::disconnect(&_updateTimer);
|
||||
QObject::disconnect(qApp);
|
||||
|
||||
qCDebug(glLogging) << "Stopping QML Renderer Thread " << _renderer->currentThreadId();
|
||||
_renderer->_queue.add(STOP);
|
||||
if (!_renderer->wait(MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) {
|
||||
qWarning() << "Failed to shut down the QML Renderer Thread";
|
||||
}
|
||||
|
||||
delete _rootItem;
|
||||
delete _renderer;
|
||||
delete _qmlComponent;
|
||||
delete _qmlEngine;
|
||||
cleanup();
|
||||
|
||||
_canvas->deleteLater();
|
||||
_rootItem->deleteLater();
|
||||
_qmlComponent->deleteLater();
|
||||
_qmlEngine->deleteLater();
|
||||
_quickWindow->deleteLater();
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::onAboutToQuit() {
|
||||
_paused = true;
|
||||
QObject::disconnect(&_updateTimer);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
||||
qCDebug(glLogging) << "Building QML surface";
|
||||
|
||||
_renderer = new OffscreenQmlRenderThread(this, shareContext);
|
||||
_renderer->moveToThread(_renderer);
|
||||
_renderer->setObjectName("QML Renderer Thread");
|
||||
_renderer->start();
|
||||
_renderControl = new QMyQuickRenderControl();
|
||||
|
||||
_renderer->_renderControl->_renderWindow = _proxyWindow;
|
||||
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||
|
||||
connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
|
||||
// Create a QQuickWindow that is associated with our render control.
|
||||
// This window never gets created or shown, meaning that it will never get an underlying native (platform) window.
|
||||
// NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events
|
||||
// NOTE: Must be created on the rendering thread or it will refuse to render,
|
||||
// so we wait until after its ctor to move object/context to this thread.
|
||||
_quickWindow = new QQuickWindow(_renderControl);
|
||||
_quickWindow->setColor(QColor(255, 255, 255, 0));
|
||||
_quickWindow->setFlags(_quickWindow->flags() | static_cast<Qt::WindowFlags>(Qt::WA_TranslucentBackground));
|
||||
|
||||
_renderControl->_renderWindow = _proxyWindow;
|
||||
|
||||
_canvas = new OffscreenGLCanvas();
|
||||
if (!_canvas->create(shareContext)) {
|
||||
qFatal("Failed to create OffscreenGLCanvas");
|
||||
return;
|
||||
};
|
||||
|
||||
connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
|
||||
|
||||
// Create a QML engine.
|
||||
_qmlEngine = new QQmlEngine;
|
||||
|
@ -625,13 +305,26 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
importList.insert(importList.begin(), PathUtils::resourcesPath());
|
||||
_qmlEngine->setImportPathList(importList);
|
||||
if (!_qmlEngine->incubationController()) {
|
||||
_qmlEngine->setIncubationController(_renderer->_quickWindow->incubationController());
|
||||
_qmlEngine->setIncubationController(_quickWindow->incubationController());
|
||||
}
|
||||
|
||||
_qmlEngine->rootContext()->setContextProperty("GL", _renderer->getGLContextData());
|
||||
// FIXME
|
||||
_qmlEngine->rootContext()->setContextProperty("GL", _glData);
|
||||
_qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
|
||||
_qmlComponent = new QQmlComponent(_qmlEngine);
|
||||
|
||||
|
||||
connect(_renderControl, &QQuickRenderControl::renderRequested, [this] { _render = true; });
|
||||
connect(_renderControl, &QQuickRenderControl::sceneChanged, [this] { _render = _polish = true; });
|
||||
|
||||
if (!_canvas->makeCurrent()) {
|
||||
qWarning("Failed to make context current for QML Renderer");
|
||||
return;
|
||||
}
|
||||
_glData = ::getGLContextData();
|
||||
_renderControl->initialize(_canvas->getContext());
|
||||
setupFbo();
|
||||
|
||||
// When Quick says there is a need to render, we will not render immediately. Instead,
|
||||
// a timer with a small interval is used to get better performance.
|
||||
QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
|
||||
|
@ -646,7 +339,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
|
||||
void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
||||
|
||||
if (!_renderer || !_renderer->_quickWindow) {
|
||||
if (!_quickWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -662,7 +355,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
|||
std::max(static_cast<int>(scale * newSize.height()), 10));
|
||||
}
|
||||
|
||||
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
||||
QSize currentSize = _quickWindow->geometry().size();
|
||||
if (newSize == currentSize && !forceResize) {
|
||||
return;
|
||||
}
|
||||
|
@ -673,12 +366,26 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
|||
_rootItem->setSize(newSize);
|
||||
}
|
||||
|
||||
{
|
||||
QMutexLocker locker(&(_renderer->_mutex));
|
||||
_renderer->_newSize = newSize;
|
||||
// Update our members
|
||||
_quickWindow->setGeometry(QRect(QPoint(), newSize));
|
||||
_quickWindow->contentItem()->setSize(newSize);
|
||||
|
||||
// Qt bug in 5.4 forces this check of pixel ratio,
|
||||
// even though we're rendering offscreen.
|
||||
qreal pixelRatio = 1.0;
|
||||
if (_renderControl && _renderControl->_renderWindow) {
|
||||
pixelRatio = _renderControl->_renderWindow->devicePixelRatio();
|
||||
}
|
||||
|
||||
_renderer->_queue.add(RESIZE);
|
||||
uvec2 newOffscreenSize = toGlm(newSize * pixelRatio);
|
||||
if (newOffscreenSize == _size) {
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
|
||||
_size = newOffscreenSize;
|
||||
_textures.setSize(_size);
|
||||
setupFbo();
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||
|
@ -710,15 +417,6 @@ void OffscreenQmlSurface::clearCache() {
|
|||
getRootContext()->engine()->clearComponentCache();
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::requestUpdate() {
|
||||
_polish = true;
|
||||
_render = true;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::requestRender() {
|
||||
_render = true;
|
||||
}
|
||||
|
||||
QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f) {
|
||||
disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
||||
if (_qmlComponent->isError()) {
|
||||
|
@ -786,8 +484,8 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
}
|
||||
// The root item is ready. Associate it with the window.
|
||||
_rootItem = newItem;
|
||||
_rootItem->setParentItem(_renderer->_quickWindow->contentItem());
|
||||
_rootItem->setSize(_renderer->_quickWindow->renderTargetSize());
|
||||
_rootItem->setParentItem(_quickWindow->contentItem());
|
||||
_rootItem->setSize(_quickWindow->renderTargetSize());
|
||||
return _rootItem;
|
||||
}
|
||||
|
||||
|
@ -797,34 +495,22 @@ void OffscreenQmlSurface::updateQuick() {
|
|||
// b) already rendering a frame
|
||||
// c) rendering too fast
|
||||
// then skip this
|
||||
if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) {
|
||||
if (!allowNewFrame(_maxFps)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_polish) {
|
||||
_renderer->_renderControl->polishItems();
|
||||
_renderControl->polishItems();
|
||||
_polish = false;
|
||||
}
|
||||
|
||||
if (_render) {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
// Lock the GUI size while syncing
|
||||
QMutexLocker locker(&(_renderer->_mutex));
|
||||
_renderer->_queue.add(RENDER);
|
||||
// FIXME need to find a better way to handle the render lockout than this locking of the main thread
|
||||
_renderer->_waitCondition.wait(&(_renderer->_mutex));
|
||||
render();
|
||||
_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& texture) {
|
||||
return _renderer->fetchTexture(texture);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& texture) {
|
||||
_renderer->releaseTexture(texture);
|
||||
}
|
||||
|
||||
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||
vec2 sourceSize;
|
||||
if (dynamic_cast<QWidget*>(sourceObject)) {
|
||||
|
@ -834,7 +520,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec
|
|||
}
|
||||
vec2 offscreenPosition = toGlm(sourcePosition);
|
||||
offscreenPosition /= sourceSize;
|
||||
offscreenPosition *= vec2(toGlm(_renderer->_quickWindow->size()));
|
||||
offscreenPosition *= vec2(toGlm(_quickWindow->size()));
|
||||
return QPointF(offscreenPosition.x, offscreenPosition.y);
|
||||
}
|
||||
|
||||
|
@ -848,7 +534,7 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO
|
|||
//
|
||||
|
||||
bool OffscreenQmlSurface::filterEnabled(QObject* originalDestination, QEvent* event) const {
|
||||
if (_renderer->_quickWindow == originalDestination) {
|
||||
if (_quickWindow == originalDestination) {
|
||||
return false;
|
||||
}
|
||||
// Only intercept events while we're in an active state
|
||||
|
@ -866,7 +552,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
// Don't intercept our own events, or we enter an infinite recursion
|
||||
QObject* recurseTest = originalDestination;
|
||||
while (recurseTest) {
|
||||
Q_ASSERT(recurseTest != _rootItem && recurseTest != _renderer->_quickWindow);
|
||||
Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
|
||||
recurseTest = recurseTest->parent();
|
||||
}
|
||||
#endif
|
||||
|
@ -884,7 +570,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease: {
|
||||
event->ignore();
|
||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, event)) {
|
||||
if (QCoreApplication::sendEvent(_quickWindow, event)) {
|
||||
return event->isAccepted();
|
||||
}
|
||||
break;
|
||||
|
@ -898,7 +584,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
wheelEvent->delta(), wheelEvent->buttons(),
|
||||
wheelEvent->modifiers(), wheelEvent->orientation());
|
||||
mappedEvent.ignore();
|
||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) {
|
||||
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||
return mappedEvent.isAccepted();
|
||||
}
|
||||
break;
|
||||
|
@ -919,7 +605,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
_qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
|
||||
}
|
||||
mappedEvent.ignore();
|
||||
if (QCoreApplication::sendEvent(_renderer->_quickWindow, &mappedEvent)) {
|
||||
if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
|
||||
return mappedEvent.isAccepted();
|
||||
}
|
||||
break;
|
||||
|
@ -938,7 +624,7 @@ void OffscreenQmlSurface::pause() {
|
|||
|
||||
void OffscreenQmlSurface::resume() {
|
||||
_paused = false;
|
||||
requestRender();
|
||||
_render = true;
|
||||
|
||||
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
|
||||
getRootContext()->setContextProperty("webEntity", this);
|
||||
|
@ -950,8 +636,8 @@ bool OffscreenQmlSurface::isPaused() const {
|
|||
|
||||
void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
|
||||
_proxyWindow = window;
|
||||
if (_renderer && _renderer->_renderControl) {
|
||||
_renderer->_renderControl->_renderWindow = window;
|
||||
if (_renderControl) {
|
||||
_renderControl->_renderWindow = window;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -960,11 +646,11 @@ QObject* OffscreenQmlSurface::getEventHandler() {
|
|||
}
|
||||
|
||||
QQuickWindow* OffscreenQmlSurface::getWindow() {
|
||||
return _renderer->_quickWindow;
|
||||
return _quickWindow;
|
||||
}
|
||||
|
||||
QSize OffscreenQmlSurface::size() const {
|
||||
return _renderer->_quickWindow->geometry().size();
|
||||
return _quickWindow->geometry().size();
|
||||
}
|
||||
|
||||
QQmlContext* OffscreenQmlSurface::getRootContext() {
|
||||
|
|
|
@ -9,24 +9,27 @@
|
|||
#ifndef hifi_OffscreenQmlSurface_h
|
||||
#define hifi_OffscreenQmlSurface_h
|
||||
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <ThreadHelpers.h>
|
||||
#include "TextureRecycler.h"
|
||||
|
||||
class QWindow;
|
||||
class QMyQuickRenderControl;
|
||||
class OffscreenGLCanvas;
|
||||
class QOpenGLContext;
|
||||
class QQmlEngine;
|
||||
class QQmlContext;
|
||||
class QQmlComponent;
|
||||
class QQuickWindow;
|
||||
class QQuickItem;
|
||||
class OffscreenQmlRenderThread;
|
||||
|
||||
class OffscreenQmlSurface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
|
||||
|
@ -88,8 +91,6 @@ signals:
|
|||
void focusTextChanged(bool focusText);
|
||||
|
||||
public slots:
|
||||
void requestUpdate();
|
||||
void requestRender();
|
||||
void onAboutToQuit();
|
||||
void focusDestroyed(QObject *obj);
|
||||
|
||||
|
@ -109,24 +110,44 @@ protected:
|
|||
private:
|
||||
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
||||
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
|
||||
void setupFbo();
|
||||
bool allowNewFrame(uint8_t fps);
|
||||
void render();
|
||||
void cleanup();
|
||||
QJsonObject getGLContextData();
|
||||
|
||||
private slots:
|
||||
void updateQuick();
|
||||
void onFocusObjectChanged(QObject* newFocus);
|
||||
|
||||
private:
|
||||
friend class OffscreenQmlRenderThread;
|
||||
OffscreenQmlRenderThread* _renderer{ nullptr };
|
||||
QQmlEngine* _qmlEngine{ nullptr };
|
||||
QQmlComponent* _qmlComponent{ nullptr };
|
||||
QQuickItem* _rootItem{ nullptr };
|
||||
QQuickWindow* _quickWindow { nullptr };
|
||||
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||
QQmlEngine* _qmlEngine { nullptr };
|
||||
QQmlComponent* _qmlComponent { nullptr };
|
||||
QQuickItem* _rootItem { nullptr };
|
||||
OffscreenGLCanvas* _canvas { nullptr };
|
||||
QJsonObject _glData;
|
||||
|
||||
QTimer _updateTimer;
|
||||
bool _render{ false };
|
||||
bool _polish{ true };
|
||||
bool _paused{ true };
|
||||
uint32_t _fbo { 0 };
|
||||
uint32_t _depthStencil { 0 };
|
||||
uint64_t _lastRenderTime { 0 };
|
||||
uvec2 _size { 1920, 1080 };
|
||||
TextureRecycler _textures { true };
|
||||
|
||||
// Texture management
|
||||
std::mutex _textureMutex;
|
||||
TextureAndFence _latestTextureAndFence { 0, 0 };
|
||||
std::list<TextureAndFence> _returnedTextures;
|
||||
|
||||
|
||||
bool _render { false };
|
||||
bool _polish { true };
|
||||
bool _paused { true };
|
||||
bool _focusText { false };
|
||||
uint8_t _maxFps{ 60 };
|
||||
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
|
||||
uint8_t _maxFps { 60 };
|
||||
MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
|
||||
QWindow* _proxyWindow { nullptr };
|
||||
|
||||
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();
|
||||
|
||||
if (!senderNode) {
|
||||
qCWarning(asset_client) << "Got completed asset for node that no longer exists";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have any pending requests for this node
|
||||
auto messageMapIt = _pendingRequests.find(senderNode);
|
||||
if (messageMapIt == _pendingRequests.end()) {
|
||||
qCWarning(asset_client) << "Got completed asset for a node that doesn't have any pending requests";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -409,6 +411,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
|||
// Check if we have this pending request
|
||||
auto requestIt = messageCallbackMap.find(messageID);
|
||||
if (requestIt == messageCallbackMap.end()) {
|
||||
qCWarning(asset_client) << "Got completed asset for a request that doesn't exist";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -416,6 +419,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
|
|||
auto& message = callbacks.message;
|
||||
|
||||
if (!message) {
|
||||
qCWarning(asset_client) << "Got completed asset for a message that doesn't exist";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,9 +107,11 @@ void AssetRequest::start() {
|
|||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto that = QPointer<AssetRequest>(this); // Used to track the request's lifetime
|
||||
auto hash = _hash;
|
||||
_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) {
|
||||
qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error;
|
||||
// If the request is dead, return
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ public:
|
|||
const State& getState() const { return _state; }
|
||||
const Error& getError() const { return _error; }
|
||||
QUrl getUrl() const { return ::getATPUrl(_hash); }
|
||||
QString getHash() const { return _hash; }
|
||||
|
||||
signals:
|
||||
void finished(AssetRequest* thisRequest);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "AssetClient.h"
|
||||
#include "AssetUtils.h"
|
||||
#include "MappingRequest.h"
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
|
||||
AssetResourceRequest::~AssetResourceRequest() {
|
||||
if (_assetMappingRequest) {
|
||||
|
@ -23,6 +24,10 @@ AssetResourceRequest::~AssetResourceRequest() {
|
|||
if (_assetRequest) {
|
||||
_assetRequest->deleteLater();
|
||||
}
|
||||
|
||||
if (_sendTimer) {
|
||||
cleanupTimer();
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetResourceRequest::urlIsAssetHash() const {
|
||||
|
@ -32,6 +37,24 @@ bool AssetResourceRequest::urlIsAssetHash() const {
|
|||
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() {
|
||||
// We'll either have a hash or an ATP path to a file (that maps to a hash)
|
||||
if (urlIsAssetHash()) {
|
||||
|
@ -58,6 +81,8 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
|||
Q_ASSERT(_state == InProgress);
|
||||
Q_ASSERT(request == _assetMappingRequest);
|
||||
|
||||
cleanupTimer();
|
||||
|
||||
switch (request->getError()) {
|
||||
case MappingRequest::NoError:
|
||||
// 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;
|
||||
});
|
||||
|
||||
setupTimer();
|
||||
_assetMappingRequest->start();
|
||||
}
|
||||
|
||||
|
@ -102,11 +128,13 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
|||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
_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) {
|
||||
Q_ASSERT(_state == InProgress);
|
||||
Q_ASSERT(req == _assetRequest);
|
||||
Q_ASSERT(req->getState() == AssetRequest::Finished);
|
||||
|
||||
cleanupTimer();
|
||||
|
||||
switch (req->getError()) {
|
||||
case AssetRequest::Error::NoError:
|
||||
|
@ -134,9 +162,35 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
|||
_assetRequest = nullptr;
|
||||
});
|
||||
|
||||
setupTimer();
|
||||
_assetRequest->start();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onTimeout();
|
||||
|
||||
private:
|
||||
void setupTimer();
|
||||
void cleanupTimer();
|
||||
|
||||
bool urlIsAssetHash() const;
|
||||
|
||||
void requestMappingForPath(const AssetPath& path);
|
||||
void requestHash(const AssetHash& hash);
|
||||
|
||||
QTimer* _sendTimer { nullptr };
|
||||
|
||||
GetMappingRequest* _assetMappingRequest { nullptr };
|
||||
AssetRequest* _assetRequest { nullptr };
|
||||
};
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
|
||||
_engine(engine)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
#include <QNetworkReply>
|
||||
|
||||
#include <QFile>
|
||||
#include <QPointer>
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "BatchLoader.h"
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
#include "ResourceManager.h"
|
||||
#include "ScriptEngines.h"
|
||||
#include "ScriptCache.h"
|
||||
|
||||
BatchLoader::BatchLoader(const QList<QUrl>& urls)
|
||||
: QObject(),
|
||||
|
@ -38,30 +40,25 @@ void BatchLoader::start() {
|
|||
|
||||
for (const auto& rawURL : _urls) {
|
||||
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
|
||||
auto request = ResourceManager::createResourceRequest(this, url);
|
||||
if (!request) {
|
||||
_data.insert(url, QString());
|
||||
qCDebug(scriptengine) << "Could not load" << url;
|
||||
continue;
|
||||
}
|
||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
||||
if (request->getResult() == ResourceRequest::Success) {
|
||||
_data.insert(url, request->getData());
|
||||
|
||||
qCDebug(scriptengine) << "Loading script at " << url;
|
||||
|
||||
QPointer<BatchLoader> self = this;
|
||||
DependencyManager::get<ScriptCache>()->getScriptContents(url.toString(), [this, self](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
if (isURL && success) {
|
||||
_data.insert(url, contents);
|
||||
qCDebug(scriptengine) << "Loaded: " << url;
|
||||
} else {
|
||||
_data.insert(url, QString());
|
||||
qCDebug(scriptengine) << "Could not load" << url;
|
||||
}
|
||||
request->deleteLater();
|
||||
checkFinished();
|
||||
});
|
||||
|
||||
// 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();
|
||||
}, false);
|
||||
}
|
||||
|
||||
checkFinished();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "ScriptEngines.h"
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
ScriptCache::ScriptCache(QObject* parent) {
|
||||
// nothing to do here...
|
||||
|
@ -133,8 +134,11 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||
contentAvailable(url.toString(), scriptContent, true, true);
|
||||
} else {
|
||||
bool alreadyWaiting = _contentCallbacks.contains(url);
|
||||
_contentCallbacks.insert(url, contentAvailable);
|
||||
auto& scriptRequest = _activeScriptRequests[url];
|
||||
|
||||
bool alreadyWaiting = scriptRequest.scriptUsers.size() > 0;
|
||||
scriptRequest.scriptUsers.push_back(contentAvailable);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
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() {
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
|
@ -160,29 +167,59 @@ void ScriptCache::scriptContentAvailable() {
|
|||
QUrl url = req->getUrl();
|
||||
|
||||
QString scriptContent;
|
||||
QList<contentAvailableCallback> allCallbacks;
|
||||
std::vector<contentAvailableCallback> allCallbacks;
|
||||
bool finished { false };
|
||||
bool success { false };
|
||||
|
||||
{
|
||||
Lock lock(_containerLock);
|
||||
allCallbacks = _contentCallbacks.values(url);
|
||||
_contentCallbacks.remove(url);
|
||||
Q_ASSERT(req->getState() == ResourceRequest::Finished);
|
||||
success = req->getResult() == ResourceRequest::Success;
|
||||
|
||||
if (success) {
|
||||
_scriptCache[url] = scriptContent = req->getData();
|
||||
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
||||
} else {
|
||||
// Dubious, but retained here because it matches the behavior before fixing the threading
|
||||
scriptContent = _scriptCache[url];
|
||||
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
||||
Lock lock(_containerLock);
|
||||
|
||||
if (_activeScriptRequests.contains(url)) {
|
||||
auto& scriptRequest = _activeScriptRequests[url];
|
||||
|
||||
if (success) {
|
||||
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();
|
||||
|
||||
if (!DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
if (finished && !DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
foreach(contentAvailableCallback thisCallback, allCallbacks) {
|
||||
thisCallback(url.toString(), scriptContent, true, success);
|
||||
}
|
||||
|
|
|
@ -15,13 +15,19 @@
|
|||
#include <mutex>
|
||||
#include <ResourceCache.h>
|
||||
|
||||
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
||||
|
||||
class ScriptUser {
|
||||
public:
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 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
|
||||
class ScriptCache : public QObject, public Dependency {
|
||||
|
@ -51,11 +57,11 @@ private:
|
|||
ScriptCache(QObject* parent = NULL);
|
||||
|
||||
Mutex _containerLock;
|
||||
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
|
||||
QMap<QUrl, ScriptRequest> _activeScriptRequests;
|
||||
|
||||
QHash<QUrl, QString> _scriptCache;
|
||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||
QSet<QUrl> _badScripts;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptCache_h
|
||||
#endif // hifi_ScriptCache_h
|
||||
|
|
|
@ -34,7 +34,6 @@ namespace Setting {
|
|||
DependencyManager::destroy<Manager>();
|
||||
|
||||
//
|
||||
globalManager->deleteLater();
|
||||
globalManager.reset();
|
||||
|
||||
// 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>();
|
||||
|
||||
QObject::connect(globalManager.data(), SIGNAL(destroyed()), thread, SLOT(quit()));
|
||||
QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer()));
|
||||
QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
|
||||
QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater()));
|
||||
globalManager->moveToThread(thread);
|
||||
thread->start();
|
||||
qCDebug(shared) << "Settings thread started.";
|
||||
|
|
|
@ -10,3 +10,6 @@ set_target_properties(vhacd-util PROPERTIES FOLDER "Tools")
|
|||
|
||||
add_subdirectory(ice-client)
|
||||
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";
|
||||
}
|
||||
_socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr);
|
||||
_stunResponseTimerCanceled = false;
|
||||
_stunResponseTimer.singleShot(stunResponseTimeoutMilliSeconds, this, [&] {
|
||||
if (_stunResponseTimerCanceled) {
|
||||
return;
|
||||
}
|
||||
if (_verbose) {
|
||||
qDebug() << "timeout waiting for stun-server response";
|
||||
}
|
||||
QCoreApplication::exit(stunFailureExitStatus);
|
||||
});
|
||||
_stunResponseTimer.setSingleShot(true);
|
||||
connect(&_stunResponseTimer, SIGNAL(timeout()), this, SLOT(stunResponseTimeout()));
|
||||
_stunResponseTimer.start(stunResponseTimeoutMilliSeconds);
|
||||
|
||||
setState(waitForStunResponse);
|
||||
} else {
|
||||
|
@ -215,16 +208,9 @@ void ICEClientApp::doSomething() {
|
|||
}
|
||||
|
||||
sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID);
|
||||
_iceResponseTimerCanceled = false;
|
||||
_iceResponseTimer.singleShot(iceResponseTimeoutMilliSeconds, this, [=] {
|
||||
if (_iceResponseTimerCanceled) {
|
||||
return;
|
||||
}
|
||||
if (_verbose) {
|
||||
qDebug() << "timeout waiting for ice-server response";
|
||||
}
|
||||
QCoreApplication::exit(iceFailureExitStatus);
|
||||
});
|
||||
_iceResponseTimer.setSingleShot(true);
|
||||
connect(&_iceResponseTimer, SIGNAL(timeout()), this, SLOT(iceResponseTimeout()));
|
||||
_iceResponseTimer.start(iceResponseTimeoutMilliSeconds);
|
||||
} else if (_state == pause0) {
|
||||
setState(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,
|
||||
const QUuid& clientID, const QUuid& peerID) {
|
||||
std::unique_ptr<NLPacket> icePacket = NLPacket::create(packetType);
|
||||
|
@ -298,7 +298,6 @@ void ICEClientApp::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet)
|
|||
}
|
||||
|
||||
_stunResponseTimer.stop();
|
||||
_stunResponseTimerCanceled = true;
|
||||
|
||||
uint16_t newPublicPort;
|
||||
QHostAddress newPublicAddress;
|
||||
|
@ -331,7 +330,6 @@ void ICEClientApp::processPacket(std::unique_ptr<udt::Packet> packet) {
|
|||
if (nlPacket->getType() == PacketType::ICEServerPeerInformation) {
|
||||
// cancel the timeout timer
|
||||
_iceResponseTimer.stop();
|
||||
_iceResponseTimerCanceled = true;
|
||||
|
||||
QDataStream iceResponseStream(message->getMessage());
|
||||
if (!_domainServerPeerSet) {
|
||||
|
|
|
@ -33,6 +33,10 @@ public:
|
|||
const int stunResponseTimeoutMilliSeconds { 2000 };
|
||||
const int iceResponseTimeoutMilliSeconds { 2000 };
|
||||
|
||||
public slots:
|
||||
void iceResponseTimeout();
|
||||
void stunResponseTimeout();
|
||||
|
||||
private:
|
||||
enum State {
|
||||
lookUpStunServer, // 0
|
||||
|
@ -83,9 +87,7 @@ private:
|
|||
int _state { 0 };
|
||||
|
||||
QTimer _stunResponseTimer;
|
||||
bool _stunResponseTimerCanceled { false };
|
||||
QTimer _iceResponseTimer;
|
||||
bool _iceResponseTimerCanceled { false };
|
||||
int _domainPingCount { 0 };
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue