Merge pull request #11537 from jherico/threaded_qml

Migrating QML rendering off the main thread
This commit is contained in:
Sam Gateau 2018-02-12 15:32:33 -08:00 committed by GitHub
commit 714e3f445d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2926 additions and 1277 deletions

View file

@ -1,6 +1,6 @@
set(TARGET_NAME native-lib)
setup_hifi_library()
link_hifi_libraries(shared networking gl gpu image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
link_hifi_libraries(shared networking gl gpu qml image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
target_opengl()
target_bullet()

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME})
file(GLOB_RECURSE QML_SRC resources/qml/*.qml resources/qml/*.js)
add_custom_target(qml SOURCES ${QML_SRC})
add_custom_target(qmls SOURCES ${QML_SRC})
GroupSources("resources/qml")
function(JOIN VALUES GLUE OUTPUT)
@ -208,7 +208,7 @@ link_hifi_libraries(
pointers
recording fbx networking model-networking entities avatars trackers
audio audio-client animation script-engine physics
render-utils entities-renderer avatars-renderer ui auto-updater midi
render-utils entities-renderer avatars-renderer ui qml auto-updater midi
controllers plugins image trackers
ui-plugins display-plugins input-plugins
${PLATFORM_GL_BACKEND}

View file

@ -803,6 +803,8 @@ std::shared_ptr<Cube3DOverlay> _keyboardFocusHighlight{ nullptr };
OverlayID _keyboardFocusHighlightID{ UNKNOWN_OVERLAY_ID };
OffscreenGLCanvas* _qmlShareContext { nullptr };
// FIXME hack access to the internal share context for the Chromium helper
// Normally we'd want to use QWebEngine::initialize(), but we can't because
// our primary context is a QGLWidget, which can't easily be initialized to share
@ -2265,18 +2267,37 @@ void Application::initializeGL() {
_isGLInitialized = true;
}
// Build a shared canvas / context for the Chromium processes
_glWidget->makeCurrent();
#if !defined(DISABLE_QML)
// Chromium rendering uses some GL functions that prevent nSight from capturing
// frames, so we only create the shared context if nsight is NOT active.
if (!nsightActive()) {
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->setObjectName("ChromiumShareContext");
_chromiumShareContext->create(_glWidget->qglContext());
_chromiumShareContext->makeCurrent();
if (!_chromiumShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make chromium shared context current");
}
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
} else {
qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
}
#endif
// Build a shared canvas / context for the QML rendering
_glWidget->makeCurrent();
_qmlShareContext = new OffscreenGLCanvas();
_qmlShareContext->setObjectName("QmlShareContext");
_qmlShareContext->create(_glWidget->qglContext());
if (!_qmlShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make QML shared context current");
}
OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
_qmlShareContext->doneCurrent();
_glWidget->makeCurrent();
gpu::Context::init<gpu::gl::GLBackend>();
qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
@ -2312,20 +2333,23 @@ void Application::initializeGL() {
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
_offscreenContext->doneCurrent();
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
// The UI can't be created until the primary OpenGL
// context is created, because it needs to share
// texture resources
// Needs to happen AFTER the render engine initialization to access its configuration
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
initializeUi();
qCDebug(interfaceapp, "Initialized Offscreen UI.");
_glWidget->makeCurrent();
// call Menu getInstance static method to set up the menu
// Needs to happen AFTER the QML UI initialization
_window->setMenuBar(Menu::getInstance());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
init();
qCDebug(interfaceapp, "init() complete.");
@ -2336,7 +2360,6 @@ void Application::initializeGL() {
_idleLoopStdev.reset();
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
// Restore the primary GL content for the main thread
if (!_offscreenContext->makeCurrent()) {
@ -2402,29 +2425,70 @@ void Application::initializeUi() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
tabletScriptingInterface->getTablet(SYSTEM_TABLET);
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
DeadlockWatchdogThread::pause();
offscreenUi->create();
DeadlockWatchdogThread::resume();
auto surfaceContext = offscreenUi->getSurfaceContext();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
connect(offscreenUi.data(), &hifi::qml::OffscreenSurface::rootContextCreated,
this, &Application::onDesktopRootContextCreated);
connect(offscreenUi.data(), &hifi::qml::OffscreenSurface::rootItemCreated,
this, &Application::onDesktopRootItemCreated);
offscreenUi->setProxyWindow(_window->windowHandle());
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
// support the window management and scripting proxies for VR use
offscreenUi->createDesktop(PathUtils::qmlUrl("hifi/Desktop.qml"));
DeadlockWatchdogThread::withPause([&] {
offscreenUi->createDesktop(PathUtils::qmlUrl("hifi/Desktop.qml"));
});
// FIXME either expose so that dialogs can set this themselves or
// do better detection in the offscreen UI of what has focus
offscreenUi->setNavigationFocused(false);
auto engine = surfaceContext->engine();
connect(engine, &QQmlEngine::quit, [] {
qApp->quit();
});
setupPreferences();
_glWidget->installEventFilter(offscreenUi.data());
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
QPointF result = pt;
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin->isHmd()) {
getApplicationCompositor().handleRealMouseMoveEvent(false);
auto resultVec = getApplicationCompositor().getReticlePosition();
result = QPointF(resultVec.x, resultVec.y);
}
return result.toPoint();
});
offscreenUi->resume();
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
resizeGL();
});
// This will set up the input plugins UI
_activeInputPlugins.clear();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
if (KeyboardMouseDevice::NAME == inputPlugin->getName()) {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
if (TouchscreenDevice::NAME == inputPlugin->getName()) {
_touchscreenDevice = std::dynamic_pointer_cast<TouchscreenDevice>(inputPlugin);
}
}
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] {
if (isHMDMode()) {
showCursor(compositorHelper->getAllowMouseCapture() ?
Cursor::Manager::lookupIcon(_preferredCursor.get()) :
Cursor::Icon::SYSTEM);
}
});
// Pre-create a couple of Web3D overlays to speed up tablet UI
auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
}
void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
auto engine = surfaceContext->engine();
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
@ -2506,48 +2570,12 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
}
_glWidget->installEventFilter(offscreenUi.data());
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
QPointF result = pt;
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin->isHmd()) {
getApplicationCompositor().handleRealMouseMoveEvent(false);
auto resultVec = getApplicationCompositor().getReticlePosition();
result = QPointF(resultVec.x, resultVec.y);
}
return result.toPoint();
});
offscreenUi->resume();
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
resizeGL();
});
// This will set up the input plugins UI
_activeInputPlugins.clear();
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
if (KeyboardMouseDevice::NAME == inputPlugin->getName()) {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
if (TouchscreenDevice::NAME == inputPlugin->getName()) {
_touchscreenDevice = std::dynamic_pointer_cast<TouchscreenDevice>(inputPlugin);
}
}
_window->setMenuBar(new Menu());
}
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] {
if (isHMDMode()) {
showCursor(compositorHelper->getAllowMouseCapture() ?
Cursor::Manager::lookupIcon(_preferredCursor.get()) :
Cursor::Icon::SYSTEM);
}
});
// Pre-create a couple of Web3D overlays to speed up tablet UI
auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
Stats::show();
AvatarInputs::show();
}
void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
@ -2786,6 +2814,8 @@ void Application::resizeGL() {
QMutexLocker viewLocker(&_viewMutex);
_myCamera.loadViewFrustum(_viewFrustum);
}
DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
}
void Application::handleSandboxStatus(QNetworkReply* reply) {
@ -3994,7 +4024,7 @@ void Application::idle() {
// Bit of a hack since there's no device pixel ratio change event I can find.
if (offscreenUi->size() != fromGlm(uiSize)) {
qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize;
offscreenUi->resize(fromGlm(uiSize), true);
offscreenUi->resize(fromGlm(uiSize));
_offscreenContext->makeCurrent();
}
}
@ -7326,9 +7356,7 @@ void Application::updateDisplayMode() {
action->setChecked(true);
}
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection);

View file

@ -391,6 +391,8 @@ public slots:
void setPreferredCursor(const QString& cursor);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);
void onDesktopRootContextCreated(QQmlContext* qmlContext);
void showDesktop();
void clearDomainOctreeDetails();
void clearDomainAvatars();

View file

@ -1,8 +1,10 @@
#include "LimitlessConnection.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h>
#include "LimitlessConnection.h"
#include "LimitlessVoiceRecognitionScriptingInterface.h"
LimitlessConnection::LimitlessConnection() :

View file

@ -25,7 +25,6 @@ Setting::Handle<bool> showAudioToolsSetting { QStringList { "AvatarInputs", "sho
AvatarInputs* AvatarInputs::getInstance() {
if (!INSTANCE) {
AvatarInputs::registerType();
AvatarInputs::show();
Q_ASSERT(INSTANCE);
}
return INSTANCE;

View file

@ -48,7 +48,6 @@ QString getTextureMemoryPressureModeString();
Stats* Stats::getInstance() {
if (!INSTANCE) {
Stats::registerType();
Stats::show();
Q_ASSERT(INSTANCE);
}
return INSTANCE;

View file

@ -134,7 +134,11 @@ void Web3DOverlay::destroyWebSurface() {
QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
DependencyManager::get<OffscreenQmlSurfaceCache>()->release(QML, _webSurface);
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
}
_webSurface.reset();
}

View file

@ -1,7 +1,7 @@
set(TARGET_NAME entities-renderer)
AUTOSCRIBE_SHADER_LIB(gpu graphics procedural render render-utils)
setup_hifi_library(Network Script)
link_hifi_libraries(shared gpu procedural graphics model-networking script-engine render render-utils image ui pointers)
link_hifi_libraries(shared gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers)
include_hifi_library_headers(networking)
include_hifi_library_headers(gl)
include_hifi_library_headers(ktx)

View file

@ -221,19 +221,17 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
});
};
{
// FIXME use the surface cache instead of explicit creation
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
_webSurface->create();
}
// FIXME use the surface cache instead of explicit creation
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
// and the current rendering load)
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
// FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
_webSurface->getSurfaceContext()->setContextProperty("desktop", QVariant());
// Let us interact with the keyboard
_webSurface->getSurfaceContext()->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [this](QQmlContext* surfaceContext) {
// FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
surfaceContext->setContextProperty("desktop", QVariant());
// Let us interact with the keyboard
surfaceContext->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
});
_fadeStartTime = usecTimestampNow();
loadSourceURL();
_webSurface->resume();

View file

@ -81,6 +81,10 @@ bool isRenderThread() {
return QThread::currentThread() == RENDER_THREAD;
}
#if defined(Q_OS_ANDROID)
#define USE_GLES 1
#endif
namespace gl {
void withSavedContext(const std::function<void()>& f) {
// Save the original GL context, because creating a QML surface will create a new context
@ -91,4 +95,47 @@ namespace gl {
savedContext->makeCurrent(savedSurface);
}
}
bool checkGLError(const char* name) {
GLenum error = glGetError();
if (!error) {
return false;
}
switch (error) {
case GL_INVALID_ENUM:
qCWarning(glLogging) << "GLBackend" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag.";
break;
case GL_INVALID_VALUE:
qCWarning(glLogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag";
break;
case GL_INVALID_OPERATION:
qCWarning(glLogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag..";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
qCWarning(glLogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag.";
break;
case GL_OUT_OF_MEMORY:
qCWarning(glLogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded.";
break;
#if !defined(USE_GLES)
case GL_STACK_UNDERFLOW:
qCWarning(glLogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow.";
break;
case GL_STACK_OVERFLOW:
qCWarning(glLogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow.";
break;
#endif
}
return true;
}
bool checkGLErrorDebug(const char* name) {
#ifdef DEBUG
return checkGLError(name);
#else
Q_UNUSED(name);
return false;
#endif
}
}

View file

@ -13,6 +13,8 @@
#include <functional>
#include <QJsonObject>
#include "GLLogging.h"
// 16 bits of depth precision
#define DEFAULT_GL_DEPTH_BUFFER_BITS 16
// 8 bits of stencil buffer (typically you really only need 1 bit for functionality
@ -43,6 +45,13 @@ bool isRenderThread();
namespace gl {
void withSavedContext(const std::function<void()>& f);
}
bool checkGLError(const char* name);
bool checkGLErrorDebug(const char* name);
} // namespace gl
#define CHECK_GL_ERROR() ::gl::checkGLErrorDebug(__FUNCTION__)
#endif

View file

@ -8,12 +8,13 @@
#include "GLShared.h"
#include <mutex>
#include <fstream>
#include <QtCore/QThread>
#include <gl/GLHelpers.h>
#include <GPUIdent.h>
#include <NumericalConstants.h>
#include <fstream>
Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl")
Q_LOGGING_CATEGORY(trace_render_gpu_gl, "trace.render.gpu.gl")
@ -21,47 +22,6 @@ Q_LOGGING_CATEGORY(trace_render_gpu_gl_detail, "trace.render.gpu.gl.detail")
namespace gpu { namespace gl {
bool checkGLError(const char* name) {
GLenum error = glGetError();
if (!error) {
return false;
} else {
switch (error) {
case GL_INVALID_ENUM:
qCWarning(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag.";
break;
case GL_INVALID_VALUE:
qCWarning(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag";
break;
case GL_INVALID_OPERATION:
qCWarning(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag..";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
qCWarning(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag.";
break;
case GL_OUT_OF_MEMORY:
qCWarning(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded.";
break;
case GL_STACK_UNDERFLOW:
qCWarning(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow.";
break;
case GL_STACK_OVERFLOW:
qCWarning(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow.";
break;
}
return true;
}
}
bool checkGLErrorDebug(const char* name) {
#ifdef DEBUG
return checkGLError(name);
#else
Q_UNUSED(name);
return false;
#endif
}
gpu::Size getFreeDedicatedMemory() {
Size result { 0 };
static bool nvidiaMemorySupported { true };

View file

@ -9,6 +9,7 @@
#define hifi_gpu_GLShared_h
#include <gl/Config.h>
#include <gl/GLHelpers.h>
#include <gpu/Forward.h>
#include <gpu/Format.h>
#include <gpu/Context.h>
@ -114,9 +115,6 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = {
GL_INT_2_10_10_10_REV,
};
bool checkGLError(const char* name = nullptr);
bool checkGLErrorDebug(const char* name = nullptr);
class GLBackend;
template <typename GPUType>
@ -141,11 +139,8 @@ class GLShader;
class GLTexture;
struct ShaderObject;
} } // namespace gpu::gl
#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__)
#endif

View file

@ -413,7 +413,7 @@ void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLui
sourceMip._size = (GLint)faceTargets.size() * sourceMip._faceSize;
sourceMip._offset = bufferOffset;
bufferOffset += sourceMip._size;
gpu::gl::checkGLError();
::gl::checkGLError(__FUNCTION__);
}
(void)CHECK_GL_ERROR();
@ -458,7 +458,7 @@ void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLui
#endif
glCompressedTexSubImage2D(faceTargets[f], destLevel, 0, 0, sourceMip._width, sourceMip._height, internalFormat,
sourceMip._faceSize, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize));
gpu::gl::checkGLError();
::gl::checkGLError(__FUNCTION__);
}
}

View file

@ -9,11 +9,14 @@
#define hifi_gpu_GLShared_h
#include <gl/Config.h>
#include <gl/GLHelpers.h>
#include <gpu/Forward.h>
#include <gpu/Format.h>
#include <gpu/Context.h>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(gpugllogging)
Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl)
Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl_detail)
@ -143,8 +146,6 @@ struct ShaderObject;
} } // namespace gpu::gl
#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__)
#endif

View file

@ -0,0 +1,6 @@
set(TARGET_NAME qml)
setup_hifi_library(Multimedia Network Qml Quick WebChannel WebSockets ${PLATFORM_QT_COMPONENTS})
link_hifi_libraries(shared networking gl)
# Required for some low level GL interaction in the OffscreenQMLSurface
target_opengl()

View file

@ -0,0 +1,11 @@
//
// Created by Bradley Austin Davis 2016/03/01
// 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 "Logging.h"
Q_LOGGING_CATEGORY(qmlLogging, "hifi.qml")

View file

@ -0,0 +1,16 @@
//
// Created by Bradley Austin Davis 2018/01/04
// Copyright 2013-2018 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_Controllers_Logging_h
#define hifi_Controllers_Logging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(qmlLogging)
#endif

View file

@ -0,0 +1,355 @@
//
// Created by Bradley Austin Davis on 2015-05-13
// 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 "OffscreenSurface.h"
#include <unordered_set>
#include <unordered_map>
#include <QtCore/QThread>
#include <QtQml/QtQml>
#include <QtQml/QQmlEngine>
#include <QtQml/QQmlComponent>
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickRenderControl>
#include <GLMHelpers.h>
#include <gl/OffscreenGLCanvas.h>
#include <shared/ReadWriteLockable.h>
#include "Logging.h"
#include "impl/SharedObject.h"
#include "impl/TextureCache.h"
using namespace hifi::qml;
using namespace hifi::qml::impl;
static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) {
return glm::clamp(size, glm::uvec2(1), glm::uvec2(maxDimension));
}
static QSize clampSize(const QSize& qsize, uint32_t maxDimension) {
return fromGlm(clampSize(toGlm(qsize), maxDimension));
}
const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {};
void OffscreenSurface::initializeEngine(QQmlEngine* engine) {
}
using namespace hifi::qml::impl;
size_t OffscreenSurface::getUsedTextureMemory() {
return SharedObject::getTextureCache().getUsedTextureMemory();
}
void OffscreenSurface::setSharedContext(QOpenGLContext* sharedContext) {
SharedObject::setSharedContext(sharedContext);
}
std::function<void(uint32_t, void*)> OffscreenSurface::getDiscardLambda() {
return [](uint32_t texture, void* fence) {
SharedObject::getTextureCache().releaseTexture({ texture, static_cast<GLsync>(fence) });
};
}
OffscreenSurface::OffscreenSurface()
: _sharedObject(new impl::SharedObject()) {
}
OffscreenSurface::~OffscreenSurface() {
disconnect(qApp);
_sharedObject->destroy();
}
bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) {
if (!_sharedObject) {
return false;
}
hifi::qml::impl::TextureAndFence typedTextureAndFence;
bool result = _sharedObject->fetchTexture(typedTextureAndFence);
textureAndFence = typedTextureAndFence;
return result;
}
void OffscreenSurface::resize(const QSize& newSize_) {
const uint32_t MAX_OFFSCREEN_DIMENSION = 4096;
_sharedObject->setSize(clampSize(newSize_, MAX_OFFSCREEN_DIMENSION));
}
QQuickItem* OffscreenSurface::getRootItem() {
return _sharedObject->getRootItem();
}
void OffscreenSurface::clearCache() {
_sharedObject->getContext()->engine()->clearComponentCache();
}
QPointF OffscreenSurface::mapToVirtualScreen(const QPointF& originalPoint) {
return _mouseTranslator(originalPoint);
}
///////////////////////////////////////////////////////
//
// Event handling customization
//
bool OffscreenSurface::filterEnabled(QObject* originalDestination, QEvent* event) const {
if (!_sharedObject || _sharedObject->getWindow() == originalDestination) {
return false;
}
// Only intercept events while we're in an active state
if (_sharedObject->isPaused()) {
return false;
}
return true;
}
bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) {
if (!filterEnabled(originalDestination, event)) {
return false;
}
#ifdef DEBUG
// Don't intercept our own events, or we enter an infinite recursion
{
auto rootItem = _sharedObject->getRootItem();
auto quickWindow = _sharedObject->getWindow();
QObject* recurseTest = originalDestination;
while (recurseTest) {
Q_ASSERT(recurseTest != rootItem && recurseTest != quickWindow);
recurseTest = recurseTest->parent();
}
}
#endif
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
event->ignore();
if (QCoreApplication::sendEvent(_sharedObject->getWindow(), event)) {
return event->isAccepted();
}
break;
}
case QEvent::Wheel: {
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
QPointF transformedPos = mapToVirtualScreen(wheelEvent->pos());
QWheelEvent mappedEvent(transformedPos, wheelEvent->delta(), wheelEvent->buttons(), wheelEvent->modifiers(),
wheelEvent->orientation());
mappedEvent.ignore();
if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &mappedEvent)) {
return mappedEvent.isAccepted();
}
break;
}
case QEvent::MouseMove: {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos());
QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->screenPos(), mouseEvent->button(),
mouseEvent->buttons(), mouseEvent->modifiers());
if (event->type() == QEvent::MouseMove) {
// TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install
// need to investigate into why this crash is happening.
//_qmlContext->setContextProperty("lastMousePosition", transformedPos);
}
mappedEvent.ignore();
if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &mappedEvent)) {
return mappedEvent.isAccepted();
}
break;
}
default:
break;
}
return false;
}
void OffscreenSurface::pause() {
_sharedObject->pause();
}
void OffscreenSurface::resume() {
_sharedObject->resume();
}
bool OffscreenSurface::isPaused() const {
return _sharedObject->isPaused();
}
void OffscreenSurface::setProxyWindow(QWindow* window) {
_sharedObject->setProxyWindow(window);
}
QObject* OffscreenSurface::getEventHandler() {
return getWindow();
}
QQuickWindow* OffscreenSurface::getWindow() {
return _sharedObject->getWindow();
}
QSize OffscreenSurface::size() const {
return _sharedObject->getSize();
}
QQmlContext* OffscreenSurface::getSurfaceContext() {
return _sharedObject->getContext();
}
void OffscreenSurface::setMaxFps(uint8_t maxFps) {
_sharedObject->setMaxFps(maxFps);
}
void OffscreenSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) {
loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) {
QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem));
});
}
void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback) {
loadInternal(qmlSource, createNewContext, nullptr, callback);
}
void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback) {
load(qmlSource, true, callback);
}
void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) {
load(qmlSource, false, callback);
}
void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback) {
return load(QUrl(qmlSourceFile), callback);
}
void OffscreenSurface::loadInternal(const QUrl& qmlSource,
bool createNewContext,
QQuickItem* parent,
const QmlContextObjectCallback& callback) {
if (QThread::currentThread() != thread()) {
qFatal("Called load on a non-surface thread");
}
// Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
if (!getRootItem()) {
_sharedObject->create(this);
}
QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = getSurfaceContext()->resolvedUrl(qmlSource);
}
if (!getRootItem()) {
_sharedObject->setObjectName(finalQmlSource.toString());
}
auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext);
auto qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this,
[=](QQmlComponent::Status) { finishQmlLoad(qmlComponent, targetContext, parent, callback); });
return;
}
finishQmlLoad(qmlComponent, targetContext, parent, callback);
}
void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
QQmlContext* qmlContext,
QQuickItem* parent,
const QmlContextObjectCallback& callback) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
qCWarning(qmlLogging) << error.url() << error.line() << error;
}
qmlComponent->deleteLater();
return;
}
QObject* newObject = qmlComponent->beginCreate(qmlContext);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
qCWarning(qmlLogging) << error.url() << error.line() << error;
}
if (!getRootItem()) {
qFatal("Unable to finish loading QML root");
}
qmlComponent->deleteLater();
return;
}
if (!newObject) {
if (!getRootItem()) {
qFatal("Could not load object as root item");
return;
}
qCWarning(qmlLogging) << "Unable to load QML item";
return;
}
qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
// All quick items should be focusable
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
if (newItem) {
// Make sure we make items focusable (critical for
// supporting keyboard shortcuts)
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
}
bool rootCreated = getRootItem() != nullptr;
// Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears
// If we already have a root, just set a couple of flags and the ancestry
if (rootCreated) {
callback(qmlContext, newItem);
if (!parent) {
parent = getRootItem();
}
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(parent);
newItem->setParentItem(parent);
} else {
// The root item is ready. Associate it with the window.
_sharedObject->setRootItem(newItem);
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
onItemCreated(qmlContext, newItem);
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
if (!rootCreated) {
onRootCreated();
emit rootItemCreated(newItem);
// Call this callback after rootitem is set, otherwise VrMenu wont work
callback(qmlContext, newItem);
}
}
QQmlContext* OffscreenSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) {
QQmlContext* targetContext = parent ? QQmlEngine::contextForObject(parent) : getSurfaceContext();
if (!targetContext) {
targetContext = getSurfaceContext();
}
if (getRootItem() && forceNewContext) {
targetContext = new QQmlContext(targetContext, targetContext->engine());
}
return targetContext;
}

View file

@ -0,0 +1,127 @@
//
// 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_qml_OffscreenSurface_h
#define hifi_qml_OffscreenSurface_h
#include <atomic>
#include <queue>
#include <map>
#include <functional>
#include <QtCore/QUrl>
#include <QtCore/QSize>
#include <QtCore/QPointF>
#include <QtCore/QSharedPointer>
#include <QtCore/QTimer>
#include <QtQml/QJSValue>
class QWindow;
class QOpenGLContext;
class QQmlContext;
class QQmlEngine;
class QQmlComponent;
class QQuickWindow;
class QQuickItem;
class OffscreenQmlSharedObject;
namespace hifi { namespace qml {
namespace impl {
class SharedObject;
}
using QmlContextObjectCallback = ::std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenSurface : public QObject {
Q_OBJECT
public:
static const QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK;
using TextureAndFence = std::pair<uint32_t, void*>;
using MouseTranslator = std::function<QPoint(const QPointF&)>;
static void setSharedContext(QOpenGLContext* context);
OffscreenSurface();
virtual ~OffscreenSurface();
QSize size() const;
virtual void resize(const QSize& size);
void clearCache();
void setMaxFps(uint8_t maxFps);
// Optional values for event handling
void setProxyWindow(QWindow* window);
void setMouseTranslator(const MouseTranslator& mouseTranslator) { _mouseTranslator = mouseTranslator; }
void pause();
void resume();
bool isPaused() const;
QQuickItem* getRootItem();
QQuickWindow* getWindow();
QObject* getEventHandler();
QQmlContext* getSurfaceContext();
// Checks to see if a new texture is available. If one is, the function returns true and
// textureAndFence will be populated with the texture ID and a fence which will be signalled
// when the texture is safe to read.
// Returns false if no new texture is available
bool fetchTexture(TextureAndFence& textureAndFence);
static std::function<void(uint32_t, void*)> getDiscardLambda();
static size_t getUsedTextureMemory();
QPointF mapToVirtualScreen(const QPointF& originalPoint);
// For use from QML/JS
Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
// For use from C++
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK);
public slots:
virtual void onFocusObjectChanged(QObject* newFocus) {}
signals:
void rootContextCreated(QQmlContext* rootContext);
void rootItemCreated(QQuickItem* rootContext);
protected:
bool eventFilter(QObject* originalDestination, QEvent* event) override;
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
virtual void initializeEngine(QQmlEngine* engine);
virtual void loadInternal(const QUrl& qmlSource,
bool createNewContext,
QQuickItem* parent,
const QmlContextObjectCallback& callback) final;
virtual void finishQmlLoad(QQmlComponent* qmlComponent,
QQmlContext* qmlContext,
QQuickItem* parent,
const QmlContextObjectCallback& onQmlLoadedCallback) final;
virtual void onRootCreated() {}
virtual void onItemCreated(QQmlContext* context, QQuickItem* newItem) {}
virtual void onRootContextCreated(QQmlContext* qmlContext) {}
virtual QQmlContext* contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext);
private:
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
friend class hifi::qml::impl::SharedObject;
impl::SharedObject* const _sharedObject;
};
}} // namespace hifi::qml
#endif

View file

@ -0,0 +1,12 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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 "Profiling.h"
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")

View file

@ -0,0 +1,13 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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
#include <Profile.h>
Q_DECLARE_LOGGING_CATEGORY(trace_render_qml)
Q_DECLARE_LOGGING_CATEGORY(trace_render_qml_gl)

View file

@ -0,0 +1,29 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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 "RenderControl.h"
using namespace hifi::qml::impl;
RenderControl::RenderControl(QObject* parent) : QQuickRenderControl(parent) {
}
void RenderControl::setRenderWindow(QWindow* renderWindow) {
_renderWindow = renderWindow;
}
QWindow* RenderControl::renderWindow(QPoint* offset) {
if (nullptr == _renderWindow) {
return QQuickRenderControl::renderWindow(offset);
}
if (nullptr != offset) {
offset->rx() = offset->ry() = 0;
}
return _renderWindow;
}

View file

@ -0,0 +1,26 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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
#include <QtQuick/QQuickRenderControl>
namespace hifi { namespace qml { namespace impl {
class RenderControl : public QQuickRenderControl {
public:
RenderControl(QObject* parent = Q_NULLPTR);
void setRenderWindow(QWindow* renderWindow);
protected:
QWindow* renderWindow(QPoint* offset) override;
private:
QWindow* _renderWindow{ nullptr };
};
}}} // namespace hifi::qml::impl

View file

@ -0,0 +1,169 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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 "RenderEventHandler.h"
#include <gl/Config.h>
#include <gl/QOpenGLContextWrapper.h>
#include <QtQuick/QQuickWindow>
#include <shared/NsightHelpers.h>
#include "Profiling.h"
#include "SharedObject.h"
#include "TextureCache.h"
#include "RenderControl.h"
#include "../Logging.h"
using namespace hifi::qml::impl;
bool RenderEventHandler::event(QEvent* e) {
switch (static_cast<OffscreenEvent::Type>(e->type())) {
case OffscreenEvent::Render:
onRender();
return true;
case OffscreenEvent::Initialize:
onInitalize();
return true;
case OffscreenEvent::Quit:
onQuit();
return true;
default:
break;
}
return QObject::event(e);
}
RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThread)
: _shared(shared) {
// Create the GL canvas in the same thread as the share canvas
if (!_canvas.create(SharedObject::getSharedContext())) {
qFatal("Unable to create new offscreen GL context");
}
moveToThread(targetThread);
_canvas.moveToThreadWithContext(targetThread);
}
void RenderEventHandler::onInitalize() {
if (_shared->isQuit()) {
return;
}
if (!_canvas.makeCurrent()) {
qFatal("Unable to make QML rendering context current on render thread");
}
_shared->initializeRenderControl(_canvas.getContext());
}
void RenderEventHandler::resize() {
PROFILE_RANGE(render_qml_gl, __FUNCTION__);
auto targetSize = _shared->getSize();
if (_currentSize != targetSize) {
auto& offscreenTextures = SharedObject::getTextureCache();
// Release hold on the textures of the old size
if (_currentSize != QSize()) {
_shared->releaseTextureAndFence();
offscreenTextures.releaseSize(_currentSize);
}
_currentSize = targetSize;
// Acquire the new texture size
if (_currentSize != QSize()) {
qCDebug(qmlLogging) << "Upating offscreen textures to " << _currentSize.width() << " x " << _currentSize.height();
offscreenTextures.acquireSize(_currentSize);
if (_depthStencil) {
glDeleteRenderbuffers(1, &_depthStencil);
_depthStencil = 0;
}
glGenRenderbuffers(1, &_depthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _currentSize.width(), _currentSize.height());
if (!_fbo) {
glGenFramebuffers(1, &_fbo);
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
glViewport(0, 0, _currentSize.width(), _currentSize.height());
glScissor(0, 0, _currentSize.width(), _currentSize.height());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
}
}
void RenderEventHandler::onRender() {
if (_shared->isQuit()) {
return;
}
if (_canvas.getContext() != QOpenGLContextWrapper::currentContext()) {
qFatal("QML rendering context not current on render thread");
}
PROFILE_RANGE(render_qml_gl, __FUNCTION__);
if (!_shared->preRender()) {
return;
}
resize();
{
PROFILE_RANGE(render_qml_gl, "render");
GLuint texture = SharedObject::getTextureCache().acquireTexture(_currentSize);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
if (nsightActive()) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
} else {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_shared->_quickWindow->setRenderTarget(_fbo, _currentSize);
_shared->_renderControl->render();
}
_shared->_lastRenderTime = usecTimestampNow();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, texture);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_shared->updateTextureAndFence({ texture, fence });
// Fence will be used in another thread / context, so a flush is required
_shared->_quickWindow->resetOpenGLState();
}
}
void RenderEventHandler::onQuit() {
if (_canvas.getContext() != QOpenGLContextWrapper::currentContext()) {
qFatal("QML rendering context not current on render thread");
}
if (_depthStencil) {
glDeleteRenderbuffers(1, &_depthStencil);
_depthStencil = 0;
}
if (_fbo) {
glDeleteFramebuffers(1, &_fbo);
_fbo = 0;
}
_shared->shutdownRendering(_canvas, _currentSize);
// Release the reference to the shared object. This will allow it to
// be destroyed (should happen on it's own thread).
_shared->deleteLater();
deleteLater();
QThread::currentThread()->quit();
}

View file

@ -0,0 +1,56 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtGui/qevent.h>
#include <GLMHelpers.h>
#include <gl/OffscreenGLCanvas.h>
namespace hifi { namespace qml { namespace impl {
class SharedObject;
class OffscreenEvent : public QEvent {
public:
enum Type {
Initialize = QEvent::User + 1,
Render,
Quit
};
OffscreenEvent(Type type) : QEvent(static_cast<QEvent::Type>(type)) {}
};
/* The render event handler lives on the QML rendering thread for a given surface
* (each surface has a dedicated rendering thread) and handles events of type
* OffscreenEvent to do one time initialization or destruction, and to actually
* perform the render.
*/
class RenderEventHandler : public QObject {
public:
RenderEventHandler(SharedObject* shared, QThread* targetThread);
private:
bool event(QEvent* e) override;
void onInitalize();
void resize();
void onRender();
void onQuit();
SharedObject* const _shared;
OffscreenGLCanvas _canvas;
QSize _currentSize;
uint32_t _fbo{ 0 };
uint32_t _depthStencil{ 0 };
};
}}} // namespace hifi::qml::impl

View file

@ -0,0 +1,448 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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 "SharedObject.h"
#include <QtCore/qlogging.h>
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickItem>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlEngine>
#include <QtGui/QOpenGLContext>
#include <NumericalConstants.h>
#include <shared/NsightHelpers.h>
#include <gl/QOpenGLContextWrapper.h>
#include "../OffscreenSurface.h"
#include "../Logging.h"
#include "Profiling.h"
#include "RenderControl.h"
#include "RenderEventHandler.h"
#include "TextureCache.h"
// Time between receiving a request to render the offscreen UI actually triggering
// the render. Could possibly be increased depending on the framerate we expect to
// achieve.
// This has the effect of capping the framerate at 200
static const int MIN_TIMER_MS = 5;
using namespace hifi::qml;
using namespace hifi::qml::impl;
TextureCache offscreenTextures;
TextureCache& SharedObject::getTextureCache() {
return offscreenTextures;
}
#define OFFSCREEN_QML_SHARED_CONTEXT_PROPERTY "com.highfidelity.qml.gl.sharedContext"
void SharedObject::setSharedContext(QOpenGLContext* sharedContext) {
qApp->setProperty(OFFSCREEN_QML_SHARED_CONTEXT_PROPERTY, QVariant::fromValue<void*>(sharedContext));
if (QOpenGLContextWrapper::currentContext() != sharedContext) {
qFatal("The shared context must be the current context when setting");
}
}
QOpenGLContext* SharedObject::getSharedContext() {
return static_cast<QOpenGLContext*>(qApp->property(OFFSCREEN_QML_SHARED_CONTEXT_PROPERTY).value<void*>());
}
SharedObject::SharedObject() {
// Create render control
_renderControl = new RenderControl();
// 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.
QQuickWindow::setDefaultAlphaBuffer(true);
_quickWindow = new QQuickWindow(_renderControl);
_quickWindow->setColor(QColor(255, 255, 255, 0));
_quickWindow->setClearBeforeRendering(true);
QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit);
}
SharedObject::~SharedObject() {
if (_quickWindow) {
_quickWindow->destroy();
_quickWindow = nullptr;
}
if (_renderControl) {
_renderControl->deleteLater();
_renderControl = nullptr;
}
if (_renderThread) {
_renderThread->quit();
_renderThread->deleteLater();
}
if (_rootItem) {
_rootItem->deleteLater();
_rootItem = nullptr;
}
releaseEngine(_qmlContext->engine());
}
void SharedObject::create(OffscreenSurface* surface) {
if (_rootItem) {
qFatal("QML surface root item already set");
}
QObject::connect(_quickWindow, &QQuickWindow::focusObjectChanged, surface, &OffscreenSurface::onFocusObjectChanged);
// Create a QML engine.
auto qmlEngine = acquireEngine(surface);
_qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine);
surface->onRootContextCreated(_qmlContext);
emit surface->rootContextCreated(_qmlContext);
if (!qmlEngine->incubationController()) {
qmlEngine->setIncubationController(_quickWindow->incubationController());
}
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(_quickWindow));
}
void SharedObject::setRootItem(QQuickItem* rootItem) {
_rootItem = rootItem;
_rootItem->setSize(_quickWindow->size());
// Create the render thread
_renderThread = new QThread();
_renderThread->setObjectName(objectName());
_renderThread->start();
// Create event handler for the render thread
_renderObject = new RenderEventHandler(this, _renderThread);
QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize));
QObject::connect(_renderControl, &QQuickRenderControl::renderRequested, this, &SharedObject::requestRender);
QObject::connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &SharedObject::requestRenderSync);
}
void SharedObject::destroy() {
if (_quit) {
return;
}
if (!_rootItem) {
deleteLater();
return;
}
_paused = true;
if (_renderTimer) {
QObject::disconnect(_renderTimer);
_renderTimer->deleteLater();
}
QObject::disconnect(_renderControl);
QObject::disconnect(qApp);
QMutexLocker lock(&_mutex);
_quit = true;
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit));
}
#define SINGLE_QML_ENGINE 0
#if SINGLE_QML_ENGINE
static QQmlEngine* globalEngine{ nullptr };
static size_t globalEngineRefCount{ 0 };
#endif
QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
QQmlEngine* result = nullptr;
if (QThread::currentThread() != qApp->thread()) {
qCWarning(qmlLogging) << "Cannot acquire QML engine on any thread but the main thread";
}
#if SINGLE_QML_ENGINE
if (!globalEngine) {
Q_ASSERT(0 == globalEngineRefCount);
globalEngine = new QQmlEngine();
surface->initializeQmlEngine(result);
++globalEngineRefCount;
}
result = globalEngine;
#else
result = new QQmlEngine();
surface->initializeEngine(result);
#endif
return result;
}
void SharedObject::releaseEngine(QQmlEngine* engine) {
Q_ASSERT(QThread::currentThread() == qApp->thread());
#if SINGLE_QML_ENGINE
Q_ASSERT(0 != globalEngineRefCount);
if (0 == --globalEngineRefCount) {
globalEngine->deleteLater();
globalEngine = nullptr;
}
#else
engine->deleteLater();
#endif
}
bool SharedObject::event(QEvent* e) {
switch (static_cast<OffscreenEvent::Type>(e->type())) {
case OffscreenEvent::Initialize:
onInitialize();
return true;
case OffscreenEvent::Render:
onRender();
return true;
default:
break;
}
return QObject::event(e);
}
// Called by the render event handler, from the render thread
void SharedObject::initializeRenderControl(QOpenGLContext* context) {
if (context->shareContext() != getSharedContext()) {
qFatal("QML rendering context has no share context");
}
if (!nsightActive()) {
_renderControl->initialize(context);
}
}
void SharedObject::releaseTextureAndFence() {
QMutexLocker lock(&_mutex);
// If the most recent texture was unused, we can directly recycle it
if (_latestTextureAndFence.first) {
offscreenTextures.releaseTexture(_latestTextureAndFence);
_latestTextureAndFence = TextureAndFence{ 0, 0 };
}
}
void SharedObject::setRenderTarget(uint32_t fbo, const QSize& size) {
_quickWindow->setRenderTarget(fbo, size);
}
QSize SharedObject::getSize() const {
QMutexLocker locker(&_mutex);
return _size;
}
void SharedObject::setSize(const QSize& size) {
if (getSize() == size) {
return;
}
{
QMutexLocker locker(&_mutex);
_size = size;
}
qCDebug(qmlLogging) << "Offscreen UI resizing to " << size.width() << "x" << size.height();
_quickWindow->setGeometry(QRect(QPoint(), size));
_quickWindow->contentItem()->setSize(size);
if (_rootItem) {
_qmlContext->setContextProperty("surfaceSize", size);
_rootItem->setSize(size);
}
requestRenderSync();
}
bool SharedObject::preRender() {
QMutexLocker lock(&_mutex);
if (_paused) {
if (_syncRequested) {
wake();
}
return false;
}
if (_syncRequested) {
bool syncResult = true;
if (!nsightActive()) {
PROFILE_RANGE(render_qml_gl, "sync")
syncResult = _renderControl->sync();
}
wake();
if (!syncResult) {
return false;
}
_syncRequested = false;
}
return true;
}
void SharedObject::shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size) {
QMutexLocker locker(&_mutex);
if (size != QSize(0, 0)) {
offscreenTextures.releaseSize(size);
}
_renderControl->invalidate();
canvas.doneCurrent();
wake();
}
bool SharedObject::isQuit() {
QMutexLocker locker(&_mutex);
return _quit;
}
void SharedObject::requestRender() {
// Don't queue multiple renders
if (_renderRequested) {
return;
}
_renderRequested = true;
}
void SharedObject::requestRenderSync() {
if (_quit) {
return;
}
{
QMutexLocker lock(&_mutex);
_syncRequested = true;
}
requestRender();
}
bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) {
QMutexLocker locker(&_mutex);
if (0 == _latestTextureAndFence.first) {
return false;
}
textureAndFence = { 0, 0 };
std::swap(textureAndFence, _latestTextureAndFence);
return true;
}
void SharedObject::setProxyWindow(QWindow* window) {
_proxyWindow = window;
_renderControl->setRenderWindow(window);
}
void SharedObject::wait() {
_cond.wait(&_mutex);
}
void SharedObject::wake() {
_cond.wakeOne();
}
void SharedObject::onInitialize() {
// Associate root item with the window.
_rootItem->setParentItem(_quickWindow->contentItem());
_renderControl->prepareThread(_renderThread);
// Set up the render thread
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Initialize));
requestRender();
// Set up timer to trigger renders
_renderTimer = new QTimer(this);
QObject::connect(_renderTimer, &QTimer::timeout, this, &SharedObject::onTimer);
_renderTimer->setTimerType(Qt::PreciseTimer);
_renderTimer->setInterval(MIN_TIMER_MS); // 5ms, Qt::PreciseTimer required
_renderTimer->start();
}
void SharedObject::onRender() {
PROFILE_RANGE(render_qml, __FUNCTION__);
if (_quit) {
return;
}
QMutexLocker lock(&_mutex);
if (_syncRequested) {
lock.unlock();
_renderControl->polishItems();
lock.relock();
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Render));
// sync and render request, main and render threads must be synchronized
wait();
} else {
QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Render));
}
_renderRequested = false;
}
void SharedObject::onTimer() {
offscreenTextures.report();
if (!_renderRequested) {
return;
}
{
QMutexLocker locker(&_mutex);
// Don't queue more than one frame at a time
if (0 != _latestTextureAndFence.first) {
return;
}
}
{
auto minRenderInterval = USECS_PER_SECOND / _maxFps;
auto lastInterval = usecTimestampNow() - _lastRenderTime;
// Don't exceed the framerate limit
if (lastInterval < minRenderInterval) {
return;
}
}
QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Render));
}
void SharedObject::onAboutToQuit() {
destroy();
}
void SharedObject::updateTextureAndFence(const TextureAndFence& newTextureAndFence) {
QMutexLocker locker(&_mutex);
// If the most recent texture was unused, we can directly recycle it
if (_latestTextureAndFence.first) {
offscreenTextures.releaseTexture(_latestTextureAndFence);
_latestTextureAndFence = { 0, 0 };
}
_latestTextureAndFence = newTextureAndFence;
}
void SharedObject::pause() {
_paused = true;
}
void SharedObject::resume() {
_paused = false;
requestRender();
}
bool SharedObject::isPaused() const {
return _paused;
}

