mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 04:34:38 +02:00
Merge pull request #11537 from jherico/threaded_qml
Migrating QML rendering off the main thread
This commit is contained in:
commit
714e3f445d
52 changed files with 2926 additions and 1277 deletions
|
@ -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()
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() :
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -48,7 +48,6 @@ QString getTextureMemoryPressureModeString();
|
|||
Stats* Stats::getInstance() {
|
||||
if (!INSTANCE) {
|
||||
Stats::registerType();
|
||||
Stats::show();
|
||||
Q_ASSERT(INSTANCE);
|
||||
}
|
||||
return INSTANCE;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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__);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
6
libraries/qml/CMakeLists.txt
Normal file
6
libraries/qml/CMakeLists.txt
Normal 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()
|
11
libraries/qml/src/qml/Logging.cpp
Normal file
11
libraries/qml/src/qml/Logging.cpp
Normal 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")
|
16
libraries/qml/src/qml/Logging.h
Normal file
16
libraries/qml/src/qml/Logging.h
Normal 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
|
355
libraries/qml/src/qml/OffscreenSurface.cpp
Normal file
355
libraries/qml/src/qml/OffscreenSurface.cpp
Normal 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;
|
||||
}
|
127
libraries/qml/src/qml/OffscreenSurface.h
Normal file
127
libraries/qml/src/qml/OffscreenSurface.h
Normal 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
|
12
libraries/qml/src/qml/impl/Profiling.cpp
Normal file
12
libraries/qml/src/qml/impl/Profiling.cpp
Normal 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")
|
13
libraries/qml/src/qml/impl/Profiling.h
Normal file
13
libraries/qml/src/qml/impl/Profiling.h
Normal 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)
|
29
libraries/qml/src/qml/impl/RenderControl.cpp
Normal file
29
libraries/qml/src/qml/impl/RenderControl.cpp
Normal 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;
|
||||
}
|
||||
|
26
libraries/qml/src/qml/impl/RenderControl.h
Normal file
26
libraries/qml/src/qml/impl/RenderControl.h
Normal 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
|
169
libraries/qml/src/qml/impl/RenderEventHandler.cpp
Normal file
169
libraries/qml/src/qml/impl/RenderEventHandler.cpp
Normal 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();
|
||||
}
|
56
libraries/qml/src/qml/impl/RenderEventHandler.h
Normal file
56
libraries/qml/src/qml/impl/RenderEventHandler.h
Normal 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
|
448
libraries/qml/src/qml/impl/SharedObject.cpp
Normal file
448
libraries/qml/src/qml/impl/SharedObject.cpp
Normal 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;
|
||||
}
|
119
libraries/qml/src/qml/impl/SharedObject.h
Normal file
119
libraries/qml/src/qml/impl/SharedObject.h
Normal 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
|
152
libraries/qml/src/qml/impl/TextureCache.cpp
Normal file
152
libraries/qml/src/qml/impl/TextureCache.cpp
Normal 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);
|
||||
}
|
||||
}
|
75
libraries/qml/src/qml/impl/TextureCache.h
Normal file
75
libraries/qml/src/qml/impl/TextureCache.h
Normal 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
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
13
tests/qml/CMakeLists.txt
Normal 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
100
tests/qml/qml/main.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
tests/qml/qml/qml/+android/UI.js
Normal file
55
tests/qml/qml/qml/+android/UI.js
Normal 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"
|
55
tests/qml/qml/qml/+ios/UI.js
Normal file
55
tests/qml/qml/qml/+ios/UI.js
Normal 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"
|
55
tests/qml/qml/qml/+osx/UI.js
Normal file
55
tests/qml/qml/qml/+osx/UI.js
Normal 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 = ""
|
140
tests/qml/qml/qml/ButtonPage.qml
Normal file
140
tests/qml/qml/qml/ButtonPage.qml
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
126
tests/qml/qml/qml/InputPage.qml
Normal file
126
tests/qml/qml/qml/InputPage.qml
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
tests/qml/qml/qml/ProgressPage.qml
Normal file
102
tests/qml/qml/qml/ProgressPage.qml
Normal 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
55
tests/qml/qml/qml/UI.js
Normal 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
198
tests/qml/src/main.cpp
Normal 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;
|
||||
}
|
||||
|
|
@ -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}
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue