Merge branch 'loginInitiative2' of https://github.com/wayne-chen/hifi into loginInitiative2

This commit is contained in:
Wayne Chen 2018-10-30 10:23:25 -07:00
commit 8e4837f8c8
37 changed files with 1014 additions and 620 deletions

View file

@ -66,7 +66,7 @@ if (ANDROID)
set(GLES_OPTION ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
else ()
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
set(PLATFORM_QT_COMPONENTS WebEngine)
endif ()
if (USE_GLES AND (NOT ANDROID))

View file

@ -52,6 +52,8 @@
#include <QTemporaryDir>
#include <gl/QOpenGLContextWrapper.h>
#include <gl/GLWindow.h>
#include <gl/GLHelpers.h>
#include <shared/FileUtils.h>
#include <shared/QtHelpers.h>
@ -973,9 +975,11 @@ OffscreenGLCanvas* _qmlShareContext { nullptr };
// and manually set THAT to be the shared context for the Chromium helper
#if !defined(DISABLE_QML)
OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
#endif
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.0f;
@ -1021,6 +1025,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
{
const QStringList args = arguments();
@ -1219,7 +1224,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
nodeList->getDomainHandler().setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS));
nodeList->getDomainHandler().setLoginScreenDomainURL(QUrl(LOGIN_SCREEN_HIFI_ADDRESS));
// We could clear ATP assets only when changing domains, but it's possible that the domain you are connected
// to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid.
@ -1374,7 +1378,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_glWidget->setMouseTracking(true);
// Make sure the window is set to the correct size by processing the pending events
QCoreApplication::processEvents();
_glWidget->createContext();
// Create the main thread context, the GPU backend
initializeGL();
@ -1742,27 +1745,30 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
userInputMapper->registerDevice(_touchscreenVirtualPadDevice->getInputDevice());
}
{
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
// this will force the model the look at the correct directory (weird order of operations issue)
scriptEngines->reloadLocalFiles();
QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH);
_defaultScriptsLocation = getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str());
// do this as late as possible so that all required subsystems are initialized
// If we've overridden the default scripts location, just load default scripts
// otherwise, load 'em all
//{
// auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
// // this will force the model the look at the correct directory (weird order of operations issue)
// scriptEngines->reloadLocalFiles();
// we just want to see if --scripts was set, we've already parsed it and done
// the change in PathUtils. Rather than pass that in the constructor, lets just
// look (this could be debated)
QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH);
QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str()));
if (!defaultScriptsLocation.exists()) {
scriptEngines->loadDefaultScripts();
scriptEngines->defaultScriptsLocationOverridden(true);
} else {
scriptEngines->loadScripts();
}
}
// // do this as late as possible so that all required subsystems are initialized
// // If we've overridden the default scripts location, just load default scripts
// // otherwise, load 'em all
// // we just want to see if --scripts was set, we've already parsed it and done
// // the change in PathUtils. Rather than pass that in the constructor, lets just
// // look (this could be debated)
// QString scriptsSwitch = QString("--").append(SCRIPTS_SWITCH);
// QDir defaultScriptsLocation(getCmdOption(argc, constArgv, scriptsSwitch.toStdString().c_str()));
// if (!defaultScriptsLocation.exists()) {
// scriptEngines->loadDefaultScripts();
// scriptEngines->defaultScriptsLocationOverridden(true);
// } else {
// scriptEngines->loadScripts();
// }
//}
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -2244,28 +2250,28 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_snapshotSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/snapshot/snap.wav"));
QVariant testProperty = property(hifi::properties::TEST);
qDebug() << testProperty;
if (testProperty.isValid()) {
const auto testScript = property(hifi::properties::TEST).toUrl();
//QVariant testProperty = property(hifi::properties::TEST);
//qDebug() << testProperty;
//if (testProperty.isValid()) {
// const auto testScript = property(hifi::properties::TEST).toUrl();
// Set last parameter to exit interface when the test script finishes, if so requested
DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, quitWhenFinished);
// // Set last parameter to exit interface when the test script finishes, if so requested
// DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, quitWhenFinished);
// This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
if (arguments().contains("--url")) {
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [this, reply] {
handleSandboxStatus(reply);
});
}
} else {
PROFILE_RANGE(render, "GetSandboxStatus");
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [this, reply] {
handleSandboxStatus(reply);
});
}
// // This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
// if (arguments().contains("--url")) {
// auto reply = SandboxUtils::getStatus();
// connect(reply, &QNetworkReply::finished, this, [this, reply] {
// handleSandboxStatus(reply);
// });
// }
//} else {
// PROFILE_RANGE(render, "GetSandboxStatus");
// auto reply = SandboxUtils::getStatus();
// connect(reply, &QNetworkReply::finished, this, [this, reply] {
// handleSandboxStatus(reply);
// });
//}
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
static const int ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS = 1000;
@ -2285,7 +2291,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose);
connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose);
connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &Application::addAssetToWorldMessageClose);
connect(&domainHandler, &DomainHandler::redirectToLoginScreenDomainURL, this, &Application::addAssetToWorldMessageClose);
updateSystemTabletMode();
@ -2341,6 +2346,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
AndroidHelper::instance().notifyLoadComplete();
#endif
pauseUntilLoginDetermined();
}
void Application::updateVerboseLogging() {
@ -2718,46 +2724,66 @@ void Application::initializeGL() {
_isGLInitialized = true;
}
if (!_glWidget->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make window context current");
}
_glWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat());
// When loading QtWebEngineWidgets, it creates a global share context on startup.
// We have to account for this possibility by checking here for an existing
// global share context
auto globalShareContext = qt_gl_global_share_context();
#if !defined(DISABLE_QML)
// Build a shared canvas / context for the Chromium processes
{
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
std::string vendor{ (const char*)glGetString(GL_VENDOR) };
if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
}
if (!globalShareContext) {
// 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());
auto format =QSurfaceFormat::defaultFormat();
#ifdef Q_OS_MAC
// On mac, the primary shared OpenGL context must be a 3.2 core context,
// or chromium flips out and spews error spam (but renders fine)
format.setMajorVersion(3);
format.setMinorVersion(2);
#endif
_chromiumShareContext->setFormat(format);
_chromiumShareContext->create();
if (!_chromiumShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make chromium shared context current");
}
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
globalShareContext = _chromiumShareContext->getContext();
qt_gl_set_global_share_context(globalShareContext);
_chromiumShareContext->doneCurrent();
// Restore the GL widget context
if (!_glWidget->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make window context current");
}
} else {
qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering";
}
}
#endif
_glWidget->createContext(globalShareContext);
if (!_glWidget->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make window context current");
}
#if !defined(DISABLE_QML)
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
std::string vendor{ (const char*)glGetString(GL_VENDOR) };
if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
}
#endif
if (!globalShareContext) {
globalShareContext = _glWidget->qglContext();
qt_gl_set_global_share_context(globalShareContext);
}
// Build a shared canvas / context for the QML rendering
{
_qmlShareContext = new OffscreenGLCanvas();
_qmlShareContext->setObjectName("QmlShareContext");
_qmlShareContext->create(_glWidget->qglContext());
_qmlShareContext->create(globalShareContext);
if (!_qmlShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make QML shared context current");
}
@ -2891,18 +2917,14 @@ void Application::showLoginScreen() {
auto accountManager = DependencyManager::get<AccountManager>();
auto dialogsManager = DependencyManager::get<DialogsManager>();
if (!accountManager->isLoggedIn()) {
// dialogsManager->showLoginScreenDialog();
_loginDialogPoppedUp = true;
dialogsManager->showLoginDialog();
QJsonObject loginData = {};
loginData["action"] = "login dialog shown";
UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData);
if (qApp->isHMDMode()) {
// create web overlay.
auto nodeList = DependencyManager::get<NodeList>();
auto loginScreenDomainURL = nodeList->getDomainHandler().getLoginScreenDomainURL();
goToLoginScreenDomainURL(loginScreenDomainURL);
}
_window->setWindowTitle("High Fidelity Interface");
} else {
resumeAfterLoginDialogActionTaken();
}
_loginDialogPoppedUp = !accountManager->isLoggedIn();
loginDialogPoppedUp.set(_loginDialogPoppedUp);
@ -3043,14 +3065,12 @@ void Application::initializeUi() {
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
flushMenuUpdates();
// Now that the menu is instantiated, ensure the display plugin menu is properly updated
{
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
// first sort the plugins into groupings: standard, advanced, developer
std::stable_sort(displayPlugins.begin(), displayPlugins.end(),
[](const DisplayPluginPointer& a, const DisplayPluginPointer& b)->bool { return a->getGrouping() < b->getGrouping(); });
[](const DisplayPluginPointer& a, const DisplayPluginPointer& b) -> bool { return a->getGrouping() < b->getGrouping(); });
int dpIndex = 1;
// concatenate the groupings into a single list in the order: standard, advanced, developer
for(const auto& displayPlugin : displayPlugins) {
@ -3063,6 +3083,7 @@ void Application::initializeUi() {
parent->addSeparator();
}
// The display plugins are created before the menu now, so we need to do this here to hide the menu bar
// now that it exists
if (_window && _window->isFullScreen()) {
@ -3638,24 +3659,6 @@ void Application::loadErrorDomain(QUrl domainURL) {
_fullSceneReceivedCounter++;
}
void Application::loadLoginScreenDomain(QUrl domainURL) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadLoginScreenDomain", Q_ARG(QUrl, domainURL));
return;
}
if (domainURL.isEmpty()) {
return;
}
auto namedPaths = prepareServerlessDomainContents(domainURL);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getDomainHandler().loadedLoginScreenDomain(namedPaths);
_fullSceneReceivedCounter++;
}
bool Application::importImage(const QString& urlString) {
qCDebug(interfaceapp) << "An image file has been dropped in";
QString filepath(urlString);
@ -3890,7 +3893,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
return;
}
if (hasFocus()) {
if (hasFocus() && !_loginDialogPoppedUp) {
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->keyPressEvent(event);
}
@ -5181,6 +5184,86 @@ void Application::init() {
_entitySimulation->setWorkloadSpace(getEntities()->getWorkloadSpace());
}
void Application::pauseUntilLoginDetermined() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "pauseUntilLoginDetermined");
return;
}
auto myAvatar = qApp->getMyAvatar();
myAvatar->setEnableMeshVisible(false);
const auto& nodeList = DependencyManager::get<NodeList>();
// disconnect domain handler.
nodeList->getDomainHandler().disconnect();
Menu::getInstance()->setVisible(false);
{
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
scriptEngines->reloadLocalFiles();
scriptEngines->loadControllerScripts();
}
}
void Application::resumeAfterLoginDialogActionTaken() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "resumeAfterLoginDialogActionTaken");
return;
}
auto myAvatar = qApp->getMyAvatar();
myAvatar->setEnableMeshVisible(true);
{
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
// this will force the model the look at the correct directory (weird order of operations issue)
scriptEngines->reloadLocalFiles();
// do this as late as possible so that all required subsystems are initialized
// If we've overridden the default scripts location, just load default scripts
// otherwise, load 'em all
// we just want to see if --scripts was set, we've already parsed it and done
// the change in PathUtils. Rather than pass that in the constructor, lets just
// look (this could be debated)
if (!_defaultScriptsLocation.exists()) {
scriptEngines->loadDefaultScripts();
scriptEngines->defaultScriptsLocationOverridden(true);
} else {
scriptEngines->loadScripts();
}
}
QVariant testProperty = property(hifi::properties::TEST);
qDebug() << testProperty;
if (testProperty.isValid()) {
const auto testScript = property(hifi::properties::TEST).toUrl();
// Set last parameter to exit interface when the test script finishes, if so requested
DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, quitWhenFinished);
// This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
if (arguments().contains("--url")) {
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [this, reply] {
handleSandboxStatus(reply);
});
}
} else {
PROFILE_RANGE(render, "GetSandboxStatus");
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [this, reply] {
handleSandboxStatus(reply);
});
}
const auto& nodeList = DependencyManager::get<NodeList>();
// disconnect domain handler.
nodeList->getDomainHandler().resetting();
Menu::getInstance()->setVisible(true);
}
void Application::loadAvatarScripts(const QVector<QString>& urls) {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
auto runningScripts = scriptEngines->getRunningScripts();
@ -6516,7 +6599,6 @@ void Application::updateWindowTitle() const {
auto nodeList = DependencyManager::get<NodeList>();
auto accountManager = DependencyManager::get<AccountManager>();
auto isInErrorState = nodeList->getDomainHandler().isInErrorState();
auto isInLoginScreenState = nodeList->getDomainHandler().isInLoginScreenState();
QString buildVersion = " - "
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
@ -6526,8 +6608,6 @@ void Application::updateWindowTitle() const {
QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" :
nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
// check for login state - login state needs empty connection status
connectionStatus = isInLoginScreenState ? "" : connectionStatus;
QString username = accountManager->getAccountInfo().getUsername();
setCrashAnnotation("username", username.toStdString());
@ -6536,8 +6616,6 @@ void Application::updateWindowTitle() const {
if (isServerlessMode()) {
if (isInErrorState) {
currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString();
} else if (isInLoginScreenState) {
currentPlaceName = "High Fidelity Interface";
} else {
currentPlaceName = "serverless: " + DependencyManager::get<AddressManager>()->getDomainURL().toString();
}
@ -6616,23 +6694,6 @@ void Application::goToErrorDomainURL(QUrl errorDomainURL) {
updateWindowTitle();
}
void Application::goToLoginScreenDomainURL(QUrl loginScreenDomainURL) {
// disable physics until we have enough information about our new location to not cause craziness.
resetPhysicsReadyInformation();
setIsServerlessMode(loginScreenDomainURL.scheme() != URL_SCHEME_HIFI);
// show avatar as a mesh and show hand controllers.
qApp->getMyAvatar()->setEnableMeshVisible(false);
DependencyManager::get<HMDScriptingInterface>()->requestShowHandControllers();
// set into login screen state.
emit loginScreenStateChanged(true);
if (isServerlessMode()) {
loadLoginScreenDomain(loginScreenDomainURL);
}
updateWindowTitle();
}
void Application::resettingDomain() {
_notifiedPacketVersionMismatchThisDomain = false;
@ -8430,10 +8491,9 @@ void Application::setShowBulletConstraintLimits(bool value) {
}
void Application::onDismissedLoginDialog() {
// TODO something with login dialog.
qDebug() << "dismissed login dialog";
_loginDialogPoppedUp = false;
loginDialogPoppedUp.set(false);
resumeAfterLoginDialogActionTaken();
}
void Application::setShowTrackedObjects(bool value) {

View file

@ -320,8 +320,6 @@ public:
void setOtherAvatarsReplicaCount(int count) { DependencyManager::get<AvatarHashMap>()->setReplicaCount(count); }
bool getLoginDialogPoppedUp() const { return _loginDialogPoppedUp; }
void pauseUntilLoginDetermined();
void resumeAfterLoginDialogActionTaken();
#if defined(Q_OS_ANDROID)
void beforeEnterBackground();
@ -341,8 +339,6 @@ signals:
void interstitialModeChanged(bool isInInterstitialMode);
void loginScreenStateChanged(bool isInLoginScreenState);
public slots:
QVector<EntityItemID> pasteEntities(float x, float y, float z);
bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset = nullptr);
@ -351,7 +347,6 @@ public slots:
void updateThreadPoolCount() const;
void updateSystemTabletMode();
void goToErrorDomainURL(QUrl errorDomainURL);
void goToLoginScreenDomainURL(QUrl loginScreenDomainURL);
Q_INVOKABLE void loadDialog();
Q_INVOKABLE void loadScriptURLDialog() const;
@ -446,7 +441,6 @@ public slots:
void loadServerlessDomain(QUrl domainURL);
void loadErrorDomain(QUrl domainURL);
void loadLoginScreenDomain(QUrl domainURL);
void setIsInterstitialMode(bool interstitialMode);
void updateVerboseLogging();
@ -516,6 +510,8 @@ private slots:
private:
void init();
void pauseUntilLoginDetermined();
void resumeAfterLoginDialogActionTaken();
bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event);
bool handleFileOpenEvent(QFileOpenEvent* event);
void cleanupBeforeQuit();
@ -664,6 +660,8 @@ private:
QPointer<LogDialog> _logDialog;
QPointer<EntityScriptServerLogDialog> _entityScriptServerLogDialog;
QDir _defaultScriptsLocation{""};
FileLogger* _logger;
TouchEvent _lastTouchEvent;

View file

@ -33,7 +33,6 @@ void ConnectionMonitor::init() {
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::redirectToLoginScreenDomainURL, this, &ConnectionMonitor::stopTimer);
connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState);
_timer.setSingleShot(true);

View file

@ -23,6 +23,7 @@
#include <SandboxUtils.h>
#include <SharedUtil.h>
#include <NetworkAccessManager.h>
#include <gl/GLHelpers.h>
#include "AddressManager.h"
#include "Application.h"
@ -40,6 +41,18 @@ extern "C" {
#endif
int main(int argc, const char* argv[]) {
#ifdef Q_OS_MAC
auto format = getDefaultOpenGLSurfaceFormat();
// Deal with some weirdness in the chromium context sharing on Mac.
// The primary share context needs to be 3.2, so that the Chromium will
// succeed in it's creation of it's command stub contexts.
format.setVersion(3, 2);
// This appears to resolve the issues with corrupted fonts on OSX. No
// idea why.
qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true");
// https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg
QSurfaceFormat::setDefaultFormat(format);
#endif
setupHifiApplication(BuildInfo::INTERFACE_NAME);
QStringList arguments;

View file

@ -115,6 +115,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
batch.resetViewTransform();
batch.setResourceTexture(0, _uiTexture);
geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId);
batch.setResourceTexture(0, nullptr);
}
void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) {

View file

@ -88,6 +88,7 @@ public:
// Move the OpenGL context to the present thread
// Extra code because of the widget 'wrapper' context
_context = context;
_context->doneCurrent();
_context->moveToThread(this);
}
@ -179,7 +180,9 @@ public:
_context->makeCurrent();
{
PROFILE_RANGE(render, "PluginPresent")
gl::globalLock();
currentPlugin->present();
gl::globalRelease(false);
CHECK_GL_ERROR();
}
_context->doneCurrent();

View file

@ -261,6 +261,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
batch.popProjectionJitter();
batch.setResourceTexture(0, nullptr);
}
bool WebEntityRenderer::hasWebSurface() {

View file

@ -25,25 +25,24 @@
#include "GLLogging.h"
#include "Config.h"
#include "GLHelpers.h"
#include "QOpenGLContextWrapper.h"
using namespace gl;
bool Context::enableDebugLogger() {
#if defined(Q_OS_MAC)
// OSX does not support GL_KHR_debug or GL_ARB_debug_output
return false;
#else
#if defined(DEBUG) || defined(USE_GLES)
static bool enableDebugLogger = true;
#else
static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL");
static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
#endif
static std::once_flag once;
std::call_once(once, [&] {
// If the previous run crashed, force GL debug logging on
if (qApp->property(hifi::properties::CRASHED).toBool()) {
enableDebugLogger = true;
}
});
return enableDebugLogger;
#endif
}
@ -68,8 +67,6 @@ void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) {
}
Context* Context::PRIMARY = nullptr;
Context::Context() {}
Context::Context(QWindow* window) {
@ -97,9 +94,6 @@ void Context::release() {
_context = nullptr;
#endif
_window = nullptr;
if (PRIMARY == this) {
PRIMARY = nullptr;
}
updateSwapchainMemoryCounter();
}
@ -235,16 +229,9 @@ typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShare
GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
void Context::create() {
if (!PRIMARY) {
PRIMARY = static_cast<Context*>(qApp->property(hifi::properties::gl::PRIMARY_CONTEXT).value<void*>());
}
if (PRIMARY) {
_version = PRIMARY->_version;
}
void Context::create(QOpenGLContext* shareContext) {
assert(0 != _hwnd);
assert(0 == _hdc);
auto hwnd = _hwnd;
@ -338,7 +325,10 @@ void Context::create() {
contextAttribs.push_back(0);
}
contextAttribs.push_back(0);
auto shareHglrc = PRIMARY ? PRIMARY->_hglrc : 0;
if (!shareContext) {
shareContext = qt_gl_global_share_context();
}
HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext);
_hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]);
}
@ -346,11 +336,6 @@ void Context::create() {
throw std::runtime_error("Could not create GL context");
}
if (!PRIMARY) {
PRIMARY = this;
qApp->setProperty(hifi::properties::gl::PRIMARY_CONTEXT, QVariant::fromValue((void*)PRIMARY));
}
if (!makeCurrent()) {
throw std::runtime_error("Could not make context current");
}
@ -363,12 +348,11 @@ void Context::create() {
#endif
OffscreenContext::~OffscreenContext() {
_window->deleteLater();
}
void OffscreenContext::create() {
void OffscreenContext::create(QOpenGLContext* shareContext) {
if (!_window) {
_window = new QWindow();
_window->setFlags(Qt::MSWindowsOwnDC);
@ -379,5 +363,5 @@ void OffscreenContext::create() {
qCDebug(glLogging) << "New Offscreen GLContext, window size = " << windowSize.width() << " , " << windowSize.height();
QGuiApplication::processEvents();
}
Parent::create();
Parent::create(shareContext);
}

View file

@ -21,6 +21,7 @@ class QSurface;
class QWindow;
class QOpenGLContext;
class QThread;
class QOpenGLDebugMessage;
#if defined(Q_OS_WIN)
#define GL_CUSTOM_CONTEXT
@ -30,7 +31,6 @@ namespace gl {
class Context {
protected:
QWindow* _window { nullptr };
static Context* PRIMARY;
static void destroyContext(QOpenGLContext* context);
#if defined(GL_CUSTOM_CONTEXT)
uint32_t _version { 0x0401 };
@ -48,6 +48,9 @@ namespace gl {
public:
static bool enableDebugLogger();
static void debugMessageHandler(const QOpenGLDebugMessage &debugMessage);
static void setupDebugLogging(QOpenGLContext* context);
Context();
Context(QWindow* window);
void release();
@ -59,14 +62,14 @@ namespace gl {
static void makeCurrent(QOpenGLContext* context, QSurface* surface);
void swapBuffers();
void doneCurrent();
virtual void create();
virtual void create(QOpenGLContext* shareContext = nullptr);
QOpenGLContext* qglContext();
void moveToThread(QThread* thread);
static size_t evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize);
static size_t getSwapchainMemoryUsage();
static void updateSwapchainMemoryUsage(size_t prevSize, size_t newSize);
private:
static std::atomic<size_t> _totalSwapchainMemoryUsage;
@ -81,7 +84,7 @@ namespace gl {
QWindow* _window { nullptr };
public:
virtual ~OffscreenContext();
void create() override;
void create(QOpenGLContext* shareContext = nullptr) override;
};
}

View file

@ -17,6 +17,8 @@
#include <QtPlatformHeaders/QWGLNativeContext>
#endif
#include <QtGui/QOpenGLDebugMessage>
#include "GLHelpers.h"
using namespace gl;
@ -47,6 +49,32 @@ void Context::moveToThread(QThread* thread) {
qglContext()->moveToThread(thread);
}
void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) {
auto severity = debugMessage.severity();
switch (severity) {
case QOpenGLDebugMessage::NotificationSeverity:
case QOpenGLDebugMessage::LowSeverity:
return;
default:
break;
}
qDebug(glLogging) << debugMessage;
return;
}
void Context::setupDebugLogging(QOpenGLContext *context) {
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context);
QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){
Context::debugMessageHandler(message);
});
if (logger->initialize()) {
logger->enableMessages();
logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
} else {
qCWarning(glLogging) << "OpenGL context does not support debugging";
}
}
#ifndef GL_CUSTOM_CONTEXT
bool Context::makeCurrent() {
updateSwapchainMemoryCounter();
@ -65,21 +93,29 @@ void Context::doneCurrent() {
}
}
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
void Context::create() {
void Context::create(QOpenGLContext* shareContext) {
_context = new QOpenGLContext();
if (PRIMARY) {
_context->setShareContext(PRIMARY->qglContext());
} else {
PRIMARY = this;
_context->setFormat(_window->format());
if (!shareContext) {
shareContext = qt_gl_global_share_context();
}
_context->setFormat(getDefaultOpenGLSurfaceFormat());
_context->create();
_context->setShareContext(shareContext);
_context->create();
_swapchainPixelSize = evalGLFormatSwapchainPixelSize(_context->format());
updateSwapchainMemoryCounter();
if (!makeCurrent()) {
throw std::runtime_error("Could not make context current");
}
if (enableDebugLogger()) {
setupDebugLogging(_context);
}
doneCurrent();
}
#endif

View file

@ -13,6 +13,8 @@
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLDebugLogger>
#include "Context.h"
size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format) {
size_t pixelSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize() + format.alphaBufferSize();
// We don't apply the length of the swap chain into this pixelSize since it is not vsible for the Process (on windows).
@ -34,14 +36,54 @@ bool gl::disableGl45() {
#endif
}
#ifdef Q_OS_MAC
#define SERIALIZE_GL_RENDERING
#endif
#ifdef SERIALIZE_GL_RENDERING
// This terrible terrible hack brought to you by the complete lack of reasonable
// OpenGL debugging tools on OSX. Without this serialization code, the UI textures
// frequently become 'glitchy' and get composited onto the main scene in what looks
// like a partially rendered state.
// This looks very much like either state bleeding across the contexts, or bad
// synchronization for the shared OpenGL textures. However, previous attempts to resolve
// it, even with gratuitous use of glFinish hasn't improved the situation
static std::mutex _globalOpenGLLock;
void gl::globalLock() {
_globalOpenGLLock.lock();
}
void gl::globalRelease(bool finish) {
if (finish) {
glFinish();
}
_globalOpenGLLock.unlock();
}
#else
void gl::globalLock() {}
void gl::globalRelease(bool finish) {}
#endif
void gl::getTargetVersion(int& major, int& minor) {
#if defined(USE_GLES)
major = 3;
minor = 2;
#else
#if defined(Q_OS_MAC)
major = 4;
minor = 1;
#else
major = 4;
minor = disableGl45() ? 1 : 5;
#endif
#endif
}
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
@ -57,6 +99,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
#else
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
#endif
if (gl::Context::enableDebugLogger()) {
format.setOption(QSurfaceFormat::DebugContext);
}
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS);
format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS);
@ -64,7 +109,6 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
::gl::getTargetVersion(major, minor);
format.setMajorVersion(major);
format.setMinorVersion(minor);
QSurfaceFormat::setDefaultFormat(format);
});
return format;
}