View file

@ -0,0 +1,119 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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
#include <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include <QtCore/QThread>
#include <QtCore/QWaitCondition>
#include <QtCore/QMutex>
#include <QtCore/QSize>
#include "TextureCache.h"
class QWindow;
class QTimer;
class QQuickWindow;
class QQuickItem;
class QOpenGLContext;
class QQmlEngine;
class QQmlContext;
class OffscreenGLCanvas;
namespace hifi { namespace qml {
class OffscreenSurface;
namespace impl {
class RenderControl;
class RenderEventHandler;
class SharedObject : public QObject {
Q_OBJECT
friend class RenderEventHandler;
public:
static void setSharedContext(QOpenGLContext* context);
static QOpenGLContext* getSharedContext();
static TextureCache& getTextureCache();
SharedObject();
virtual ~SharedObject();
void create(OffscreenSurface* surface);
void setRootItem(QQuickItem* rootItem);
void destroy();
bool isQuit();
QSize getSize() const;
void setSize(const QSize& size);
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
QQuickWindow* getWindow() { return _quickWindow; }
QQuickItem* getRootItem() { return _rootItem; }
QQmlContext* getContext() { return _qmlContext; }
void setProxyWindow(QWindow* window);
void pause();
void resume();
bool isPaused() const;
bool fetchTexture(TextureAndFence& textureAndFence);
private:
bool event(QEvent* e) override;
bool preRender();
void shutdownRendering(OffscreenGLCanvas& canvas, const QSize& size);
// Called by the render event handler, from the render thread
void initializeRenderControl(QOpenGLContext* context);
void releaseTextureAndFence();
void setRenderTarget(uint32_t fbo, const QSize& size);
QQmlEngine* acquireEngine(OffscreenSurface* surface);
void releaseEngine(QQmlEngine* engine);
void requestRender();
void requestRenderSync();
void wait();
void wake();
void onInitialize();
void onRender();
void onTimer();
void onAboutToQuit();
void updateTextureAndFence(const TextureAndFence& newTextureAndFence);
// Texture management
TextureAndFence _latestTextureAndFence{ 0, 0 };
RenderControl* _renderControl{ nullptr };
RenderEventHandler* _renderObject{ nullptr };
QQuickWindow* _quickWindow{ nullptr };
QWindow* _proxyWindow{ nullptr };
QQuickItem* _item{ nullptr };
QQuickItem* _rootItem{ nullptr };
QQmlContext* _qmlContext{ nullptr };
QTimer* _renderTimer{ nullptr };
QThread* _renderThread{ nullptr };
QWaitCondition _cond;
mutable QMutex _mutex;
uint64_t _lastRenderTime{ 0 };
QSize _size{ 100, 100 };
uint8_t _maxFps{ 60 };
bool _renderRequested{ false };
bool _syncRequested{ false };
bool _quit{ false };
bool _paused{ false };
};
} // namespace impl
}} // namespace hifi::qml

View file

@ -0,0 +1,152 @@
#include "TextureCache.h"
#include <cassert>
#include <gl/Config.h>
#include <QtCore/QThread>
#include <QtCore/QCoreApplication>
#include "Profiling.h"
using namespace hifi::qml::impl;
#if defined(Q_OS_ANDROID)
#define USE_GLES 1
#endif
uint64_t uvec2ToUint64(const QSize& size) {
uint64_t result = size.width();
result <<= 32;
result |= size.height();
return result;
}
void TextureCache::acquireSize(const QSize& size) {
auto sizeKey = uvec2ToUint64(size);
Lock lock(_mutex);
auto& textureSet = _textures[sizeKey];
++textureSet.clientCount;
}
void TextureCache::releaseSize(const QSize& size) {
auto sizeKey = uvec2ToUint64(size);
ValueList texturesToDelete;
{
Lock lock(_mutex);
assert(_textures.count(sizeKey));
auto& textureSet = _textures[sizeKey];
if (0 == --textureSet.clientCount) {
texturesToDelete.swap(textureSet.returnedTextures);
_textures.erase(sizeKey);
}
}
for (const auto& textureAndFence : texturesToDelete) {
destroy(textureAndFence);
}
}
uint32_t TextureCache::acquireTexture(const QSize& size) {
Lock lock(_mutex);
recycle();
++_activeTextureCount;
auto sizeKey = uvec2ToUint64(size);
assert(_textures.count(sizeKey));
auto& textureSet = _textures[sizeKey];
if (!textureSet.returnedTextures.empty()) {
auto textureAndFence = textureSet.returnedTextures.front();
textureSet.returnedTextures.pop_front();
glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED);
glDeleteSync((GLsync)textureAndFence.second);
return textureAndFence.first;
}
return createTexture(size);
}
void TextureCache::releaseTexture(const Value& textureAndFence) {
--_activeTextureCount;
Lock lock(_mutex);
_returnedTextures.push_back(textureAndFence);
}
void TextureCache::report() {
if (randFloat() < 0.01f) {
PROFILE_COUNTER(render_qml_gl, "offscreenTextures",
{
{ "total", QVariant::fromValue(_allTextureCount.load()) },
{ "active", QVariant::fromValue(_activeTextureCount.load()) },
});
PROFILE_COUNTER(render_qml_gl, "offscreenTextureMemory", { { "value", QVariant::fromValue(_totalTextureUsage) } });
}
}
size_t TextureCache::getUsedTextureMemory() {
return _totalTextureUsage;
}
size_t TextureCache::getMemoryForSize(const QSize& size) {
// Base size + mips
return static_cast<size_t>(((size.width() * size.height()) << 2) * 1.33f);
}
void TextureCache::destroyTexture(uint32_t texture) {
--_allTextureCount;
auto size = _textureSizes[texture];
assert(getMemoryForSize(size) <= _totalTextureUsage);
_totalTextureUsage -= getMemoryForSize(size);
_textureSizes.erase(texture);
// FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context.
glDeleteTextures(1, &texture);
}
void TextureCache::destroy(const Value& textureAndFence) {
const auto& fence = textureAndFence.second;
// FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context.
glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync((GLsync)fence);
destroyTexture(textureAndFence.first);
}
uint32_t TextureCache::createTexture(const QSize& size) {
// Need a new texture
uint32_t newTexture;
glGenTextures(1, &newTexture);
++_allTextureCount;
_textureSizes[newTexture] = size;
_totalTextureUsage += getMemoryForSize(size);
glBindTexture(GL_TEXTURE_2D, newTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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);
#if !defined(USE_GLES)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
#endif
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size.width(), size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
return newTexture;
}
void TextureCache::recycle() {
// First handle any global returns
ValueList returnedTextures;
returnedTextures.swap(_returnedTextures);
for (auto textureAndFence : returnedTextures) {
GLuint texture = textureAndFence.first;
QSize size = _textureSizes[texture];
auto sizeKey = uvec2ToUint64(size);
// Textures can be returned after all surfaces of the given size have been destroyed,
// in which case we just destroy the texture
if (!_textures.count(sizeKey)) {
destroy(textureAndFence);
continue;
}
_textures[sizeKey].returnedTextures.push_back(textureAndFence);
}
}