View file

@ -35,6 +35,9 @@ int glVersionToInteger(QString glVersion);
bool isRenderThread();
namespace gl {
void globalLock();
void globalRelease(bool finish = true);
void withSavedContext(const std::function<void()>& f);
bool checkGLError(const char* name);

View file

@ -63,10 +63,10 @@ int GLWidget::getDeviceHeight() const {
return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
}
void GLWidget::createContext() {
void GLWidget::createContext(QOpenGLContext* shareContext) {
_context = new gl::Context();
_context->setWindow(windowHandle());
_context->create();
_context->create(shareContext);
_context->makeCurrent();
_context->clear();
_context->doneCurrent();

View file

@ -29,7 +29,7 @@ public:
int getDeviceHeight() const;
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
QPaintEngine* paintEngine() const override;
void createContext();
void createContext(QOpenGLContext* shareContext = nullptr);
bool makeCurrent();
void doneCurrent();
void swapBuffers();

View file

@ -22,7 +22,7 @@ void GLWindow::createContext(QOpenGLContext* shareContext) {
void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) {
_context = new gl::Context();
_context->setWindow(this);
_context->create();
_context->create(shareContext);
_context->makeCurrent();
_context->clear();
}

View file

@ -33,6 +33,7 @@ OffscreenGLCanvas::OffscreenGLCanvas() :
_context(new QOpenGLContext),
_offscreenSurface(new QOffscreenSurface)
{
setFormat(getDefaultOpenGLSurfaceFormat());
}
OffscreenGLCanvas::~OffscreenGLCanvas() {
@ -49,12 +50,15 @@ OffscreenGLCanvas::~OffscreenGLCanvas() {
}
void OffscreenGLCanvas::setFormat(const QSurfaceFormat& format) {
_context->setFormat(format);
}
bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
if (nullptr != sharedContext) {
sharedContext->doneCurrent();
_context->setShareContext(sharedContext);
}
_context->setFormat(getDefaultOpenGLSurfaceFormat());
if (!_context->create()) {
qFatal("Failed to create OffscreenGLCanvas context");
}
@ -69,38 +73,16 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
if (!_context->makeCurrent(_offscreenSurface)) {
qFatal("Unable to make offscreen surface current");
}
_context->doneCurrent();
#else
if (!_offscreenSurface->isValid()) {
qFatal("Offscreen surface is invalid");
}
#endif
if (gl::Context::enableDebugLogger()) {
_context->makeCurrent(_offscreenSurface);
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);
connect(logger, &QOpenGLDebugLogger::messageLogged, this, &OffscreenGLCanvas::onMessageLogged);
logger->initialize();
logger->enableMessages();
logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
_context->doneCurrent();
}
return true;
}
void OffscreenGLCanvas::onMessageLogged(const QOpenGLDebugMessage& debugMessage) {
auto severity = debugMessage.severity();
switch (severity) {
case QOpenGLDebugMessage::NotificationSeverity:
case QOpenGLDebugMessage::LowSeverity:
return;
default:
break;
}
qDebug(glLogging) << debugMessage;
return;
}
bool OffscreenGLCanvas::makeCurrent() {
bool result = _context->makeCurrent(_offscreenSurface);
if (glGetString) {

View file

@ -18,11 +18,13 @@
class QOpenGLContext;
class QOffscreenSurface;
class QOpenGLDebugMessage;
class QSurfaceFormat;
class OffscreenGLCanvas : public QObject {
public:
OffscreenGLCanvas();
~OffscreenGLCanvas();
void setFormat(const QSurfaceFormat& format);
bool create(QOpenGLContext* sharedContext = nullptr);
bool makeCurrent();
void doneCurrent();
@ -35,9 +37,6 @@ public:
void setThreadContext();
static bool restoreThreadContext();
private slots:
void onMessageLogged(const QOpenGLDebugMessage &debugMessage);
protected:
void clearThreadContext();

View file

@ -13,6 +13,10 @@
#include <QOpenGLContext>
#ifdef Q_OS_WIN
#include <QtPlatformHeaders/QWGLNativeContext>
#endif
uint32_t QOpenGLContextWrapper::currentContextVersion() {
QOpenGLContext* context = QOpenGLContext::currentContext();
if (!context) {
@ -45,6 +49,19 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) {
_context->setFormat(format);
}
#ifdef Q_OS_WIN
void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) {
HGLRC result = 0;
if (context != nullptr) {
auto nativeHandle = context->nativeHandle();
if (nativeHandle.canConvert<QWGLNativeContext>()) {
result = nativeHandle.value<QWGLNativeContext>().context();
}
}
return result;
}
#endif
bool QOpenGLContextWrapper::create() {
return _context->create();
}

View file

@ -13,6 +13,7 @@
#define hifi_QOpenGLContextWrapper_h
#include <stdint.h>
#include <QtGlobal>
class QOpenGLContext;
class QSurface;
@ -21,6 +22,10 @@ class QThread;
class QOpenGLContextWrapper {
public:
#ifdef Q_OS_WIN
static void* nativeContext(QOpenGLContext* context);
#endif
QOpenGLContextWrapper();
QOpenGLContextWrapper(QOpenGLContext* context);
virtual ~QOpenGLContextWrapper();

View file

@ -708,37 +708,37 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) {
void GLBackend::releaseBuffer(GLuint id, Size size) const {
Lock lock(_trashMutex);
_buffersTrash.push_back({ id, size });
_currentFrameTrash.buffersTrash.push_back({ id, size });
}
void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const {
Lock lock(_trashMutex);
_externalTexturesTrash.push_back({ id, recycler });
_currentFrameTrash.externalTexturesTrash.push_back({ id, recycler });
}
void GLBackend::releaseTexture(GLuint id, Size size) const {
Lock lock(_trashMutex);
_texturesTrash.push_back({ id, size });
_currentFrameTrash.texturesTrash.push_back({ id, size });
}
void GLBackend::releaseFramebuffer(GLuint id) const {
Lock lock(_trashMutex);
_framebuffersTrash.push_back(id);
_currentFrameTrash.framebuffersTrash.push_back(id);
}
void GLBackend::releaseShader(GLuint id) const {
Lock lock(_trashMutex);
_shadersTrash.push_back(id);
_currentFrameTrash.shadersTrash.push_back(id);
}
void GLBackend::releaseProgram(GLuint id) const {
Lock lock(_trashMutex);
_programsTrash.push_back(id);
_currentFrameTrash.programsTrash.push_back(id);
}
void GLBackend::releaseQuery(GLuint id) const {
Lock lock(_trashMutex);
_queriesTrash.push_back(id);
_currentFrameTrash.queriesTrash.push_back(id);
}
void GLBackend::queueLambda(const std::function<void()> lambda) const {
@ -746,6 +746,81 @@ void GLBackend::queueLambda(const std::function<void()> lambda) const {
_lambdaQueue.push_back(lambda);
}
void GLBackend::FrameTrash::cleanup() {
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(fence);
{
std::vector<GLuint> ids;
ids.reserve(buffersTrash.size());
for (auto pair : buffersTrash) {
ids.push_back(pair.first);
}
if (!ids.empty()) {
glDeleteBuffers((GLsizei)ids.size(), ids.data());
}
}
{
std::vector<GLuint> ids;
ids.reserve(framebuffersTrash.size());
for (auto id : framebuffersTrash) {
ids.push_back(id);
}
if (!ids.empty()) {
glDeleteFramebuffers((GLsizei)ids.size(), ids.data());
}
}
{
std::vector<GLuint> ids;
ids.reserve(texturesTrash.size());
for (auto pair : texturesTrash) {
ids.push_back(pair.first);
}
if (!ids.empty()) {
glDeleteTextures((GLsizei)ids.size(), ids.data());
}
}
{
if (!externalTexturesTrash.empty()) {
std::vector<GLsync> fences;
fences.resize(externalTexturesTrash.size());
for (size_t i = 0; i < externalTexturesTrash.size(); ++i) {
fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
// External texture fences will be read in another thread/context, so we need a flush
glFlush();
size_t index = 0;
for (auto pair : externalTexturesTrash) {
auto fence = fences[index++];
pair.second(pair.first, fence);
}
}
}
for (auto id : programsTrash) {
glDeleteProgram(id);
}
for (auto id : shadersTrash) {
glDeleteShader(id);
}
{
std::vector<GLuint> ids;
ids.reserve(queriesTrash.size());
for (auto id : queriesTrash) {
ids.push_back(id);
}
if (!ids.empty()) {
glDeleteQueries((GLsizei)ids.size(), ids.data());
}
}
}
void GLBackend::recycle() const {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__)
{
@ -759,112 +834,16 @@ void GLBackend::recycle() const {
}
}
{
std::vector<GLuint> ids;
std::list<std::pair<GLuint, Size>> buffersTrash;
{
Lock lock(_trashMutex);
std::swap(_buffersTrash, buffersTrash);
}
ids.reserve(buffersTrash.size());
for (auto pair : buffersTrash) {
ids.push_back(pair.first);
}
if (!ids.empty()) {
glDeleteBuffers((GLsizei)ids.size(), ids.data());
}
while (!_previousFrameTrashes.empty()) {
_previousFrameTrashes.front().cleanup();
_previousFrameTrashes.pop_front();
}
_previousFrameTrashes.emplace_back();
{
std::vector<GLuint> ids;
std::list<GLuint> framebuffersTrash;
{
Lock lock(_trashMutex);
std::swap(_framebuffersTrash, framebuffersTrash);
}
ids.reserve(framebuffersTrash.size());
for (auto id : framebuffersTrash) {
ids.push_back(id);
}
if (!ids.empty()) {
glDeleteFramebuffers((GLsizei)ids.size(), ids.data());
}
}
{
std::vector<GLuint> ids;
std::list<std::pair<GLuint, Size>> texturesTrash;
{
Lock lock(_trashMutex);
std::swap(_texturesTrash, texturesTrash);
}
ids.reserve(texturesTrash.size());
for (auto pair : texturesTrash) {
ids.push_back(pair.first);
}
if (!ids.empty()) {
glDeleteTextures((GLsizei)ids.size(), ids.data());
}
}
{
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
{
Lock lock(_trashMutex);
std::swap(_externalTexturesTrash, externalTexturesTrash);
}
if (!externalTexturesTrash.empty()) {
std::vector<GLsync> fences;
fences.resize(externalTexturesTrash.size());
for (size_t i = 0; i < externalTexturesTrash.size(); ++i) {
fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
// External texture fences will be read in another thread/context, so we need a flush
glFlush();
size_t index = 0;
for (auto pair : externalTexturesTrash) {
auto fence = fences[index++];
pair.second(pair.first, fence);
}
}
}
{
std::list<GLuint> programsTrash;
{
Lock lock(_trashMutex);
std::swap(_programsTrash, programsTrash);
}
for (auto id : programsTrash) {
glDeleteProgram(id);
}
}
{
std::list<GLuint> shadersTrash;
{
Lock lock(_trashMutex);
std::swap(_shadersTrash, shadersTrash);
}
for (auto id : shadersTrash) {
glDeleteShader(id);
}
}
{
std::vector<GLuint> ids;
std::list<GLuint> queriesTrash;
{
Lock lock(_trashMutex);
std::swap(_queriesTrash, queriesTrash);
}
ids.reserve(queriesTrash.size());
for (auto id : queriesTrash) {
ids.push_back(id);
}
if (!ids.empty()) {
glDeleteQueries((GLsizei)ids.size(), ids.data());
}
Lock lock(_trashMutex);
_previousFrameTrashes.back().swap(_currentFrameTrash);
_previousFrameTrashes.back().fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
_textureManagement._transferEngine->manageMemory();

View file

@ -419,16 +419,34 @@ protected:
static const size_t INVALID_OFFSET = (size_t)-1;
bool _inRenderTransferPass{ false };
int _currentDraw{ -1 };
std::list<std::string> profileRanges;
struct FrameTrash {
GLsync fence = nullptr;
std::list<std::pair<GLuint, Size>> buffersTrash;
std::list<std::pair<GLuint, Size>> texturesTrash;
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
std::list<GLuint> framebuffersTrash;
std::list<GLuint> shadersTrash;
std::list<GLuint> programsTrash;
std::list<GLuint> queriesTrash;
void swap(FrameTrash& other) {
buffersTrash.swap(other.buffersTrash);
texturesTrash.swap(other.texturesTrash);
externalTexturesTrash.swap(other.externalTexturesTrash);
framebuffersTrash.swap(other.framebuffersTrash);
shadersTrash.swap(other.shadersTrash);
programsTrash.swap(other.programsTrash);
queriesTrash.swap(other.queriesTrash);
}
void cleanup();
};
mutable Mutex _trashMutex;
mutable std::list<std::pair<GLuint, Size>> _buffersTrash;
mutable std::list<std::pair<GLuint, Size>> _texturesTrash;
mutable std::list<std::pair<GLuint, Texture::ExternalRecycler>> _externalTexturesTrash;
mutable std::list<GLuint> _framebuffersTrash;
mutable std::list<GLuint> _shadersTrash;
mutable std::list<GLuint> _programsTrash;
mutable std::list<GLuint> _queriesTrash;
mutable FrameTrash _currentFrameTrash;
mutable std::list<FrameTrash> _previousFrameTrashes;
std::list<std::string> profileRanges;
mutable std::list<std::function<void()>> _lambdaQueue;
void renderPassTransfer(const Batch& batch);

View file

@ -62,16 +62,6 @@ DomainHandler::DomainHandler(QObject* parent) :
// stop the refresh timer if redirected to the error domain
connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop);
// stop the refresh timer if redirected to the login screen domain
connect(this, &DomainHandler::redirectToLoginScreenDomainURL, &_apiRefreshTimer, &QTimer::stop);
// stop the refresh timer if redirected to the login screen domain
connect(this, &DomainHandler::redirectToLoginScreenDomainURL, [this]() {
_isInLoginScreenState = true;
qCDebug(networking) << "Redirecting user to " << _loginScreenDomainURL;
});
}
void DomainHandler::disconnect() {
@ -123,7 +113,7 @@ void DomainHandler::softReset() {
QMetaObject::invokeMethod(&_settingsTimer, "stop");
// restart the API refresh timer in case we fail to connect and need to refresh information
if (!_isInErrorState || !_isInLoginScreenState) {
if (!_isInErrorState) {
QMetaObject::invokeMethod(&_apiRefreshTimer, "start");
}
}
@ -135,9 +125,6 @@ void DomainHandler::hardReset() {
_isInErrorState = false;
emit redirectErrorStateChanged(_isInErrorState);
_isInLoginScreenState = false;
emit loginScreenStateChanged(_isInLoginScreenState);
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
_pendingDomainID = QUuid();
_iceServerSockAddr = HifiSockAddr();
@ -176,11 +163,6 @@ void DomainHandler::setErrorDomainURL(const QUrl& url) {
return;
}
void DomainHandler::setLoginScreenDomainURL(const QUrl& url) {
_loginScreenDomainURL = url;
return;
}
void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) {
if (_sockAddr != sockAddr) {
// we should reset on a sockAddr change
@ -383,17 +365,6 @@ void DomainHandler::loadedErrorDomain(std::map<QString, QString> namedPaths) {
DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, QString());
}
void DomainHandler::loadedLoginScreenDomain(std::map<QString, QString> namedPaths) {
auto lookup = namedPaths.find("/");
QString viewpoint;
if (lookup != namedPaths.end()) {
viewpoint = lookup->second;
} else {
viewpoint = DOMAIN_SPAWNING_POINT;
}
DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, QString());
}
void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) {
_lastDomainConnectionError = reasonCode;
if (getInterstitialModeEnabled() && isHardRefusal(reasonCode)) {

View file

@ -58,9 +58,6 @@ public:
int getLastDomainConnectionError() { return _lastDomainConnectionError; }
QUrl getLoginScreenDomainURL(){ return _loginScreenDomainURL; }
void setLoginScreenDomainURL(const QUrl& url);
const QHostAddress& getIP() const { return _sockAddr.getAddress(); }
void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); }
@ -96,8 +93,6 @@ public:
void loadedErrorDomain(std::map<QString, QString> namedPaths);
void loadedLoginScreenDomain(std::map<QString, QString> namedPaths);
QString getViewPointFromNamedPath(QString namedPath);
bool hasSettings() const { return !_settingsObject.isEmpty(); }
@ -211,9 +206,6 @@ signals:
void redirectToErrorDomainURL(QUrl errorDomainURL);
void redirectErrorStateChanged(bool isInErrorState);
void redirectToLoginScreenDomainURL();
void loginScreenStateChanged(bool isInLoginScreenState);
void limitOfSilentDomainCheckInsReached();
private:
@ -227,7 +219,6 @@ private:
Node::LocalID _localID;
QUrl _domainURL;
QUrl _errorDomainURL;
QUrl _loginScreenDomainURL;
HifiSockAddr _sockAddr;
QUuid _assignmentUUID;
QUuid _connectionToken;

View file

@ -12,6 +12,7 @@
#include <gl/Config.h>
#include <gl/QOpenGLContextWrapper.h>
#include <gl/GLHelpers.h>
#include <QtQuick/QQuickWindow>
@ -114,6 +115,7 @@ void RenderEventHandler::onRender() {
PROFILE_RANGE(render_qml_gl, __FUNCTION__);
gl::globalLock();
if (!_shared->preRender()) {
return;
}
@ -139,11 +141,12 @@ void RenderEventHandler::onRender() {
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// Fence will be used in another thread / context, so a flush is required
glFlush();
_shared->updateTextureAndFence({ texture, fence });
// Fence will be used in another thread / context, so a flush is required
_shared->_quickWindow->resetOpenGLState();
}
gl::globalRelease();
}
void RenderEventHandler::onQuit() {
@ -167,4 +170,5 @@ void RenderEventHandler::onQuit() {
moveToThread(qApp->thread());
QThread::currentThread()->quit();
}
#endif
#endif

View file

@ -51,8 +51,10 @@ uint32_t TextureCache::acquireTexture(const QSize& size) {
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);
if (textureAndFence.second) {
glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED);
glDeleteSync((GLsync)textureAndFence.second);
}
return textureAndFence.first;
}
return createTexture(size);
@ -101,9 +103,11 @@ void TextureCache::destroyTexture(uint32_t 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);
if (fence) {
// 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);
}

View file

@ -28,6 +28,7 @@ static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStanda
static const bool HIFI_SCRIPT_DEBUGGABLES { true };
static const QString SETTINGS_KEY { "RunningScripts" };
static const QUrl DEFAULT_SCRIPTS_LOCATION { "file:///~//defaultScripts.js" };
static const QUrl CONTROLLER_SCRIPTS_LOCATION { "file:///~//system//controllers//controllerScripts.js" };
// Using a QVariantList so this is human-readable in the settings file
static Setting::Handle<QVariantList> runningScriptsHandle(SETTINGS_KEY, { QVariant(DEFAULT_SCRIPTS_LOCATION) });
@ -287,6 +288,10 @@ void ScriptEngines::loadDefaultScripts() {
loadScript(DEFAULT_SCRIPTS_LOCATION);
}
void ScriptEngines::loadControllerScripts() {
loadScript(CONTROLLER_SCRIPTS_LOCATION);
}
void ScriptEngines::loadOneScript(const QString& scriptFilename) {
loadScript(scriptFilename);
}

View file

@ -65,6 +65,7 @@ public:
void setDebugScriptUrl(const QString& url) { _debugScriptUrl = url; };
void loadDefaultScripts();
void loadControllerScripts();
void reloadLocalFiles();
QStringList getRunningScripts();

View file

@ -710,15 +710,6 @@ const GROUPS = [
decimals: 2,
propertyID: "particleRadius",
},
{
label: "Size Spread",
type: "slider",
min: 0,
max: 4,
step: 0.01,
decimals: 2,
propertyID: "radiusSpread",
},
{
label: "Size Start",
type: "slider",
@ -739,6 +730,15 @@ const GROUPS = [
propertyID: "radiusFinish",
fallbackProperty: "particleRadius",
},
{
label: "Size Spread",
type: "slider",
min: 0,
max: 4,
step: 0.01,
decimals: 2,
propertyID: "radiusSpread",
},
]
},
{
@ -783,15 +783,6 @@ const GROUPS = [
decimals: 2,
propertyID: "alpha",
},
{
label: "Alpha Spread",
type: "slider",
min: 0,
max: 1,
step: 0.01,
decimals: 2,
propertyID: "alphaSpread",
},
{
label: "Alpha Start",
type: "slider",
@ -812,6 +803,15 @@ const GROUPS = [
propertyID: "alphaFinish",
fallbackProperty: "alpha",
},
{
label: "Alpha Spread",
type: "slider",
min: 0,
max: 1,
step: 0.01,
decimals: 2,
propertyID: "alphaSpread",
},
]
},
{
@ -853,17 +853,6 @@ const GROUPS = [
unit: "deg",
propertyID: "particleSpin",
},
{
label: "Spin Spread",
type: "slider",
min: 0,
max: 360,
step: 1,
decimals: 0,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "spinSpread",
},
{
label: "Spin Start",
type: "slider",
@ -888,6 +877,17 @@ const GROUPS = [
propertyID: "spinFinish",
fallbackProperty: "particleSpin",
},
{
label: "Spin Spread",
type: "slider",
min: 0,
max: 360,
step: 1,
decimals: 0,
multiplier: DEGREES_TO_RADIANS,
unit: "deg",
propertyID: "spinSpread",
},
{
label: "Rotate with Entity",
type: "bool",
@ -1356,15 +1356,15 @@ function getPropertyInputElement(propertyID) {
}
function enableChildren(el, selector) {
var elSelectors = el.querySelectorAll(selector);
for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
let elSelectors = el.querySelectorAll(selector);
for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
elSelectors[selectorIndex].removeAttribute('disabled');
}
}
function disableChildren(el, selector) {
var elSelectors = el.querySelectorAll(selector);
for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
let elSelectors = el.querySelectorAll(selector);
for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
elSelectors[selectorIndex].setAttribute('disabled', 'disabled');
}
}
@ -1372,7 +1372,7 @@ function disableChildren(el, selector) {
function enableProperties() {
enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker");
enableChildren(document, ".colpick");
var elLocked = getPropertyInputElement("locked");
let elLocked = getPropertyInputElement("locked");
if (elLocked.checked === false) {
removeStaticUserData();
@ -1383,10 +1383,10 @@ function enableProperties() {
function disableProperties() {
disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker");
disableChildren(document, ".colpick");
for (var pickKey in colorPickers) {
for (let pickKey in colorPickers) {
colorPickers[pickKey].colpickHide();
}
var elLocked = getPropertyInputElement("locked");
let elLocked = getPropertyInputElement("locked");
if (elLocked.checked === true) {
if ($('#property-userData-editor').css('display') === "block") {
@ -1527,7 +1527,7 @@ function getPropertyValue(originalPropertyName) {
* PROPERTY UPDATE FUNCTIONS
*/
function updateProperty(originalPropertyName, propertyValue) {
function updateProperty(originalPropertyName, propertyValue, isParticleProperty) {
let propertyUpdate = {};
// if this is a compound property name (i.e. animation.running) then split it by . up to 3 times
let splitPropertyName = originalPropertyName.split('.');
@ -1546,8 +1546,8 @@ function updateProperty(originalPropertyName, propertyValue) {
propertyUpdate[originalPropertyName] = propertyValue;
}
// queue up particle property changes with the debounced sync to avoid
// causing particle emitting to reset each frame when updating values
if (properties[originalPropertyName].isParticleProperty) {
// causing particle emitting to reset excessively with each value change
if (isParticleProperty) {
Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) {
particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey];
});
@ -1570,30 +1570,29 @@ function updateProperties(propertiesToUpdate) {
}));
}
function createEmitTextPropertyUpdateFunction(propertyName) {
function createEmitTextPropertyUpdateFunction(propertyName, isParticleProperty) {
return function() {
updateProperty(propertyName, this.value);
updateProperty(propertyName, this.value, isParticleProperty);
};
}
function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) {
function createEmitCheckedPropertyUpdateFunction(propertyName, inverse, isParticleProperty) {
return function() {
updateProperty(propertyName, inverse ? !this.checked : this.checked);
updateProperty(propertyName, inverse ? !this.checked : this.checked, isParticleProperty);
};
}
function createEmitNumberPropertyUpdateFunction(propertyName, multiplier) {
function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, isParticleProperty) {
return function() {
if (multiplier === undefined) {
multiplier = 1;
}
let value = parseFloat(this.value) * multiplier;
updateProperty(propertyName, value);
updateProperty(propertyName, value, isParticleProperty);
};
}
function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier) {
function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier, isParticleProperty) {
return function () {
if (multiplier === undefined) {
multiplier = 1;
@ -1602,11 +1601,11 @@ function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier
x: elX.value * multiplier,
y: elY.value * multiplier
};
updateProperty(propertyName, newValue);
updateProperty(propertyName, newValue, isParticleProperty);
};
}
function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier) {
function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier, isParticleProperty) {
return function() {
if (multiplier === undefined) {
multiplier = 1;
@ -1616,26 +1615,26 @@ function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multi
y: elY.value * multiplier,
z: elZ.value * multiplier
};
updateProperty(propertyName, newValue);
updateProperty(propertyName, newValue, isParticleProperty);
};
}
function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue) {
function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue, isParticleProperty) {
return function() {
emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value);
emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value, isParticleProperty);
};
}
function emitColorPropertyUpdate(propertyName, red, green, blue) {
function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) {
let newValue = {
red: red,
green: green,
blue: blue
};
updateProperty(propertyName, newValue);
updateProperty(propertyName, newValue, isParticleProperty);
}
function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) {
function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString, isParticleProperty) {
if (subPropertyElement.checked) {
if (propertyValue.indexOf(subPropertyString)) {
propertyValue += subPropertyString + ',';
@ -1644,13 +1643,13 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen
// We've unchecked, so remove
propertyValue = propertyValue.replace(subPropertyString + ",", "");
}
updateProperty(propertyName, propertyValue);
updateProperty(propertyName, propertyValue, isParticleProperty);
}
function createImageURLUpdateFunction(propertyName) {
function createImageURLUpdateFunction(propertyName, isParticleProperty) {
return function () {
var newTextures = JSON.stringify({ "tex.picture": this.value });
updateProperty(propertyName, newTextures);
let newTextures = JSON.stringify({ "tex.picture": this.value });
updateProperty(propertyName, newTextures, isParticleProperty);
};
}
@ -1673,7 +1672,7 @@ function createStringProperty(property, elProperty, elLabel) {
elInput.readOnly = true;
}
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName));
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty));
elProperty.appendChild(elLabel);
elProperty.appendChild(elInput);
@ -1709,10 +1708,10 @@ function createBoolProperty(property, elProperty, elLabel) {
let subPropertyOf = propertyData.subPropertyOf;
if (subPropertyOf !== undefined) {
elInput.addEventListener('change', function() {
updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName);
updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName, property.isParticleProperty);
});
} else {
elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse));
elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, property.isParticleProperty));
}
return elInput;
@ -1745,7 +1744,7 @@ function createNumberProperty(property, elProperty, elLabel) {
elInput.value = defaultValue;
}
elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals));
elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals, property.isParticleProperty));
elProperty.appendChild(elLabel);
elProperty.appendChild(elInput);
@ -1791,7 +1790,7 @@ function createSliderProperty(property, elProperty, elLabel) {
if (propertyData.multiplier !== undefined) {
inputValue *= propertyData.multiplier;
}
updateProperty(property.name, inputValue);
updateProperty(property.name, inputValue, property.isParticleProperty);
};
elSlider.oninput = function (event) {
let sliderValue = event.target.value;
@ -1807,7 +1806,7 @@ function createSliderProperty(property, elProperty, elLabel) {
if (propertyData.multiplier !== undefined) {
sliderValue *= propertyData.multiplier;
}
updateProperty(property.name, sliderValue);
updateProperty(property.name, sliderValue, property.isParticleProperty);
};
elDiv.appendChild(elLabel);
@ -1843,8 +1842,8 @@ function createVec3Property(property, elProperty, elLabel) {
let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT],
propertyData.min, propertyData.max, propertyData.step);
let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY,
elInputZ, propertyData.multiplier);
let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ,
propertyData.multiplier, property.isParticleProperty);
elInputX.addEventListener('change', inputChangeFunction);
elInputY.addEventListener('change', inputChangeFunction);
elInputZ.addEventListener('change', inputChangeFunction);
@ -1876,8 +1875,8 @@ function createVec2Property(property, elProperty, elLabel) {
let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT],
propertyData.min, propertyData.max, propertyData.step);
let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX,
elInputY, propertyData.multiplier);
let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY,
propertyData.multiplier, property.isParticleProperty);
elInputX.addEventListener('change', inputChangeFunction);
elInputY.addEventListener('change', inputChangeFunction);
@ -1908,7 +1907,8 @@ function createColorProperty(property, elProperty, elLabel) {
let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP);
let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP);
let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB);
let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB,
property.isParticleProperty);
elInputR.addEventListener('change', inputChangeFunction);
elInputG.addEventListener('change', inputChangeFunction);
elInputB.addEventListener('change', inputChangeFunction);
@ -1964,7 +1964,7 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) {
elInput.add(option);
}
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName));
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty));
elProperty.appendChild(elLabel);
elProperty.appendChild(elInput);
@ -1991,7 +1991,7 @@ function createTextareaProperty(property, elProperty, elLabel) {
elInput.readOnly = true;
}
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName));
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty));
elProperty.appendChild(elInput);
@ -2057,9 +2057,9 @@ function createTextureProperty(property, elProperty, elLabel) {
elInput.imageLoad = imageLoad;
elInput.oninput = function (event) {
// Add throttle
var url = event.target.value;
let url = event.target.value;
imageLoad(url);
updateProperty(property.name, url)
updateProperty(property.name, url, property.isParticleProperty)
};
elInput.onchange = elInput.oninput;
@ -2200,7 +2200,7 @@ function reloadServerScripts() {
function copySkyboxURLToAmbientURL() {
let skyboxURL = getPropertyInputElement("skybox.url").value;
getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL;
updateProperty("ambientLight.ambientURL", skyboxURL);
updateProperty("ambientLight.ambientURL", skyboxURL, false);
}
@ -2215,13 +2215,13 @@ function clearUserData() {
showUserDataTextArea();
showNewJSONEditorButton();
hideSaveUserDataButton();
updateProperty('userData', elUserData.value);
updateProperty('userData', elUserData.value, false);
}
function newJSONEditor() {
deleteJSONEditor();
createJSONEditor();
var data = {};
let data = {};
setEditorJSON(data);
hideUserDataTextArea();
hideNewJSONEditorButton();
@ -2233,7 +2233,7 @@ function saveUserData() {
}
function setUserDataFromEditor(noUpdate) {
var json = null;
let json = null;
try {
json = editor.get();
} catch (e) {
@ -2242,7 +2242,7 @@ function setUserDataFromEditor(noUpdate) {
if (json === null) {
return;
} else {
var text = editor.getText();
let text = editor.getText();
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
@ -2255,15 +2255,15 @@ function setUserDataFromEditor(noUpdate) {
);
return;
} else {
updateProperty('userData', text);
updateProperty('userData', text, false);
}
}
}
function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) {
var propertyUpdate = {};
var parsedData = {};
var keysToBeRemoved = removeKeys ? removeKeys : [];
let propertyUpdate = {};
let parsedData = {};
let keysToBeRemoved = removeKeys ? removeKeys : [];
try {
if ($('#property-userData-editor').css('height') !== "0px") {
// if there is an expanded, we want to use its json.
@ -2278,14 +2278,14 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
if (!(groupName in parsedData)) {
parsedData[groupName] = {};
}
var keys = Object.keys(updateKeyPair);
let keys = Object.keys(updateKeyPair);
keys.forEach(function (key) {
if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") {
if (updateKeyPair[key] instanceof Element) {
if (updateKeyPair[key].type === "checkbox") {
parsedData[groupName][key] = updateKeyPair[key].checked;
} else {
var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value);
let val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value);
parsedData[groupName][key] = val;
}
} else {
@ -2318,8 +2318,8 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
var editor = null;
function createJSONEditor() {
var container = document.getElementById("property-userData-editor");
var options = {
let container = document.getElementById("property-userData-editor");
let options = {
search: false,
mode: 'tree',
modes: ['code', 'tree'],
@ -2331,7 +2331,7 @@ function createJSONEditor() {
alert('JSON editor:' + e);
},
onChange: function() {
var currentJSONString = editor.getText();
let currentJSONString = editor.getText();
if (currentJSONString === '{"":""}') {
return;
@ -2432,13 +2432,13 @@ function clearMaterialData() {
showMaterialDataTextArea();
showNewJSONMaterialEditorButton();
hideSaveMaterialDataButton();
updateProperty('materialData', elMaterialData.value);
updateProperty('materialData', elMaterialData.value, false);
}
function newJSONMaterialEditor() {
deleteJSONMaterialEditor();
createJSONMaterialEditor();
var data = {};
let data = {};
setMaterialEditorJSON(data);
hideMaterialDataTextArea();
hideNewJSONMaterialEditorButton();
@ -2450,7 +2450,7 @@ function saveMaterialData() {
}
function setMaterialDataFromEditor(noUpdate) {
var json = null;
let json = null;
try {
json = materialEditor.get();
} catch (e) {
@ -2459,7 +2459,7 @@ function setMaterialDataFromEditor(noUpdate) {
if (json === null) {
return;
} else {
var text = materialEditor.getText();
let text = materialEditor.getText();
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
@ -2472,7 +2472,7 @@ function setMaterialDataFromEditor(noUpdate) {
);
return;
} else {
updateProperty('materialData', text);
updateProperty('materialData', text, false);
}
}
}
@ -2480,8 +2480,8 @@ function setMaterialDataFromEditor(noUpdate) {
var materialEditor = null;
function createJSONMaterialEditor() {
var container = document.getElementById("property-materialData-editor");
var options = {
let container = document.getElementById("property-materialData-editor");
let options = {
search: false,
mode: 'tree',
modes: ['code', 'tree'],
@ -2493,7 +2493,7 @@ function createJSONMaterialEditor() {
alert('JSON editor:' + e);
},
onChange: function() {
var currentJSONString = materialEditor.getText();
let currentJSONString = materialEditor.getText();
if (currentJSONString === '{"":""}') {
return;
@ -2583,11 +2583,11 @@ function saveJSONMaterialData(noUpdate) {
}
function bindAllNonJSONEditorElements() {
var inputs = $('input');
var i;
let inputs = $('input');
let i;
for (i = 0; i < inputs.length; ++i) {
var input = inputs[i];
var field = $(input);
let input = inputs[i];
let field = $(input);
// TODO FIXME: (JSHint) Functions declared within loops referencing
// an outer scoped variable may lead to confusing semantics.
field.on('focus', function(e) {
@ -2650,7 +2650,7 @@ function setDropdownValue(event) {
*/
function setTextareaScrolling(element) {
var isScrolling = element.scrollHeight > element.offsetHeight;
let isScrolling = element.scrollHeight > element.offsetHeight;
element.setAttribute("scrolling", isScrolling ? "true" : "false");
}
@ -3236,22 +3236,22 @@ function loaded() {
let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace");
let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh");
elParentMaterialNameString.addEventListener('change', function () {
updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value);
updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value, false);
});
elParentMaterialNameNumber.addEventListener('change', function () {
updateProperty("parentMaterialName", this.value);
updateProperty("parentMaterialName", this.value, false);
});
elParentMaterialNameCheckbox.addEventListener('change', function () {
if (this.checked) {
updateProperty("parentMaterialName", elParentMaterialNameNumber.value);
updateProperty("parentMaterialName", elParentMaterialNameNumber.value, false);
showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString);
} else {
updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value);
updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value, false);
showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString);
}
});
getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures'));
getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false));
// Collapsible sections
let elCollapsible = document.getElementsByClassName("section-header");
@ -3346,12 +3346,12 @@ function loaded() {
let propertyID = elDropdown.getAttribute("propertyID");
let property = properties[propertyID];
property.elInput = dt;
dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name));
dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name, property.isParticleProperty));
}
elDropdowns = document.getElementsByTagName("select");
while (elDropdowns.length > 0) {
var el = elDropdowns[0];
let el = elDropdowns[0];
el.parentNode.removeChild(el);
elDropdowns = document.getElementsByTagName("select");
}

View file

@ -0,0 +1,77 @@
/****************************************************************************
**
** 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 QtWebEngine 1.5
Item {
width: 640
height: 480
Rectangle {
width: 5
height: 5
color: "red"
ColorAnimation on color { loops: Animation.Infinite; from: "red"; to: "yellow"; duration: 1000 }
}
WebEngineView {
id: root
url: "https://google.com/"
x: 6; y: 6;
width: parent.width * 0.8
height: parent.height * 0.8
}
}

View file

@ -0,0 +1,60 @@
#include "MacQml.h"
#include <cmath>
#include <QtQuick/QQuickItem>
#include <SharedUtil.h>
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
void MacQml::update() {
auto rootItem =_surface->getRootItem();
float now = sinf(secTimestampNow());
rootItem->setProperty("level", fabs(now));
rootItem->setProperty("muted", now > 0.0f);
rootItem->setProperty("statsValue", rand());
// Fetch any new textures
TextureAndFence newTextureAndFence;
if (_surface->fetchTexture(newTextureAndFence)) {
if (_texture != 0) {
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_discardLamdba(_texture, readFence);
}
_texture = newTextureAndFence.first;
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
}
}
void MacQml::init() {
Parent::init();
_glf.glGenFramebuffers(1, &_fbo);
_surface.reset(new hifi::qml::OffscreenSurface());
//QUrl url =getTestResource("qml/main.qml");
QUrl url = getTestResource("qml/MacQml.qml");
hifi::qml::QmlContextObjectCallback callback =[](QQmlContext* context, QQuickItem* item) {
};
_surface->load(url, callback);
_surface->resize(_window->size());
_surface->resume();
}
void MacQml::draw() {
auto size = _window->geometry().size();
if (_texture) {
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
_glf.glBlitFramebuffer(
// src coordinates
0, 0, size.width(), size.height(),
// dst coordinates
0, 0, size.width(), size.height(),
// blit mask and filter
GL_COLOR_BUFFER_BIT, GL_NEAREST);
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
}

View file

@ -0,0 +1,16 @@
#include "TestCase.h"
#include <qml/OffscreenSurface.h>
class MacQml : public TestCase {
using Parent = TestCase;
public:
GLuint _texture{ 0 };
QmlPtr _surface;
GLuint _fbo{ 0 };
MacQml(const QWindow* window) : Parent(window) {}
void update() override;
void init() override;
void draw() override;
};

View file

@ -0,0 +1,131 @@
#include "StressWeb.h"
#include <QtQuick/QQuickItem>
#include <SharedUtil.h>
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
static const int DEFAULT_MAX_FPS = 10;
static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" };
static const char* URL_PROPERTY{ "url" };
QString StressWeb::getSourceUrl(bool video) {
static const std::vector<QString> SOURCE_URLS{
"https://www.reddit.com/wiki/random",
"https://en.wikipedia.org/wiki/Wikipedia:Random",
"https://slashdot.org/",
};
static const std::vector<QString> VIDEO_SOURCE_URLS{
"https://www.youtube.com/watch?v=gDXwhHm4GhM",
"https://www.youtube.com/watch?v=Ch_hoYPPeGc",
};
const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS;
auto index = rand() % sourceUrls.size();
return sourceUrls[index];
}
void StressWeb::buildSurface(QmlInfo& qmlInfo, bool video) {
++_surfaceCount;
auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f));
auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs);
qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow();
qmlInfo.texture = 0;
qmlInfo.surface.reset(new hifi::qml::OffscreenSurface());
qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) {
item->setProperty(URL_PROPERTY, getSourceUrl(video));
});
qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS);
qmlInfo.surface->resize(_qmlSize);
qmlInfo.surface->resume();
}
void StressWeb::destroySurface(QmlInfo& qmlInfo) {
auto& surface = qmlInfo.surface;
auto& currentTexture = qmlInfo.texture;
if (currentTexture) {
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_discardLamdba(currentTexture, readFence);
}
auto webView = surface->getRootItem();
if (webView) {
// stop loading
QMetaObject::invokeMethod(webView, "stop");
webView->setProperty(URL_PROPERTY, "about:blank");
}
surface->pause();
surface.reset();
}
void StressWeb::update() {
auto now = usecTimestampNow();
// Fetch any new textures
for (size_t x = 0; x < DIVISIONS_X; ++x) {
for (size_t y = 0; y < DIVISIONS_Y; ++y) {
auto& qmlInfo = _surfaces[x][y];
if (!qmlInfo.surface) {
if (now < _createStopTime && randFloat() > 0.99f) {
buildSurface(qmlInfo, x == 0 && y == 0);
} else {
continue;
}
}
if (now > qmlInfo.lifetime) {
destroySurface(qmlInfo);
continue;
}
auto& surface = qmlInfo.surface;
auto& currentTexture = qmlInfo.texture;
TextureAndFence newTextureAndFence;
if (surface->fetchTexture(newTextureAndFence)) {
if (currentTexture != 0) {
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_discardLamdba(currentTexture, readFence);
}
currentTexture = newTextureAndFence.first;
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
}
}
}
}
void StressWeb::init() {
Parent::init();
_createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND);
_glf.glGenFramebuffers(1, &_fbo);
}
void StressWeb::draw() {
auto size = _window->geometry().size();
auto incrementX = size.width() / DIVISIONS_X;
auto incrementY = size.height() / DIVISIONS_Y;
for (uint32_t x = 0; x < DIVISIONS_X; ++x) {
for (uint32_t y = 0; y < DIVISIONS_Y; ++y) {
auto& qmlInfo = _surfaces[x][y];
if (!qmlInfo.surface || !qmlInfo.texture) {
continue;
}
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0);
_glf.glBlitFramebuffer(
// src coordinates
0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1,
// dst coordinates
incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1),
// blit mask and filter
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}

View file

@ -0,0 +1,34 @@
#include "TestCase.h"
#include <array>
#include <qml/OffscreenSurface.h>
#define DIVISIONS_X 5
#define DIVISIONS_Y 5
class StressWeb : public TestCase {
using Parent = TestCase;
public:
using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>;
struct QmlInfo {
QmlPtr surface;
GLuint texture{ 0 };
uint64_t lifetime{ 0 };
};
size_t _surfaceCount{ 0 };
uint64_t _createStopTime{ 0 };
const QSize _qmlSize{ 640, 480 };
std::array<std::array<QmlInfo, DIVISIONS_Y>, DIVISIONS_X> _surfaces;
GLuint _fbo{ 0 };
StressWeb(const QWindow* window) : Parent(window) {}
static QString getSourceUrl(bool video);
void buildSurface(QmlInfo& qmlInfo, bool video);
void destroySurface(QmlInfo& qmlInfo);
void update() override;
void init() override;
void draw() override;
};

View file

@ -0,0 +1,25 @@
#include "TestCase.h"
#include <QtCore/QLoggingCategory>
#include <QtCore/QDir>
void TestCase::destroy() {
}
void TestCase::update() {
}
void TestCase::init() {
_glf.initializeOpenGLFunctions();
_discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda();
}
QUrl TestCase::getTestResource(const QString& relativePath) {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../")) + "/";
qDebug() << "Resources Path: " << dir;
}
return QUrl::fromLocalFile(dir + relativePath);
}

View file

@ -0,0 +1,23 @@
#pragma once
#include <functional>
#include <QtGui/QWindow>
#include <QtGui/QOpenGLFunctions_4_1_Core>
#include <qml/OffscreenSurface.h>
class TestCase {
public:
using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>;
using Builder = std::function<TestCase*(const QWindow*)>;
TestCase(const QWindow* window) : _window(window) {}
virtual void init();
virtual void destroy();
virtual void update();
virtual void draw() = 0;
static QUrl getTestResource(const QString& relativePath);
protected:
QOpenGLFunctions_4_1_Core _glf;
const QWindow* _window;
std::function<void(uint32_t, void*)> _discardLamdba;
};

View file

@ -43,6 +43,11 @@
#include <qml/OffscreenSurface.h>
#include <unordered_set>
#include <array>
#include <gl/GLHelpers.h>
#include <gl/Context.h>
#include "TestCase.h"
#include "MacQml.h"
namespace gl {
extern void initModuleGl();
@ -67,53 +72,37 @@ QUrl getTestResource(const QString& relativePath) {
return QUrl::fromLocalFile(dir + relativePath);
}
#define DIVISIONS_X 5
#define DIVISIONS_Y 5
using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>;
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
struct QmlInfo {
QmlPtr surface;
GLuint texture{ 0 };
uint64_t lifetime{ 0 };
};
class TestWindow : public QWindow {
public:
TestWindow();
TestWindow(const TestCase::Builder& caseBuilder);
private:
QOpenGLContext _glContext;
OffscreenGLCanvas _sharedContext;
std::array<std::array<QmlInfo, DIVISIONS_Y>, DIVISIONS_X> _surfaces;
TestCase* _testCase{ nullptr };
QOpenGLFunctions_4_1_Core _glf;
std::function<void(uint32_t, void*)> _discardLamdba;
QSize _size;
size_t _surfaceCount{ 0 };
GLuint _fbo{ 0 };
const QSize _qmlSize{ 640, 480 };
bool _aboutToQuit{ false };
uint64_t _createStopTime;
void initGl();
void updateSurfaces();
void buildSurface(QmlInfo& qmlInfo, bool allowVideo);
void destroySurface(QmlInfo& qmlInfo);
void resizeWindow(const QSize& size);
void draw();
void resizeEvent(QResizeEvent* ev) override;
};
TestWindow::TestWindow() {
TestWindow::TestWindow(const TestCase::Builder& builder) {
Setting::init();
_testCase = builder(this);
setSurfaceType(QSurface::OpenGLSurface);
qmlRegisterType<QTestItem>("Hifi", 1, 0, "TestItem");
show();
_createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND);
resize(QSize(800, 600));
@ -129,162 +118,84 @@ TestWindow::TestWindow() {
});
}
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
OffscreenGLCanvas* _chromiumShareContext{ nullptr};
void TestWindow::initGl() {
_glContext.setFormat(format());
auto globalShareContext = qt_gl_global_share_context();
if (globalShareContext) {
_glContext.setShareContext(globalShareContext);
globalShareContext->makeCurrent(this);
gl::Context::setupDebugLogging(globalShareContext);
globalShareContext->doneCurrent();
}
if (!_glContext.create() || !_glContext.makeCurrent(this)) {
qFatal("Unable to intialize Window GL context");
}
gl::Context::setupDebugLogging(&_glContext);
gl::initModuleGl();
_glf.initializeOpenGLFunctions();
_glf.glGenFramebuffers(1, &_fbo);
if (!_sharedContext.create(&_glContext) || !_sharedContext.makeCurrent()) {
qFatal("Unable to intialize Shared GL context");
}
hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext());
_discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda();
if (!globalShareContext) {
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->setObjectName("ChromiumShareContext");
_chromiumShareContext->create(&_glContext);
if (!_chromiumShareContext->makeCurrent()) {
qFatal("Unable to make chromium shared context current");
}
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
_chromiumShareContext->doneCurrent();
}
// Restore the GL widget context
if (!_glContext.makeCurrent(this)) {
qFatal("Unable to make window context current");
}
_testCase->init();
}
void TestWindow::resizeWindow(const QSize& size) {
_size = size;
}
static const int DEFAULT_MAX_FPS = 10;
static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" };
static const char* URL_PROPERTY{ "url" };
QString getSourceUrl(bool video) {
static const std::vector<QString> SOURCE_URLS{
"https://www.reddit.com/wiki/random",
"https://en.wikipedia.org/wiki/Wikipedia:Random",
"https://slashdot.org/",
};
static const std::vector<QString> VIDEO_SOURCE_URLS{
"https://www.youtube.com/watch?v=gDXwhHm4GhM",
"https://www.youtube.com/watch?v=Ch_hoYPPeGc",
};
const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS;
auto index = rand() % sourceUrls.size();
return sourceUrls[index];
}
void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) {
++_surfaceCount;
auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f));
auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs);
qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow();
qmlInfo.texture = 0;
qmlInfo.surface.reset(new hifi::qml::OffscreenSurface());
qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) {
item->setProperty(URL_PROPERTY, getSourceUrl(video));
});
qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS);
qmlInfo.surface->resize(_qmlSize);
qmlInfo.surface->resume();
}
void TestWindow::destroySurface(QmlInfo& qmlInfo) {
auto& surface = qmlInfo.surface;
auto& currentTexture = qmlInfo.texture;
if (currentTexture) {
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_discardLamdba(currentTexture, readFence);
}
auto webView = surface->getRootItem();
if (webView) {
// stop loading
QMetaObject::invokeMethod(webView, "stop");
webView->setProperty(URL_PROPERTY, "about:blank");
}
surface->pause();
surface.reset();
}
void TestWindow::updateSurfaces() {
auto now = usecTimestampNow();
// Fetch any new textures
for (size_t x = 0; x < DIVISIONS_X; ++x) {
for (size_t y = 0; y < DIVISIONS_Y; ++y) {
auto& qmlInfo = _surfaces[x][y];
if (!qmlInfo.surface) {
if (now < _createStopTime && randFloat() > 0.99f) {
buildSurface(qmlInfo, x == 0 && y == 0);
} else {
continue;
}
}
if (now > qmlInfo.lifetime) {
destroySurface(qmlInfo);
continue;
}
auto& surface = qmlInfo.surface;
auto& currentTexture = qmlInfo.texture;
TextureAndFence newTextureAndFence;
if (surface->fetchTexture(newTextureAndFence)) {
if (currentTexture != 0) {
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_discardLamdba(currentTexture, readFence);
}
currentTexture = newTextureAndFence.first;
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
}
}
}
}
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;
}
updateSurfaces();
auto size = this->geometry().size();
auto incrementX = size.width() / DIVISIONS_X;
auto incrementY = size.height() / DIVISIONS_Y;
_testCase->update();
auto size = geometry().size();
_glf.glViewport(0, 0, size.width(), size.height());
_glf.glClearColor(1, 0, 0, 1);
_glf.glClear(GL_COLOR_BUFFER_BIT);
for (uint32_t x = 0; x < DIVISIONS_X; ++x) {
for (uint32_t y = 0; y < DIVISIONS_Y; ++y) {
auto& qmlInfo = _surfaces[x][y];
if (!qmlInfo.surface || !qmlInfo.texture) {
continue;
}
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0);
_glf.glBlitFramebuffer(
// src coordinates
0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1,
// dst coordinates
incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1),
// blit mask and filter
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
_testCase->draw();
_glContext.swapBuffers(this);
}
@ -292,19 +203,15 @@ void TestWindow::resizeEvent(QResizeEvent* ev) {
resizeWindow(ev->size());
}
int main(int argc, char** argv) {
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
int main(int argc, char** argv) {
auto format = getDefaultOpenGLSurfaceFormat();
format.setVersion(4, 1);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
format.setOption(QSurfaceFormat::DebugContext);
QSurfaceFormat::setDefaultFormat(format);
// setFormat(format);
QGuiApplication app(argc, argv);
TestWindow window;
TestCase::Builder builder = [](const QWindow* window)->TestCase*{ return new MacQml(window); };
TestWindow window(builder);
return app.exec();
}