View file

@ -0,0 +1,75 @@
//
// Created by Bradley Austin Davis on 2018-01-04
// Copyright 2013-2018 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_QmlTextureCache_h
#define hifi_QmlTextureCache_h
#include <list>
#include <mutex>
#include <functional>
#include <unordered_map>
#include <utility>
#include <cstdint>
#include <QtCore/QSize>
namespace hifi { namespace qml { namespace impl {
class TextureAndFence : public std::pair<uint32_t, void*> {
using Parent = std::pair<uint32_t, void*>;
public:
TextureAndFence() : Parent(0, 0) {}
TextureAndFence(uint32_t texture, void* sync) : Parent(texture, sync) {};
};
class TextureCache {
public:
using Value = TextureAndFence;
using ValueList = std::list<Value>;
using Size = uint64_t;
struct TextureSet {
Size textureSize;
// The number of surfaces with this size
size_t clientCount{ 0 };
ValueList returnedTextures;
};
void releaseSize(const QSize& size);
void acquireSize(const QSize& size);
uint32_t acquireTexture(const QSize& size);
void releaseTexture(const Value& textureAndFence);
// For debugging
void report();
size_t getUsedTextureMemory();
private:
static size_t getMemoryForSize(const QSize& size);
uint32_t createTexture(const QSize& size);
void destroyTexture(uint32_t texture);
void destroy(const Value& textureAndFence);
void recycle();
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
std::atomic<int> _allTextureCount;
std::atomic<int> _activeTextureCount;
std::unordered_map<Size, TextureSet> _textures;
std::unordered_map<uint32_t, QSize> _textureSizes;
Mutex _mutex;
std::list<Value> _returnedTextures;
size_t _totalTextureUsage{ 0 };
};
}}} // namespace hifi::qml::impl
#endif

View file

@ -103,7 +103,7 @@ public:
return self();
}
Promise fail(ErrorFunction errorOnly) {
return fail([this, errorOnly](QString error, QVariantMap result) {
return fail([errorOnly](QString error, QVariantMap result) {
errorOnly(error);
});
}
@ -122,7 +122,7 @@ public:
}
Promise then(SuccessFunction successOnly) {
return then([this, successOnly](QString error, QVariantMap result) {
return then([successOnly](QString error, QVariantMap result) {
successOnly(result);
});
}

View file

@ -1,6 +1,6 @@
set(TARGET_NAME ui)
setup_hifi_library(Widgets Multimedia Network Qml Quick Script WebChannel WebSockets XmlPatterns ${PLATFORM_QT_COMPONENTS})
link_hifi_libraries(shared networking gl audio audio-client plugins pointers)
setup_hifi_library(OpenGL Multimedia Network Qml Quick Script WebChannel WebSockets XmlPatterns ${PLATFORM_QT_COMPONENTS})
link_hifi_libraries(shared networking qml gl audio audio-client plugins pointers)
include_hifi_library_headers(controllers)
# Required for some low level GL interaction in the OffscreenQMLSurface

View file

@ -127,13 +127,17 @@ void OffscreenUi::removeModalDialog(QObject* modal) {
}
}
void OffscreenUi::create() {
OffscreenQmlSurface::create();
auto myContext = getSurfaceContext();
void OffscreenUi::onRootContextCreated(QQmlContext* qmlContext) {
OffscreenQmlSurface::onRootContextCreated(qmlContext);
qmlContext->setContextProperty("OffscreenUi", this);
qmlContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
qmlContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
#ifdef DEBUG
qmlContext->setContextProperty("DebugQML", QVariant(true));
#else
qmlContext->setContextProperty("DebugQML", QVariant(false));
#endif
myContext->setContextProperty("OffscreenUi", this);
myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags());
myContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
}
void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
@ -656,12 +660,6 @@ void OffscreenUi::createDesktop(const QUrl& url) {
return;
}
#ifdef DEBUG
getSurfaceContext()->setContextProperty("DebugQML", QVariant(true));
#else
getSurfaceContext()->setContextProperty("DebugQML", QVariant(false));
#endif
load(url, [=](QQmlContext* context, QObject* newObject) {
Q_UNUSED(context)
_desktop = static_cast<QQuickItem*>(newObject);

View file

@ -57,7 +57,6 @@ class OffscreenUi : public OffscreenQmlSurface, public Dependency {
friend class VrMenu;
public:
OffscreenUi();
virtual void create() override;
void createDesktop(const QUrl& url);
void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
void hide(const QString& name);
@ -253,6 +252,9 @@ private slots:
void hoverEndEvent(const PointerEvent& event);
void handlePointerEvent(const PointerEvent& event);
protected:
void onRootContextCreated(QQmlContext* qmlContext) override;
private:
QString fileDialog(const QVariantMap& properties);
ModalDialogListener *fileDialogAsync(const QVariantMap &properties);

File diff suppressed because it is too large Load diff

View file

@ -9,200 +9,79 @@
#ifndef hifi_OffscreenQmlSurface_h
#define hifi_OffscreenQmlSurface_h
#include <atomic>
#include <queue>
#include <map>
#include <functional>
#include <QtCore/QJsonObject>
#include <QtCore/QTimer>
#include <QtCore/QUrl>
#include <GLMHelpers.h>
#include <ThreadHelpers.h>
#include <qml/OffscreenSurface.h>
#include <QtGui/qevent.h>
#include <QTouchEvent>
#include "PointerEvent.h"
class QWindow;
class QMyQuickRenderControl;
class OffscreenGLCanvas;
class QOpenGLContext;
class QQmlEngine;
class QQmlContext;
class QQmlComponent;
class QQuickWindow;
class QQuickItem;
class QJSValue;
// GPU resources are typically buffered for one copy being used by the renderer,
// one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*)>;
using QmlContextObjectCallback = std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis<OffscreenQmlSurface> {
class OffscreenQmlSurface : public hifi::qml::OffscreenSurface {
using Parent = hifi::qml::OffscreenSurface;
Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
public:
static void setSharedContext(QOpenGLContext* context);
static QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK;
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback);
static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); };
OffscreenQmlSurface();
virtual ~OffscreenQmlSurface();
using MouseTranslator = std::function<QPoint(const QPointF&)>;
virtual void create();
void resize(const QSize& size, bool forceResize = false);
QSize size() const;
// Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... })
Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
// For C++ use
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
void clearCache();
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
// Optional values for event handling
void setProxyWindow(QWindow* window);
void setMouseTranslator(MouseTranslator mouseTranslator) {
_mouseTranslator = mouseTranslator;
}
bool isFocusText() const { return _focusText; }
void pause();
void resume();
bool isPaused() const;
bool getCleaned() { return _isCleaned; }
QQuickItem* getRootItem();
QQuickWindow* getWindow();
QObject* getEventHandler();
QQmlContext* getSurfaceContext();
QPointF mapToVirtualScreen(const QPointF& originalPoint);
bool eventFilter(QObject* originalDestination, QEvent* event) override;
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false, bool passwordField = false);
Q_INVOKABLE void synthesizeKeyPress(QString key, QObject* targetOverride = nullptr);
Q_INVOKABLE void lowerKeyboard();
using TextureAndFence = std::pair<uint32_t, void*>;
// Checks to see if a new texture is available. If one is, the function returns true and
// textureAndFence will be populated with the texture ID and a fence which will be signalled
// when the texture is safe to read.
// Returns false if no new texture is available
bool fetchTexture(TextureAndFence& textureAndFence);
static std::function<void(uint32_t, void*)> getDiscardLambda();
static size_t getUsedTextureMemory();
PointerEvent::EventType choosePointerEventType(QEvent::Type type);
unsigned int deviceIdByTouchPoint(qreal x, qreal y);
signals:
void focusObjectChanged(QObject* newFocus);
void focusTextChanged(bool focusText);
public slots:
void onAboutToQuit();
void focusDestroyed(QObject *obj);
// audio output device
public slots:
void changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate = false);
void forceHtmlAudioOutputDeviceUpdate();
void forceQmlAudioOutputDeviceUpdate();
signals:
void audioOutputDeviceChanged(const QString& deviceName);
// event bridge
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
void emitWebEvent(const QVariant& webMessage);
signals:
void scriptEventReceived(const QVariant& message);
// web event bridge
void webEventReceived(const QVariant& message);
// script event bridge
void scriptEventReceived(const QVariant& message);
// qml event bridge
void fromQml(const QVariant& message);
public slots:
void focusDestroyed(QObject *obj);
// script event bridge
void emitScriptEvent(const QVariant& scriptMessage);
// web event bridge
void emitWebEvent(const QVariant& webMessage);
// qml event bridge
void sendToQml(const QVariant& message);
signals:
void fromQml(QVariant message);
protected:
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
void setFocusText(bool newFocusText);
void initializeEngine(QQmlEngine* engine) override;
void onRootContextCreated(QQmlContext* qmlContext) override;
private:
static QOpenGLContext* getSharedContext();
QQmlContext* contextForUrl(const QUrl& url, QQuickItem* parent, bool forceNewContext = false);
void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback);
void onRootCreated() override;
void onItemCreated(QQmlContext* qmlContext, QQuickItem* newItem) override;
QQmlContext* contextForUrl(const QUrl& url, QQuickItem* parent, bool forceNewContext = false) override;
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
bool allowNewFrame(uint8_t fps);
void render();
void cleanup();
void disconnectAudioOutputTimer();
private slots:
void updateQuick();
void onFocusObjectChanged(QObject* newFocus);
void onFocusObjectChanged(QObject* newFocus) override;
public slots:
void hoverBeginEvent(const PointerEvent& event, class QTouchDevice& device);
void hoverEndEvent(const PointerEvent& event, class QTouchDevice& device);
bool handlePointerEvent(const PointerEvent& event, class QTouchDevice& device, bool release = false);
void changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate = false);
void forceHtmlAudioOutputDeviceUpdate();
void forceQmlAudioOutputDeviceUpdate();
private:
using Mutex = std::mutex;
using Lock = std::unique_lock<std::mutex>;
QQuickWindow* _quickWindow { nullptr };
QQmlContext* _qmlContext { nullptr };
QQuickItem* _rootItem { nullptr };
#if !defined(DISABLE_QML)
QMyQuickRenderControl* _renderControl{ nullptr };
OffscreenGLCanvas* _canvas { nullptr };
#endif
QTimer _updateTimer;
uint32_t _fbo { 0 };
uint32_t _depthStencil { 0 };
uint64_t _lastRenderTime { 0 };
uvec2 _size;
#if !defined(Q_OS_ANDROID)
QTimer _audioOutputUpdateTimer;
QString _currentAudioOutputDevice;
#endif
// Texture management
Mutex _latestTextureAndFenceMutex;
TextureAndFence _latestTextureAndFence { 0, 0 };
bool _render { false };
bool _polish { true };
bool _paused { true };
bool _focusText { false };
bool _isCleaned{ false };
uint8_t _maxFps { 60 };
MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
QWindow* _proxyWindow { nullptr };
QQuickItem* _currentFocusItem { nullptr };
struct TouchState {
@ -214,6 +93,9 @@ private:
bool _pressed;
bool _touchBeginAccepted { false };
std::map<uint32_t, TouchState> _activeTouchPoints;
QString _currentAudioOutputDevice;
QTimer _audioOutputUpdateTimer;
};
#endif

View file

@ -44,7 +44,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP
QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) {
auto surface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface());
surface->create();
surface->load(rootSource);
surface->resize(QSize(100, 100));
return surface;

View file

@ -10,7 +10,7 @@ if (APPLE OR WIN32)
set(TARGET_NAME hifiNeuron)
setup_hifi_plugin(Qml)
link_hifi_libraries(shared controllers ui plugins input-plugins)
link_hifi_libraries(shared controllers qml ui plugins input-plugins)
target_neuron()
endif()

View file

@ -19,7 +19,7 @@ if (WIN32 AND (NOT USE_GLES))
set(TARGET_NAME oculus)
setup_hifi_plugin(Multimedia)
link_hifi_libraries(
shared gl gpu controllers ui
shared gl gpu gpu-gl controllers ui qml
plugins ui-plugins display-plugins input-plugins
audio-client networking render-utils
${PLATFORM_GL_BACKEND}

View file

@ -11,7 +11,7 @@ if (WIN32 AND (NOT USE_GLES))
add_definitions(-DGLEW_STATIC)
set(TARGET_NAME openvr)
setup_hifi_plugin(Gui Qml Multimedia)
link_hifi_libraries(shared gl networking controllers ui
link_hifi_libraries(shared gl qml networking controllers ui
plugins display-plugins ui-plugins input-plugins script-engine
audio-client render-utils graphics gpu render model-networking fbx ktx image procedural ${PLATFORM_GL_BACKEND})

13
tests/qml/CMakeLists.txt Normal file
View file

@ -0,0 +1,13 @@
set(TARGET_NAME qml-test)
setup_hifi_project(Quick Qml Gui OpenGL)
setup_memory_debugger()
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
link_hifi_libraries(shared networking gl qml)
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/qml\"")
package_libraries_for_deployment()
endif()
target_nsight()

100
tests/qml/qml/main.qml Normal file
View file

@ -0,0 +1,100 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Controls 1.2
import "qml/UI.js" as UI
import "qml"
Item {
width: 640
height: 480
Rectangle {
width: 5
height: 5
color: "red"
ColorAnimation on color { loops: Animation.Infinite; from: "red"; to: "yellow"; duration: 1000 }
}
TabView {
id: tabView
anchors.fill: parent
anchors.margins: UI.margin
tabPosition: UI.tabPosition
Layout.minimumWidth: 360
Layout.minimumHeight: 360
Layout.preferredWidth: 480
Layout.preferredHeight: 640
Tab {
title: "Buttons"
ButtonPage {
enabled: enabler.checked
}
}
Tab {
title: "Progress"
ProgressPage {
enabled: enabler.checked
}
}
Tab {
title: "Input"
InputPage {
enabled: enabler.checked
}
}
}
}

View file

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
.pragma library
var margin = 0
var tabPosition = Qt.BottomEdge
var label = "Gallery"

View file

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
.pragma library
var margin = 0
var tabPosition = Qt.BottomEdge
var label = "Gallery"

View file

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
.pragma library
var margin = 12
var tabPosition = Qt.TopEdge
var label = ""

View file

@ -0,0 +1,140 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ScrollView {
id: page
implicitWidth: 640
implicitHeight: 200
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
Item {
id: content
width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing)
height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing)
GridLayout {
id: grid
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: grid.rowSpacing
anchors.rightMargin: grid.rowSpacing
anchors.topMargin: grid.columnSpacing
columns: page.width < page.height ? 1 : 2
GroupBox {
title: "Button"
Layout.fillWidth: true
Layout.columnSpan: grid.columns
RowLayout {
anchors.fill: parent
Button { text: "OK"; isDefault: true }
Button { text: "Cancel" }
Item { Layout.fillWidth: true }
Button {
text: "Attach"
menu: Menu {
MenuItem { text: "Image" }
MenuItem { text: "Document" }
}
}
}
}
GroupBox {
title: "CheckBox"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
CheckBox { text: "E-mail"; checked: true }
CheckBox { text: "Calendar"; checked: true }
CheckBox { text: "Contacts" }
}
}
GroupBox {
title: "RadioButton"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
ExclusiveGroup { id: radioGroup }
RadioButton { text: "Portrait"; exclusiveGroup: radioGroup }
RadioButton { text: "Landscape"; exclusiveGroup: radioGroup }
RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true }
}
}
GroupBox {
title: "Switch"
Layout.fillWidth: true
Layout.columnSpan: grid.columns
ColumnLayout {
anchors.fill: parent
RowLayout {
Label { text: "Wi-Fi"; Layout.fillWidth: true }
Switch { checked: true }
}
RowLayout {
Label { text: "Bluetooth"; Layout.fillWidth: true }
Switch { checked: false }
}
}
}
}
}
}

View file

@ -0,0 +1,126 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ScrollView {
id: page
implicitWidth: 640
implicitHeight: 400
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
Item {
id: content
width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing)
height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing)
ColumnLayout {
id: column
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: column.spacing
GroupBox {
title: "TextField"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 }
TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true }
}
}
GroupBox {
title: "ComboBox"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
ComboBox {
model: Qt.fontFamilies()
Layout.fillWidth: true
}
ComboBox {
editable: true
model: ListModel {
id: listModel
ListElement { text: "Apple" }
ListElement { text: "Banana" }
ListElement { text: "Coconut" }
ListElement { text: "Orange" }
}
onAccepted: {
if (find(currentText) === -1) {
listModel.append({text: editText})
currentIndex = find(editText)
}
}
Layout.fillWidth: true
}
}
}
GroupBox {
title: "SpinBox"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
SpinBox { value: 99; Layout.fillWidth: true; z: 1 }
SpinBox { decimals: 2; Layout.fillWidth: true }
}
}
}
}
}

View file

@ -0,0 +1,102 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.2
ScrollView {
id: page
implicitWidth: 640
implicitHeight: 400
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
Item {
id: content
width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing)
height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing)
ColumnLayout {
id: column
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: column.spacing
GroupBox {
title: "ProgressBar"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
ProgressBar { indeterminate: true; Layout.fillWidth: true }
ProgressBar { value: slider.value; Layout.fillWidth: true }
}
}
GroupBox {
title: "Slider"
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
Slider { id: slider; value: 0.5; Layout.fillWidth: true }
}
}
GroupBox {
title: "BusyIndicator"
Layout.fillWidth: true
BusyIndicator { running: true }
}
}
}
}

55
tests/qml/qml/qml/UI.js Normal file
View file

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
.pragma library
var margin = 2
var tabPosition = Qt.TopEdge
var label = ""

198
tests/qml/src/main.cpp Normal file
View file

@ -0,0 +1,198 @@
//
// main.cpp
// tests/gpu-test/src
//
// 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 <unordered_map>
#include <memory>
#include <cstdio>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <QtCore/QDir>
#include <QtCore/QElapsedTimer>
#include <QtCore/QFile>
#include <QtCore/QLoggingCategory>
#include <QtGui/QResizeEvent>
#include <QtGui/QWindow>
#include <QtGui/QGuiApplication>
#include <QtGui/QImage>
#include <QtGui/QOpenGLFunctions_4_5_Core>
#include <QtGui/QOpenGLContext>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlComponent>
#include <gl/OffscreenGLCanvas.h>
#include <GLMHelpers.h>
#include <PathUtils.h>
#include <NumericalConstants.h>
#include <PerfStat.h>
#include <PathUtils.h>
#include <ViewFrustum.h>
#include <qml/OffscreenSurface.h>
class OffscreenQmlSurface : public hifi::qml::OffscreenSurface {
};
class TestWindow : public QWindow {
public:
TestWindow();
private:
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
QOpenGLContext _glContext;
OffscreenGLCanvas _sharedContext;
OffscreenQmlSurface _offscreenQml;
QOpenGLFunctions_4_5_Core _glf;
uint32_t _currentTexture{ 0 };
GLsync _readFence{ 0 };
std::function<void(uint32_t, void*)> _discardLamdba;
QSize _size;
GLuint _fbo{ 0 };
const QSize _qmlSize{ 640, 480 };
bool _aboutToQuit{ false };
void initGl();
void resizeWindow(const QSize& size);
void draw();
void resizeEvent(QResizeEvent* ev) override;
};
TestWindow::TestWindow() {
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setVersion(4, 5);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
format.setOption(QSurfaceFormat::DebugContext);
QSurfaceFormat::setDefaultFormat(format);
setFormat(format);
show();
resize(QSize(800, 600));
auto timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer);
timer->setInterval(5);
connect(timer, &QTimer::timeout, [&] { draw(); });
timer->start();
connect(qApp, &QCoreApplication::aboutToQuit, [this, timer] {
timer->stop();
_aboutToQuit = true;
});
}
void TestWindow::initGl() {
_glContext.setFormat(format());
if (!_glContext.create() || !_glContext.makeCurrent(this)) {
qFatal("Unable to intialize Window GL context");
}
_glf.initializeOpenGLFunctions();
_glf.glCreateFramebuffers(1, &_fbo);
if (!_sharedContext.create(&_glContext) || !_sharedContext.makeCurrent()) {
qFatal("Unable to intialize Shared GL context");
}
hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext());
_discardLamdba = _offscreenQml.getDiscardLambda();
_offscreenQml.resize({ 640, 480 });
_offscreenQml.load(QUrl::fromLocalFile("C:/Users/bdavi/Git/hifi/tests/qml/qml/main.qml"));
}
void TestWindow::resizeWindow(const QSize& size) {
_size = size;
}
void TestWindow::draw() {
if (_aboutToQuit) {
return;
}
// Attempting to draw before we're visible and have a valid size will
// produce GL errors.
if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) {
return;
}
static std::once_flag once;
std::call_once(once, [&] { initGl(); });
if (!_glContext.makeCurrent(this)) {
return;
}
_glf.glClearColor(1, 0, 0, 1);
_glf.glClear(GL_COLOR_BUFFER_BIT);
TextureAndFence newTextureAndFence;
if (_offscreenQml.fetchTexture(newTextureAndFence)) {
if (_currentTexture) {
_discardLamdba(_currentTexture, _readFence);
_readFence = 0;
}
_currentTexture = newTextureAndFence.first;
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
_glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, _currentTexture, 0);
}
auto diff = _size - _qmlSize;
diff /= 2;
auto qmlExtent = diff + _qmlSize;
if (_currentTexture) {
_glf.glBlitNamedFramebuffer(_fbo, 0,
0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1,
diff.width(), diff.height(), qmlExtent.width() - 1, qmlExtent.height() - 2,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
if (_readFence) {
_glf.glDeleteSync(_readFence);
}
_readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
_glf.glFlush();
_glContext.swapBuffers(this);
}
void TestWindow::resizeEvent(QResizeEvent* ev) {
resizeWindow(ev->size());
}
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
#ifdef Q_OS_WIN
OutputDebugStringA(message.toLocal8Bit().constData());
OutputDebugStringA("\n");
#endif
}
}
int main(int argc, char** argv) {
QGuiApplication app(argc, argv);
qInstallMessageHandler(messageHandler);
TestWindow window;
app.exec();
return 0;
}

View file

@ -14,11 +14,11 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# link in the shared libraries
link_hifi_libraries(
shared networking animation
ktx image octree gl gpu
ktx image octree gl gpu gpu-gl
render render-utils
graphics fbx model-networking
entities entities-renderer audio avatars script-engine
physics procedural midi ui
physics procedural midi qml ui
${PLATFORM_GL_BACKEND}
)