diff --git a/.gitignore b/.gitignore index dfffe2ac51..f45572c388 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,12 @@ TAGS # ignore node files for the console node_modules npm-debug.log + +# Android studio files +*___jb_old___ + +# Generated assets for Android +android/app/src/main/assets + +# Resource binary file +interface/compiledResources \ No newline at end of file diff --git a/BUILD_LINUX_CHEATSHEET.md b/BUILD_LINUX_CHEATSHEET.md index 7d77f5d685..9e7534418a 100644 --- a/BUILD_LINUX_CHEATSHEET.md +++ b/BUILD_LINUX_CHEATSHEET.md @@ -1,5 +1,7 @@ - # this guide is specific to Ubuntu 16.04. - # deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com +## This guide is specific to Ubuntu 16.04. +Deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com + +``` sudo su - apt-get -y update apt-get install -y software-properties-common @@ -8,20 +10,27 @@ add-apt-repository "deb http://debian.highfidelity.com stable main" apt-get -y update apt-get install -y hifi-domain-server apt-get install -y hifi-assignment-client +``` - # When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to: +When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to: +``` apt-get install -y hifi-dev-domain-server apt-get install -y hifi-dev-assignment-client +``` - # domain server and assignment clients should already be running. The processes are controlled via: +Domain server and assignment clients should already be running. The processes are controlled via: +``` systemctl start hifi-domain-server systemctl stop hifi-domain-server +``` - # Once the machine is setup and processes are running you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (further customizations can be done via http://IPAddress:40100). - - # The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time. - # As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly). +Once the machine is setup and processes are running, you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (Further customizations can be done via http://IPAddress:40100). + +The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time. +As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly). To do this you can modify /etc/crontab by adding the following lines +``` 0 */1 * * * root apt-get update 1 */1 * * * root apt-get install --only-upgrade -y hifi-domain-server 2 */1 * * * root apt-get install --only-upgrade -y hifi-assignment-client +``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 34ff672067..bbec4a0884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,9 +26,11 @@ endif() if (ANDROID OR UWP) option(BUILD_SERVER "Build server components" OFF) option(BUILD_TOOLS "Build tools" OFF) + option(BUILD_INSTALLER "Build installer" OFF) else() option(BUILD_SERVER "Build server components" ON) option(BUILD_TOOLS "Build tools" ON) + option(BUILD_INSTALLER "Build installer" ON) endif() if (SERVER_ONLY) @@ -40,25 +42,53 @@ else() endif() if (ANDROID) - set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) - set(PLATFORM_GL_BACKEND gpu-gles) + option(USE_GLES "Use OpenGL ES" ON) + set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) else () - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) - set(PLATFORM_GL_BACKEND gpu-gl) + option(USE_GLES "Use OpenGL ES" OFF) + set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) endif () +if (USE_GLES AND (NOT ANDROID)) + option(DISABLE_QML "Disable QML" ON) +else() + option(DISABLE_QML "Disable QML" OFF) +endif() +option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF) + + + +set(PLATFORM_QT_GL OpenGL) + +if (USE_GLES) + add_definitions(-DUSE_GLES) + set(PLATFORM_GL_BACKEND gpu-gles) +else() + set(PLATFORM_GL_BACKEND gpu-gl) +endif() + + foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}") endforeach() -option(BUILD_INSTALLER "Build installer" ON) - MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS}) MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER}) +MESSAGE(STATUS "GL ES: " ${USE_GLES}) + +if (DISABLE_QML) +MESSAGE(STATUS "QML disabled!") +add_definitions(-DDISABLE_QML) +endif() + +if (DISABLE_KTX_CACHE) +MESSAGE(STATUS "KTX cache disabled!") +add_definitions(-DDISABLE_KTX_CACHE) +endif() if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING}) MESSAGE(STATUS "Memory debugging is enabled") diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 5682611151..4c50cd8609 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,26 +1,28 @@ set(TARGET_NAME native-lib) -setup_hifi_library(Gui Qml Quick) - -# Minimal dependencies for testing UI compositing -#link_hifi_libraries(shared networking gl ui) - -link_hifi_libraries( - shared networking octree - script-engine recording trackers - gl ktx image gpu gpu-gles render render-utils - physics - audio audio-client - ui midi controllers pointers - graphics model-networking fbx animation - entities entities-renderer - avatars avatars-renderer - ui-plugins input-plugins - # display-plugins - # auto-updater -) - - -target_link_libraries(native-lib android log m) +setup_hifi_library() +link_hifi_libraries(shared networking gl gpu image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND}) target_opengl() target_bullet() +set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface") +add_subdirectory("${INTERFACE_DIR}" "libraries/interface") +include_directories("${INTERFACE_DIR}/src") + +target_link_libraries(native-lib android log m interface) + +set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/") +target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src") +target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui) + +# finished libraries +# core -> qt +# networking -> openssl, tbb +# fbx -> draco +# physics -> bullet +# entities-renderer -> polyvox + +# unfinished libraries +# image -> nvtt (doesn't look good, but can be made optional) +# script-engine -> quazip (probably not required for the android client) + + diff --git a/android/app/build.gradle b/android/app/build.gradle index 066eb7da3d..1cff708c3b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,10 +1,11 @@ apply plugin: 'com.android.application' - android { compileSdkVersion 26 + //buildToolsVersion '27.0.3' + defaultConfig { - applicationId "com.highfidelity.iface" + applicationId "io.highfidelity.hifiinterface" minSdkVersion 24 targetSdkVersion 26 versionCode 1 @@ -21,24 +22,19 @@ android { '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, - '-DBUILD_BRANCH=' + BUILD_BRANCH + '-DBUILD_BRANCH=' + BUILD_BRANCH, + '-DDISABLE_QML=ON', + '-DDISABLE_KTX_CACHE=ON' } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } } - buildTypes { - applicationVariants.all { variant -> - variant.outputs.all { - if (RELEASE_NUMBER != '0') { - outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" - } - } - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -50,11 +46,58 @@ android { path '../../CMakeLists.txt' } } + + applicationVariants.all { variant -> + // Our asset contents depend on items produced in the CMake build + // so our merge has to depend on the external native build + variant.externalNativeBuildTasks.each { task -> + variant.mergeResources.dependsOn(task) + } + + variant.mergeAssets.doLast { + def assetList = new LinkedList() + def youngestLastModified = 0 + + // Copy the compiled resources generated by the external native build + copy { + from new File(projectDir, "../../interface/compiledResources") + into outputDir + duplicatesStrategy DuplicatesStrategy.INCLUDE + eachFile { details -> + youngestLastModified = Math.max(youngestLastModified, details.lastModified) + assetList.add(details.path) + } + } + + // Copy the scripts directory + copy { + from new File(projectDir, "../../scripts") + into new File(outputDir, "scripts") + duplicatesStrategy DuplicatesStrategy.INCLUDE + eachFile { details-> + youngestLastModified = Math.max(youngestLastModified, details.lastModified) + assetList.add("scripts/" + details.path) + } + } + + // Write a list of files to be unpacked to the cache folder + new File(outputDir, 'cache_assets.txt').withWriter { out -> + out.println(Long.toString(youngestLastModified)) + assetList.each { file -> out.println(file) } + } + } + + variant.outputs.all { + if (RELEASE_NUMBER != '0') { + outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" + } + } + } } dependencies { implementation 'com.google.vr:sdk-audio:1.80.0' implementation 'com.google.vr:sdk-base:1.80.0' - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(include: ['*.jar'], dir: 'libs') } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2160f7b591..efb7abbb93 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + + @@ -7,11 +8,13 @@ + - + + + + + + + + + android:launchMode="singleTop" + android:enableVrMode="com.google.vr.vrcore/com.google.vr.vrcore.common.VrCoreListenerService" + > - - + + @@ -43,4 +62,8 @@ - \ No newline at end of file + + + + + diff --git a/android/app/src/main/cpp/+android/simple.qml b/android/app/src/main/cpp/+android/simple.qml deleted file mode 100644 index fc722d7e2c..0000000000 --- a/android/app/src/main/cpp/+android/simple.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick 2.7 -import QtWebView 1.1 - -Rectangle { - id: window - anchors.fill: parent - color: "red" - ColorAnimation on color { from: "blue"; to: "yellow"; duration: 1000; loops: Animation.Infinite } - - Text { - text: "Hello" - anchors.top: parent.top - } - WebView { - anchors.fill: parent - anchors.margins: 10 - url: "http://doc.qt.io/qt-5/qml-qtwebview-webview.html" - } -} diff --git a/android/app/src/main/cpp/main.cpp b/android/app/src/main/cpp/main.cpp deleted file mode 100644 index b947323720..0000000000 --- a/android/app/src/main/cpp/main.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -Q_LOGGING_CATEGORY(gpugllogging, "hifi.gl") - -bool checkGLError(const char* name) { - GLenum error = glGetError(); - if (!error) { - return false; - } else { - switch (error) { - case GL_INVALID_ENUM: - qCDebug(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: - qCDebug(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: - qCDebug(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: - qCDebug(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: - qCDebug(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; - default: - qCDebug(gpugllogging) << "GLBackend" << name << ": Unknown error: " << error; - break; - } - return true; - } -} - -bool checkGLErrorDebug(const char* name) { - return checkGLError(name); -} - - -int QtMsgTypeToAndroidPriority(QtMsgType type) { - int priority = ANDROID_LOG_UNKNOWN; - switch (type) { - case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break; - case QtWarningMsg: priority = ANDROID_LOG_WARN; break; - case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break; - case QtFatalMsg: priority = ANDROID_LOG_FATAL; break; - case QtInfoMsg: priority = ANDROID_LOG_INFO; break; - default: break; - } - return priority; -} - -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - __android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str()); -} - -void qt_gl_set_global_share_context(QOpenGLContext *context); - -int main(int argc, char* argv[]) -{ - qInstallMessageHandler(messageHandler); - QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); - QGuiApplication app(argc,argv); - app.setOrganizationName("QtProject"); - app.setOrganizationDomain("qt-project.org"); - app.setApplicationName(QFileInfo(app.applicationFilePath()).baseName()); - QtWebView::initialize(); - qputenv("QSG_RENDERER_DEBUG", (QStringList() << "render" << "build" << "change" << "upload" << "roots" << "dump").join(';').toUtf8()); - - OffscreenGLCanvas sharedCanvas; - if (!sharedCanvas.create()) { - qFatal("Unable to create primary offscreen context"); - } - qt_gl_set_global_share_context(sharedCanvas.getContext()); - auto globalContext = QOpenGLContext::globalShareContext(); - - GLWindow window; - window.create(); - window.setGeometry(qApp->primaryScreen()->availableGeometry()); - window.createContext(globalContext); - if (!window.makeCurrent()) { - qFatal("Unable to make primary window GL context current"); - } - - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - - static const ivec2 offscreenSize { 640, 480 }; - - OffscreenQmlSurface::setSharedContext(sharedCanvas.getContext()); - OffscreenQmlSurface* qmlSurface = new OffscreenQmlSurface(); - qmlSurface->create(); - qmlSurface->resize(fromGlm(offscreenSize)); - qmlSurface->load("qrc:///simple.qml"); - qmlSurface->resume(); - - auto discardLambda = qmlSurface->getDiscardLambda(); - - window.showFullScreen(); - QTimer timer; - timer.setInterval(10); - timer.setSingleShot(false); - OffscreenQmlSurface::TextureAndFence currentTextureAndFence; - timer.connect(&timer, &QTimer::timeout, &app, [&]{ - window.makeCurrent(); - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - - OffscreenQmlSurface::TextureAndFence newTextureAndFence; - if (qmlSurface->fetchTexture(newTextureAndFence)) { - if (currentTextureAndFence.first) { - discardLambda(currentTextureAndFence.first, currentTextureAndFence.second); - } - currentTextureAndFence = newTextureAndFence; - } - checkGLErrorDebug(__FUNCTION__); - - if (currentTextureAndFence.second) { - glWaitSync((GLsync)currentTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)currentTextureAndFence.second); - currentTextureAndFence.second = nullptr; - } - - if (currentTextureAndFence.first) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, currentTextureAndFence.first, 0); - glBlitFramebuffer(0, 0, offscreenSize.x, offscreenSize.y, 100, 100, offscreenSize.x + 100, offscreenSize.y + 100, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - } - - window.swapBuffers(); - window.doneCurrent(); - }); - timer.start(); - return app.exec(); -} diff --git a/android/app/src/main/cpp/main.qrc b/android/app/src/main/cpp/main.qrc deleted file mode 100644 index 81cf8ef111..0000000000 --- a/android/app/src/main/cpp/main.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - simple.qml - +android/simple.qml - - diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp new file mode 100644 index 0000000000..13daf4c471 --- /dev/null +++ b/android/app/src/main/cpp/native.cpp @@ -0,0 +1,160 @@ +#include + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + if (!message.isEmpty()) { + const char * local=message.toStdString().c_str(); + switch (type) { + case QtDebugMsg: + __android_log_write(ANDROID_LOG_DEBUG,"Interface",local); + break; + case QtInfoMsg: + __android_log_write(ANDROID_LOG_INFO,"Interface",local); + break; + case QtWarningMsg: + __android_log_write(ANDROID_LOG_WARN,"Interface",local); + break; + case QtCriticalMsg: + __android_log_write(ANDROID_LOG_ERROR,"Interface",local); + break; + case QtFatalMsg: + default: + __android_log_write(ANDROID_LOG_FATAL,"Interface",local); + abort(); + } + } +} + +AAssetManager* g_assetManager = nullptr; + +void withAssetData(const char* filename, const std::function& callback) { + auto asset = AAssetManager_open(g_assetManager, filename, AASSET_MODE_BUFFER); + if (!asset) { + throw std::runtime_error("Failed to open file"); + } + auto buffer = AAsset_getBuffer(asset); + off64_t size = AAsset_getLength64(asset); + callback(size, buffer); + AAsset_close(asset); +} + +QStringList readAssetLines(const char* filename) { + QStringList result; + withAssetData(filename, [&](off64_t size, const void* data){ + QByteArray buffer = QByteArray::fromRawData((const char*)data, size); + { + QTextStream in(&buffer); + while (!in.atEnd()) { + QString line = in.readLine(); + result << line; + } + } + }); + return result; +} + +void copyAsset(const char* sourceAsset, const QString& destFilename) { + withAssetData(sourceAsset, [&](off64_t size, const void* data){ + QFile file(destFilename); + if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) { + throw std::runtime_error("Unable to open output file for writing"); + } + if (!file.resize(size)) { + throw std::runtime_error("Unable to resize output file"); + } + if (size != 0) { + auto mapped = file.map(0, size); + if (!mapped) { + throw std::runtime_error("Unable to map output file"); + } + memcpy(mapped, data, size); + } + file.close(); + }); +} + +void unpackAndroidAssets() { + const QString DEST_PREFIX = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/"; + + QStringList filesToCopy = readAssetLines("cache_assets.txt"); + QString dateStamp = filesToCopy.takeFirst(); + QString dateStampFilename = DEST_PREFIX + dateStamp; + qDebug() << "Checking date stamp" << dateStamp; + if (QFileInfo(dateStampFilename).exists()) { + return; + } + + auto rootDir = QDir::root(); + for (const auto& fileToCopy : filesToCopy) { + auto destFileName = DEST_PREFIX + fileToCopy; + auto destFolder = QFileInfo(destFileName).absoluteDir(); + if (!destFolder.exists()) { + qDebug() << "Creating folder" << destFolder.absolutePath(); + if (!rootDir.mkpath(destFolder.absolutePath())) { + throw std::runtime_error("Error creating output path"); + } + } + if (QFile::exists(destFileName)) { + qDebug() << "Removing old file" << destFileName; + if (!QFile::remove(destFileName)) { + throw std::runtime_error("Failed to remove existing file"); + } + } + + qDebug() << "Copying asset " << fileToCopy << "to" << destFileName; + copyAsset(fileToCopy.toStdString().c_str(), destFileName); + } + + { + qDebug() << "Writing date stamp" << dateStamp; + QFile file(dateStampFilename); + if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + throw std::runtime_error("Can't write date stamp"); + } + QTextStream(&file) << "touch" << endl; + file.close(); + } +} + +extern "C" { + +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) { + qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId(); + g_assetManager = AAssetManager_fromJava(env, asset_mgr); + auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler); + unpackAndroidAssets(); + qInstallMessageHandler(oldMessageHandler); +} + +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) { + qDebug() << "nativeOnPause"; +} + +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) { + qDebug() << "nativeOnResume"; +} + +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) { + qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId(); +} + +} + + diff --git a/android/app/src/main/cpp/simple.qml b/android/app/src/main/cpp/simple.qml deleted file mode 100644 index 38f3c80374..0000000000 --- a/android/app/src/main/cpp/simple.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick 2.0 - -Rectangle { - id: window - width: 320 - height: 480 - focus: true - color: "red" - ColorAnimation on color { from: "red"; to: "yellow"; duration: 1000; loops: Animation.Infinite } -} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java new file mode 100644 index 0000000000..de3bcee88d --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -0,0 +1,178 @@ +// +// InterfaceActivity.java +// android/app/src/main/java +// +// Created by Stephen Birarda on 1/26/15. +// 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 +// + +package io.highfidelity.hifiinterface; + +import android.content.Intent; +import android.content.res.AssetManager; +import android.net.Uri; +import android.os.Bundle; +import android.view.WindowManager; +import android.util.Log; +import org.qtproject.qt5.android.bindings.QtActivity; + +/*import com.google.vr.cardboard.DisplaySynchronizer; +import com.google.vr.cardboard.DisplayUtils; +import com.google.vr.ndk.base.GvrApi;*/ +import android.graphics.Point; +import android.content.res.Configuration; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.view.View; + +public class InterfaceActivity extends QtActivity { + + //public static native void handleHifiURL(String hifiURLString); + private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); + //private native void nativeOnPause(); + //private native void nativeOnResume(); + //private native void nativeOnStop(); + //private native void nativeOnStart(); + //private native void saveRealScreenSize(int width, int height); + //private native void setAppVersion(String version); + private native long nativeOnExitVr(); + + private AssetManager assetManager; + + private static boolean inVrMode; +// private GvrApi gvrApi; + // Opaque native pointer to the Application C++ object. + // This object is owned by the InterfaceActivity instance and passed to the native methods. + //private long nativeGvrApi; + + public void enterVr() { + //Log.d("[VR]", "Entering Vr mode (java)"); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + inVrMode = true; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // Get the intent that started this activity in case we have a hifi:// URL to parse + Intent intent = getIntent(); + if (intent.getAction() == Intent.ACTION_VIEW) { + Uri data = intent.getData(); + +// if (data.getScheme().equals("hifi")) { +// handleHifiURL(data.toString()); +// } + } + +/* DisplaySynchronizer displaySynchronizer = new DisplaySynchronizer(this, DisplayUtils.getDefaultDisplay(this)); + gvrApi = new GvrApi(this, displaySynchronizer); + */ +// Log.d("GVR", "gvrApi.toString(): " + gvrApi.toString()); + + assetManager = getResources().getAssets(); + + //nativeGvrApi = + nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/); + + Point size = new Point(); + getWindowManager().getDefaultDisplay().getRealSize(size); +// saveRealScreenSize(size.x, size.y); + + try { + PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0); + String version = pInfo.versionName; +// setAppVersion(version); + } catch (PackageManager.NameNotFoundException e) { + Log.e("GVR", "Error getting application version", e); + } + + final View rootView = getWindow().getDecorView().findViewById(android.R.id.content); + + // This is a workaround to hide the menu bar when the virtual keyboard is shown from Qt + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight(); + if (getActionBar().isShowing()) { + getActionBar().hide(); + } + } + }); + } + + @Override + protected void onPause() { + super.onPause(); + //nativeOnPause(); + //gvrApi.pauseTracking(); + } + + @Override + protected void onStart() { + super.onStart(); +// nativeOnStart(); + } + + @Override + protected void onStop() { + Log.d("[Background]","Calling nativeOnStop from InterfaceActivity"); +// nativeOnStop(); + super.onStop(); + } + + @Override + protected void onResume() { + super.onResume(); + //nativeOnResume(); + //gvrApi.resumeTracking(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Checks the orientation of the screen + if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ +// Log.d("[VR]", "Portrait, forcing landscape"); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + if (inVrMode) { + inVrMode = false; +// Log.d("[VR]", "Starting VR exit"); + nativeOnExitVr(); + } else { + Log.w("[VR]", "Portrait detected but not in VR mode. Should not happen"); + } + } + } + + public void openUrlInAndroidWebView(String urlString) { + Log.d("openUrl", "Received in open " + urlString); + Intent openUrlIntent = new Intent(this, WebViewActivity.class); + openUrlIntent.putExtra(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL, urlString); + startActivity(openUrlIntent); + } + + /** + * Called when view focus is changed + */ + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + getWindow().getDecorView().setSystemUiVisibility(uiOptions); + } + } + +} \ No newline at end of file diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java new file mode 100644 index 0000000000..34b087ca25 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -0,0 +1,130 @@ +package io.highfidelity.hifiinterface; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.app.Activity; + +import android.content.DialogInterface; +import android.app.AlertDialog; + +import org.json.JSONException; +import org.json.JSONObject; +import android.util.Log; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.List; + +public class PermissionChecker extends Activity { + private static final int REQUEST_PERMISSIONS = 20; + + private static final boolean CHOOSE_AVATAR_ON_STARTUP = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (CHOOSE_AVATAR_ON_STARTUP) { + showMenu(); + } + this.requestAppPermissions(new + String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.RECORD_AUDIO, + Manifest.permission.CAMERA} + ,2,REQUEST_PERMISSIONS); + + } + + public void requestAppPermissions(final String[] requestedPermissions, + final int stringId, final int requestCode) { + int permissionCheck = PackageManager.PERMISSION_GRANTED; + boolean shouldShowRequestPermissionRationale = false; + for (String permission : requestedPermissions) { + permissionCheck = permissionCheck + checkSelfPermission(permission); + shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || shouldShowRequestPermissionRationale(permission); + } + if (permissionCheck != PackageManager.PERMISSION_GRANTED) { + System.out.println("Permission was not granted. Ask for permissions"); + if (shouldShowRequestPermissionRationale) { + requestPermissions(requestedPermissions, requestCode); + } else { + requestPermissions(requestedPermissions, requestCode); + } + } else { + System.out.println("Launching the other activity.."); + launchActivityWithPermissions(); + } + } + + private void launchActivityWithPermissions(){ + finish(); + Intent i = new Intent(this, InterfaceActivity.class); + startActivity(i); + finish(); + } + + private void showMenu(){ + final List avatarOptions = Arrays.asList("\uD83D\uDC66\uD83C\uDFFB Cody","\uD83D\uDC66\uD83C\uDFFF Will","\uD83D\uDC68\uD83C\uDFFD Albert", "\uD83D\uDC7D Being of Light"); + final String[] avatarPaths = { + "http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst", + "http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst", + "http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" }; + + final String pathForJson = "/data/data/io.highfidelity.hifiinterface/files/.config/High Fidelity - dev/"; + new AlertDialog.Builder(this) + .setTitle("Pick an avatar") + .setItems(avatarOptions.toArray(new CharSequence[avatarOptions.size()]),new DialogInterface.OnClickListener(){ + + @Override + public void onClick(DialogInterface dialog, int which) { + if(which < avatarPaths.length ) { + JSONObject obj = new JSONObject(); + try { + obj.put("firstRun",false); + obj.put("Avatar/fullAvatarURL", avatarPaths[which]); + File directory = new File(pathForJson); + + if(!directory.exists()) directory.mkdirs(); + + File file = new File(pathForJson + "Interface.json"); + Writer output = new BufferedWriter(new FileWriter(file)); + output.write(obj.toString().replace("\\","")); + output.close(); + System.out.println("I Could write config file expect to see the selected avatar"+obj.toString().replace("\\","")); + + } catch (JSONException e) { + System.out.println("JSONException something weired went wrong"); + } catch (IOException e) { + System.out.println("Could not write file :("); + } + } else { + System.out.println("Default avatar selected..."); + } + launchActivityWithPermissions(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + int permissionCheck = PackageManager.PERMISSION_GRANTED; + for (int permission : grantResults) { + permissionCheck = permissionCheck + permission; + } + if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) { + launchActivityWithPermissions(); + } else if (grantResults.length > 0) { + System.out.println("User has deliberately denied Permissions. Launching anyways"); + launchActivityWithPermissions(); + } + } + + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java new file mode 100644 index 0000000000..4d706248d8 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java @@ -0,0 +1,242 @@ +// +// WebViewActivity.java +// interface/java +// +// Created by Cristian Duarte and Gabriel Calero on 8/17/17. +// Copyright 2017 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 +// + +//package hifiinterface.highfidelity.io.mybrowserapplication; +package io.highfidelity.hifiinterface; + +import android.app.ActionBar; +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.net.http.SslError; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; +import android.widget.Toast; +import android.widget.Toolbar; +import android.os.Looper; +import java.lang.Thread; +import java.lang.Runnable; + +import java.net.MalformedURLException; +import java.net.URL; + +public class WebViewActivity extends Activity { + + public static final String WEB_VIEW_ACTIVITY_EXTRA_URL = "url"; + + private native void nativeProcessURL(String url); + + private WebView myWebView; + private ProgressBar mProgressBar; + private ActionBar mActionBar; + private String mUrl; + + enum SafenessLevel { + NOT_ANALYZED_YET(""), + NOT_SECURE(""), + SECURE("\uD83D\uDD12 "), + BAD_SECURE("\uD83D\uDD13 "); + + String icon; + SafenessLevel(String icon) { + this.icon = icon; + } + } + + private SafenessLevel safenessLevel = SafenessLevel.NOT_ANALYZED_YET; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_web_view); + + setActionBar((Toolbar) findViewById(R.id.toolbar_actionbar)); + mActionBar = getActionBar(); + mActionBar.setDisplayHomeAsUpEnabled(true); + + mProgressBar = (ProgressBar) findViewById(R.id.toolbarProgressBar); + + mUrl = getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL); + myWebView = (WebView) findViewById(R.id.web_view); + myWebView.setWebViewClient(new HiFiWebViewClient()); + myWebView.setWebChromeClient(new HiFiWebChromeClient()); + WebSettings webSettings = myWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setBuiltInZoomControls(true); + webSettings.setDisplayZoomControls(false); + myWebView.loadUrl(mUrl); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // Check if the key event was the Back button and if there's history + if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) { + myWebView.goBack(); + return true; + } + // If it wasn't the Back key or there's no web page history, bubble up to the default + // system behavior (probably exit the activity) + return super.onKeyDown(keyCode, event); + } + + private void showSubtitleWithUrl(String url) { + try { + mActionBar.setSubtitle(safenessLevel.icon + new URL(url.toString()).getHost()); + } catch (MalformedURLException e) { + Toast.makeText(WebViewActivity.this, "Error loading page: " + "bad url", Toast.LENGTH_LONG).show(); + Log.e("openUrl", "bad url"); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.web_view_menu, menu); + return true; + } + + private String intentUrlOrWebUrl() { + return myWebView==null || myWebView.getUrl()==null?mUrl:myWebView.getUrl(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + switch (item.getItemId()) { + // Respond to the action bar's Up/Home/back button + case android.R.id.home: + finish(); + break; + case R.id.action_browser: + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(intentUrlOrWebUrl())); + startActivity(i); + return true; + case R.id.action_share: + Intent is = new Intent(Intent.ACTION_SEND); + is.setType("text/plain"); + is.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.web_view_action_share_subject)); + is.putExtra(Intent.EXTRA_TEXT, intentUrlOrWebUrl()); + startActivity(Intent.createChooser(is, getString(R.string.web_view_action_share_chooser))); + return true; + } + return super.onOptionsItemSelected(item); + } + + class HiFiWebViewClient extends WebViewClient { + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + mProgressBar.setVisibility(View.GONE); + if (safenessLevel!=SafenessLevel.BAD_SECURE) { + if (url.startsWith("https:")) { + safenessLevel=SafenessLevel.SECURE; + } else { + safenessLevel=SafenessLevel.NOT_SECURE; + } + } + showSubtitleWithUrl(url); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + safenessLevel = SafenessLevel.NOT_ANALYZED_YET; + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setProgress(0); + showSubtitleWithUrl(url); + } + + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + Toast.makeText(WebViewActivity.this, "Error loading page: " + error.getDescription(), Toast.LENGTH_LONG).show(); + if (ERROR_FAILED_SSL_HANDSHAKE == error.getErrorCode()) { + safenessLevel = SafenessLevel.BAD_SECURE; + } + } + + @Override + public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + Toast.makeText(WebViewActivity.this, "Network Error loading page: " + errorResponse.getReasonPhrase(), Toast.LENGTH_LONG).show(); + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + super.onReceivedSslError(view, handler, error); + Toast.makeText(WebViewActivity.this, "SSL error loading page: " + error.toString(), Toast.LENGTH_LONG).show(); + safenessLevel = SafenessLevel.BAD_SECURE; + } + + private boolean isFst(WebResourceRequest request) { + return isFst(request.getUrl().toString()); + } + + private boolean isFst(String url) { + return url.endsWith(".fst"); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + // managing avatar selections + if (isFst(request)) { + final String url = request.getUrl().toString(); + new Thread(new Runnable() { + public void run() { + nativeProcessURL(url); + } + }).start(); // Avoid deadlock in Qt dialog + WebViewActivity.this.finish(); + return true; + } + return super.shouldOverrideUrlLoading(view, request); + } + + @Override + public void onLoadResource(WebView view, String url) { + if (isFst(url)) { + // processed separately + } else { + super.onLoadResource(view, url); + } + } + } + + class HiFiWebChromeClient extends WebChromeClient { + + @Override + public void onProgressChanged(WebView view, int newProgress) { + super.onProgressChanged(view, newProgress); + mProgressBar.setProgress(newProgress); + } + + @Override + public void onReceivedTitle(WebView view, String title) { + super.onReceivedTitle(view, title); + mActionBar.setTitle(title); + } + + } +} diff --git a/android/app/src/main/res/drawable/ic_close_black_24dp.xml b/android/app/src/main/res/drawable/ic_close_black_24dp.xml new file mode 100644 index 0000000000..ede4b7108d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_close_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/layout/activity_web_view.xml b/android/app/src/main/res/layout/activity_web_view.xml new file mode 100644 index 0000000000..1e30b58676 --- /dev/null +++ b/android/app/src/main/res/layout/activity_web_view.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/web_view_menu.xml b/android/app/src/main/res/menu/web_view_menu.xml new file mode 100644 index 0000000000..074e1608c5 --- /dev/null +++ b/android/app/src/main/res/menu/web_view_menu.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index cde69bccce..d376d7af88 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 9a078e3e1a..81a137957d 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index c133a0cbd3..88b1be5903 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index efc028a636..d032c30014 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index bfa42f0e7b..4a018d4ed9 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 3af2608a44..3cccf1037b 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 324e72cdd7..e97062e0ee 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 9bec2e6231..8d142881da 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index aee44e1384..f01203c738 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 34947cd6bb..211e298961 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..a9ec657aa9 --- /dev/null +++ b/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,12 @@ + + + + 16dp + 16dp + + + + 14dp + + 12dp + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 5d6a4c1b99..b8080fae0f 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,3 +1,8 @@ - TestApp + Interface + Open in browser + Share link + Shared a link + Share link + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 033324ac58..23fe67f029 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -11,5 +11,15 @@ - + + + + + diff --git a/android/build.gradle b/android/build.gradle index c7eefd051b..1f2c563e1b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -40,6 +40,7 @@ ext { BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : '' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' QT5_DEPS = [ + 'Qt5Concurrent', 'Qt5Core', 'Qt5Gui', 'Qt5Multimedia', @@ -47,8 +48,11 @@ ext { 'Qt5OpenGL', 'Qt5Qml', 'Qt5Quick', + 'Qt5QuickControls2', + 'Qt5QuickTemplates2', 'Qt5Script', 'Qt5ScriptTools', + 'Qt5Svg', 'Qt5WebChannel', 'Qt5WebSockets', 'Qt5Widgets', @@ -93,6 +97,11 @@ def packages = [ versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m', checksum: '617a80d213a5ec69fbfa21a1f2f738cd', ], + glad: [ + file: 'glad_armv8-libcpp.zip', + versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE', + checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449', + ], glm: [ file: 'glm-0.9.8.tgz', versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2', @@ -145,16 +154,18 @@ def options = [ def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt') -def captureOutput = { String command -> - def proc = command.execute() - def sout = new StringBuilder(), serr = new StringBuilder() - proc.consumeProcessOutput(sout, serr) - proc.waitForOrKill(10000) - def errorOutput = serr.toString() - if (!errorOutput.isEmpty()) { - throw new GradleException("Command '${command}' failed with error ${errorOutput}") +def captureOutput = { String command, List commandArgs -> + def result + new ByteArrayOutputStream().withStream { os -> + def execResult = exec { + executable = command + args = commandArgs + standardOutput = os + errorOutput = new ByteArrayOutputStream() + } + result = os.toString() } - return sout.toString() + return result; } def relativize = { File root, File absolute -> @@ -168,11 +179,13 @@ def scanQmlImports = { File qmlRootPath -> throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString()) } - def command = qmlImportCommandFile.absolutePath + - " -rootPath ${qmlRootPath.absolutePath}" + - " -importPath ${qmlRoot.absolutePath}/qml" + def command = qmlImportCommandFile.absolutePath + def args = [ + '-rootPath', qmlRootPath.absolutePath, + '-importPath', "${qmlRoot.absolutePath}/qml" + ] - def commandResult = captureOutput(command) + def commandResult = captureOutput(command, args) new JsonSlurper().parseText(commandResult).each { if (!it.containsKey('path')) { println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" @@ -257,7 +270,7 @@ def parseQtDependencies = { List qtLibs -> def generateLibsXml = { def libDestinationDirectory = jniFolder def jarDestinationDirectory = new File(appDir, 'libs') - def assetDestinationDirectory = new File(appDir, 'src/main/assets/bundled'); + def assetDestinationDirectory = new File(appDir, 'src/main/assets/--Added-by-androiddeployqt--'); def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') def libPrefix = 'lib' + File.separator def jarPrefix = 'jar' + File.separator @@ -293,7 +306,7 @@ def generateLibsXml = { } else if (relativePath.startsWith('jar')) { destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) } else { - xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("bundled/${relativePath}:${relativePath}".replace(File.separator, '/')) + xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("--Added-by-androiddeployqt--/${relativePath}:${relativePath}".replace(File.separator, '/')) destinationFile = new File(assetDestinationDirectory, relativePath) } @@ -427,9 +440,55 @@ task extractGvrBinaries(dependsOn: extractDependencies) { } } } - } +def generateAssetsFileList = { + def assetsPath = "${appDir}/src/main/assets/" + def addedByAndroidDeployQtName = "--Added-by-androiddeployqt--/" + + def addedByAndroidDeployQtPath = assetsPath + addedByAndroidDeployQtName + + def addedByAndroidDeployQt = new File(addedByAndroidDeployQtPath) + if (!addedByAndroidDeployQt.exists() && !addedByAndroidDeployQt.mkdirs()) { + throw new GradleScriptException("Failed to create directory " + addedByAndroidDeployQtPath, null); + } + def outputFilename = "/qt_cache_pregenerated_file_list" + def outputFile = new File(addedByAndroidDeployQtPath + outputFilename); + Map> directoryContents = new TreeMap<>(); + + def dir = new File(assetsPath) + dir.eachFileRecurse (FileType.ANY) { file -> + + def name = file.path.substring(assetsPath.length()) + int slashIndex = name.lastIndexOf('/') + def pathName = slashIndex >= 0 ? name.substring(0, slashIndex) : "/" + def fileName = slashIndex >= 0 ? name.substring(pathName.length() + 1) : name + if (!fileName.isEmpty() && file.isDirectory() && !fileName.endsWith("/")) { + fileName += "/" + } + + if (!directoryContents.containsKey(pathName)) { + directoryContents[pathName] = new ArrayList() + } + if (!fileName.isEmpty()) { + directoryContents[pathName].add(fileName); + } + } + DataOutputStream fos = new DataOutputStream(new FileOutputStream(outputFile)); + for (Map.Entry> e: directoryContents.entrySet()) { + def entryList = e.getValue() + fos.writeInt(e.key.length()*2); // 2 bytes per char + fos.writeChars(e.key); + fos.writeInt(entryList.size()); + for (String entry: entryList) { + fos.writeInt(entry.length()*2); + fos.writeChars(entry); + } + } + fos.close(); +} + + // Copy required Qt main libraries and required plugins based on the predefined list here // FIXME eventually we would like to use the readelf functionality to automatically detect dependencies // from our built applications and use that during the full build process. However doing so would mean @@ -438,10 +497,11 @@ task extractGvrBinaries(dependsOn: extractDependencies) { task qtBundle { doLast { parseQtDependencies(QT5_DEPS) - //def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/") - def qmlImportFolder = new File("${projectDir}/app/src/main/cpp") + def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/") + //def qmlImportFolder = new File("${projectDir}/app/src/main/cpp") scanQmlImports(qmlImportFolder) generateLibsXml() + generateAssetsFileList() } } @@ -450,10 +510,12 @@ task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBina task cleanDependencies(type: Delete) { delete HIFI_ANDROID_PRECOMPILED delete 'app/src/main/jniLibs/arm64-v8a' - delete 'app/src/main/assets/bundled' + delete 'app/src/main/assets/--Added-by-androiddeployqt--' delete 'app/src/main/res/values/libs.xml' } + + // FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. // See the comment on the qtBundle task above /* diff --git a/android/build_recipes.md b/android/build_recipes.md index 78ca961f5c..cd7b0413bb 100644 --- a/android/build_recipes.md +++ b/android/build_recipes.md @@ -43,15 +43,17 @@ export LDFLAGS="-pie" * Install Python 3.6 for Windows * Open a Git Bash command prompt * Ensure the following commands are visible in the path with `which ` - * gcc - * javac - * python - * gmake + * gcc + * javac + * python + * gmake * If any of them fail, fix your path and restart the bash prompt * Fetch the pre-built OpenSSL binaries for Android/iOS from here: https://github.com/leenjewel/openssl_for_ios_and_android/releases * Grab the latest release of the 1.0.2 series * Open the archive and extract the `/android/openssl-arm64-v8a` folder +### All platforms + * Download the Qt sources * `git clone git://code.qt.io/qt/qt5.git` * `cd qt5` @@ -60,7 +62,11 @@ export LDFLAGS="-pie" * `git submodule update --recursive` * `cd ..` * Create a build directory with the command `mkdir qt5build` -* Configure the Qt5 build with the command `../qt5/configure -xplatform android-clang -android-ndk-host windows-x86_64 -confirm-license -opensource --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-ndk C:/Android/NDK -android-toolchain-version 4.9 -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -platform win32-g++ -prefix C:/qt5build_debug -android-sdk C:/Android/SDK -c++std c++14 -openssl-linked -L/lib -I/include` +* Configure the Qt5 build with the command `../qt5/configure -opensource -confirm-license -xplatform android-clang --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-toolchain-version 4.9 -android-ndk $HOME/Android/NDK -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -android-ndk-host windows-x86_64 -platform win32-g++ -prefix C:/qt5build_debug -android-sdk $HOME/Android/SDK -c++std c++14 -openssl-linked -L/lib -I/include` +* Some of those entries must be customized depending on platform. + * `-platform win32-g++` + * `-android-ndk-host windows-x86_64` + * `-prefix C:/qt5build_debug` diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index aac7c9b461..0000000000 --- a/android/gradle.properties +++ /dev/null @@ -1,17 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d1c2c6597b..a42b78a6fa 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -343,7 +343,6 @@ void Agent::scriptRequestFinished() { void Agent::executeScript() { _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); - _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do DependencyManager::get()->setScriptEngine(_scriptEngine); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index c9ded2d6fb..1f3f770867 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -123,12 +123,12 @@ void ScriptableAvatar::update(float deltatime) { AnimPose& absPose = absPoses[i]; if (data.rotation != absPose.rot()) { data.rotation = absPose.rot(); - data.rotationSet = true; + data.rotationIsDefaultPose = false; } AnimPose& relPose = poses[i]; if (data.translation != relPose.trans()) { data.translation = relPose.trans(); - data.translationSet = true; + data.translationIsDefaultPose = false; } } diff --git a/cmake/externals/glew/CMakeLists.txt b/cmake/externals/glad32es/CMakeLists.txt similarity index 50% rename from cmake/externals/glew/CMakeLists.txt rename to cmake/externals/glad32es/CMakeLists.txt index 6c3208981d..8ca4d44021 100644 --- a/cmake/externals/glew/CMakeLists.txt +++ b/cmake/externals/glad32es/CMakeLists.txt @@ -1,27 +1,22 @@ -set(EXTERNAL_NAME glew) - -if (ANDROID) - set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") -endif () +set(EXTERNAL_NAME glad32es) include(ExternalProject) +include(SelectLibraryConfigurations) + ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/glew_simple_1.13.0.zip - URL_MD5 73f833649e904257b35bf4e84f8bdfb5 - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON + URL https://hifi-public.s3.amazonaws.com/austin/glad/glad32es.zip + URL_MD5 6a641d8c49dee4c895fa59315f5682a6 + CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 ) # Hide this external target (for ide users) -set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - -ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) - string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glew include directories") +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) if (UNIX) set(LIB_PREFIX "lib") @@ -30,5 +25,9 @@ elseif (WIN32) set(LIB_EXT "lib") endif () -set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glew_d.${LIB_EXT} CACHE FILEPATH "Path to glew debug library") -set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glew.${LIB_EXT} CACHE FILEPATH "Path to glew release library") +set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT}) +set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT}) +select_library_configurations(${EXTERNAL_NAME_UPPER}) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories") +set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries") diff --git a/cmake/externals/glad41/CMakeLists.txt b/cmake/externals/glad41/CMakeLists.txt new file mode 100644 index 0000000000..2371044362 --- /dev/null +++ b/cmake/externals/glad41/CMakeLists.txt @@ -0,0 +1,33 @@ +set(EXTERNAL_NAME glad41) + +include(ExternalProject) +include(SelectLibraryConfigurations) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://hifi-public.s3.amazonaws.com/austin/glad/glad41.zip + URL_MD5 1324eeec33abe91e67d19ae551ba624d + CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +if (UNIX) + set(LIB_PREFIX "lib") + set(LIB_EXT "a") +elseif (WIN32) + set(LIB_EXT "lib") +endif () + +set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT}) +set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT}) +select_library_configurations(${EXTERNAL_NAME_UPPER}) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories") +set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries") diff --git a/cmake/externals/glad45/CMakeLists.txt b/cmake/externals/glad45/CMakeLists.txt new file mode 100644 index 0000000000..8ad455fa7e --- /dev/null +++ b/cmake/externals/glad45/CMakeLists.txt @@ -0,0 +1,33 @@ +set(EXTERNAL_NAME glad45) + +include(ExternalProject) +include(SelectLibraryConfigurations) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://hifi-public.s3.amazonaws.com/austin/glad/glad45.zip + URL_MD5 cfb19b3cb5b2f8f1d1669fb3150e5f05 + CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +if (UNIX) + set(LIB_PREFIX "lib") + set(LIB_EXT "a") +elseif (WIN32) + set(LIB_EXT "lib") +endif () + +set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT}) +set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT}) +select_library_configurations(${EXTERNAL_NAME_UPPER}) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories") +set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries") diff --git a/cmake/macros/GenerateQrc.cmake b/cmake/macros/GenerateQrc.cmake index 0283b3ea9b..9bf530b2a2 100644 --- a/cmake/macros/GenerateQrc.cmake +++ b/cmake/macros/GenerateQrc.cmake @@ -12,9 +12,14 @@ function(GENERATE_QRC) foreach(GLOB ${GENERATE_QRC_GLOBS}) file(GLOB_RECURSE FOUND_FILES RELATIVE ${GENERATE_QRC_PATH} ${GLOB}) foreach(FILENAME ${FOUND_FILES}) + if (${FILENAME} MATCHES "^\\.\\.") + continue() + endif() + list(APPEND ALL_FILES "${GENERATE_QRC_PATH}/${FILENAME}") set(QRC_CONTENTS "${QRC_CONTENTS}${GENERATE_QRC_PATH}/${FILENAME}\n") endforeach() endforeach() - + + set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE) configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT}) endfunction() diff --git a/cmake/macros/TargetGlad.cmake b/cmake/macros/TargetGlad.cmake new file mode 100644 index 0000000000..929f61c3f2 --- /dev/null +++ b/cmake/macros/TargetGlad.cmake @@ -0,0 +1,38 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_GLAD) + if (ANDROID) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/glad) + set(GLAD_INCLUDE_DIRS "${INSTALL_DIR}/include") + set(GLAD_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libglad_d.a) + set(GLAD_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libglad.a) + select_library_configurations(GLAD) + find_library(EGL EGL) + target_link_libraries(${TARGET_NAME} ${EGL}) + else() + if (USE_GLES) + set(GLAD_VER "32es") + else() + set(GLAD_VER "45") + endif() + find_package(OpenGL REQUIRED) + list(APPEND GLAD_EXTRA_LIBRARIES ${OPENGL_LIBRARY}) + if (NOT WIN32) + list(APPEND GLAD_EXTRA_LIBRARIES dl) + endif() + set(GLAD "glad${GLAD_VER}") + string(TOUPPER ${GLAD} GLAD_UPPER) + add_dependency_external_projects(${GLAD}) + set(GLAD_INCLUDE_DIRS ${${GLAD_UPPER}_INCLUDE_DIRS}) + set(GLAD_LIBRARY ${${GLAD_UPPER}_LIBRARY}) + endif() + + target_include_directories(${TARGET_NAME} PUBLIC ${GLAD_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${GLAD_LIBRARY}) + target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES}) +endmacro() diff --git a/cmake/macros/TargetGlew.cmake b/cmake/macros/TargetGlew.cmake deleted file mode 100644 index bc4d5cb033..0000000000 --- a/cmake/macros/TargetGlew.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2015 High Fidelity, Inc. -# Created by Bradley Austin Davis on 2015/10/10 -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# -macro(TARGET_GLEW) - if (NOT ANDROID) - add_definitions(-DGLEW_STATIC) - add_dependency_external_projects(glew) - find_package(GLEW REQUIRED) - target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY}) - endif() -endmacro() \ No newline at end of file diff --git a/cmake/macros/TargetOpenGL.cmake b/cmake/macros/TargetOpenGL.cmake index 6ad92259bb..84fe5a7a5a 100644 --- a/cmake/macros/TargetOpenGL.cmake +++ b/cmake/macros/TargetOpenGL.cmake @@ -6,20 +6,6 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_OPENGL) - if (APPLE) - # link in required OS X frameworks and include the right GL headers - find_library(OpenGL OpenGL) - target_link_libraries(${TARGET_NAME} ${OpenGL}) - elseif(ANDROID) - target_link_libraries(${TARGET_NAME} GLESv3 EGL) - else() - find_package(OpenGL REQUIRED) - if (${OPENGL_INCLUDE_DIR}) - include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}") - endif() - target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}") - target_include_directories(${TARGET_NAME} PUBLIC ${OPENGL_INCLUDE_DIR}) - endif() + target_glad() target_nsight() - target_glew() endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 21225756b4..383c50ed44 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -11,9 +11,20 @@ function(JOIN VALUES GLUE OUTPUT) set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE) endfunction() +set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) +set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc) +generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *) + +add_custom_command( + OUTPUT ${RESOURCES_RCC} + DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS} + COMMAND "${QT_DIR}/bin/rcc" + ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC} +) + +list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC}) +add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS}) -set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) -generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -40,6 +51,7 @@ endif() # grab the implementation and header files from src dirs file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") +list(APPEND INTERFACE_SRCS ${RESOURCES_RCC}) # Add SpeechRecognizer if on Windows or OS X, otherwise remove if (WIN32) @@ -72,16 +84,6 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") -list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC}) - -if (UNIX) - install( - DIRECTORY "${CMAKE_SOURCE_DIR}/interface/resources/qml" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/resources - COMPONENT ${CLIENT_COMPONENT} - ) -endif() - # translation disabled until we strip out the line numbers # set(QM ${TARGET_NAME}_en.qm) # set(TS ${TARGET_NAME}_en.ts) @@ -89,21 +91,7 @@ endif() # setup the android parameters that will help us produce an APK if (ANDROID) - set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build") - set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk") - - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}") - set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME}) - set(ANDROID_APP_DISPLAY_NAME Interface) - set(ANDROID_API_LEVEL 19) - set(ANDROID_APK_PACKAGE io.highfidelity.interface) - set(ANDROID_ACTIVITY_NAME io.highfidelity.interface.InterfaceActivity) - set(ANDROID_APK_VERSION_NAME "0.1") - set(ANDROID_APK_VERSION_CODE 1) - set(ANDROID_APK_FULLSCREEN TRUE) - set(ANDROID_DEPLOY_QT_INSTALL "--install") - set(BUILD_SHARED_LIBS ON) endif () @@ -175,6 +163,8 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () +add_dependencies(${TARGET_NAME} resources) + if (WIN32) # These are external plugins, but we need to do the 'add dependency' here so that their # binary directories get added to the fixup path @@ -258,15 +248,20 @@ endforeach() # include headers for interface and InterfaceConfig. include_directories("${PROJECT_SOURCE_DIR}/src") +if (ANDROID) + find_library(ANDROID_LOG_LIB log) + target_link_libraries(${TARGET_NAME} ${ANDROID_LOG_LIB}) +endif () + target_link_libraries( ${TARGET_NAME} Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg - Qt5::WebChannel Qt5::WebEngine + Qt5::WebChannel ${PLATFORM_QT_LIBRARIES} ) -if (UNIX) +if (UNIX AND NOT ANDROID) if (CMAKE_SYSTEM_NAME MATCHES "Linux") # Linux target_link_libraries(${TARGET_NAME} pthread atomic) @@ -274,7 +269,7 @@ if (UNIX) # OSX target_link_libraries(${TARGET_NAME} pthread) endif () -endif(UNIX) +endif() # assume we are using a Qt build without bearer management add_definitions(-DQT_NO_BEARERMANAGEMENT) @@ -304,12 +299,20 @@ if (APPLE) # call the fixup_interface macro to add required bundling commands for installation fixup_interface() -else (APPLE) +else() # copy the resources files beside the executable add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${RESOURCES_RCC}" + "$" + # FIXME, the edit script code loads HTML from the scripts folder + # which in turn relies on CSS that refers to the fonts. In theory + # we should be able to modify the CSS to reference the QRC path to + # the ttf files, but doing so generates a CORS policy violation, + # so we have to retain a copy of the fonts outside of the resources binary COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/resources" - "$/resources" + "${PROJECT_SOURCE_DIR}/resources/fonts" + "$/resources/fonts" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "$/scripts" @@ -335,7 +338,7 @@ else (APPLE) optional_win_executable_signing() endif() -endif (APPLE) +endif() if (SCRIPTS_INSTALL_DIR) @@ -359,20 +362,6 @@ if (WIN32) package_libraries_for_deployment() endif() -if (ANDROID) - set(HIFI_URL_INTENT "\ - \n \ - \n \ - \n \ - \n \ - \n " - ) - - set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}") - - qt_create_apk() -endif () - add_dependency_external_projects(GifCreator) find_package(GifCreator REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS}) diff --git a/interface/compiledResources/.placeholder b/interface/compiledResources/.placeholder new file mode 100644 index 0000000000..e69de29bb2 diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg new file mode 100644 index 0000000000..340f0fcd2f --- /dev/null +++ b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/interface/resources/images/+gles/Default-Sky-9-cubemap.ktx b/interface/resources/images/+gles/Default-Sky-9-cubemap.ktx new file mode 100644 index 0000000000..6d46791fa2 Binary files /dev/null and b/interface/resources/images/+gles/Default-Sky-9-cubemap.ktx differ diff --git a/interface/resources/images/preview-privacy.png b/interface/resources/images/preview-privacy.png new file mode 100644 index 0000000000..1a718de233 Binary files /dev/null and b/interface/resources/images/preview-privacy.png differ diff --git a/interface/resources/meshes/+gles/defaultAvatar_full.fst b/interface/resources/meshes/+gles/defaultAvatar_full.fst new file mode 100644 index 0000000000..7d917318dd --- /dev/null +++ b/interface/resources/meshes/+gles/defaultAvatar_full.fst @@ -0,0 +1,135 @@ +name = being_of_light +type = body+head +scale = 1 +filename = being_of_light/being_of_light.fbx +texdir = being_of_light/textures +joint = jointRoot = Hips +joint = jointLeftHand = LeftHand +joint = jointHead = HeadTop_End +joint = jointLean = Spine +joint = jointEyeLeft = LeftEye +joint = jointRightHand = RightHand +joint = jointNeck = Head +joint = jointEyeRight = RightEye +freeJoint = LeftArm +freeJoint = LeftForeArm +freeJoint = RightArm +freeJoint = RightForeArm +bs = MouthFrown_L = Frown_Left = 1 +bs = MouthLeft = Midmouth_Left = 1 +bs = BrowsU_R = BrowsUp_Right = 1 +bs = ChinUpperRaise = UpperLipUp_Right = 0.5 +bs = ChinUpperRaise = UpperLipUp_Left = 0.5 +bs = MouthSmile_R = Smile_Right = 1 +bs = MouthDimple_L = Smile_Left = 0.25 +bs = EyeBlink_L = Blink_Left = 1 +bs = BrowsD_L = BrowsDown_Left = 1 +bs = MouthFrown_R = Frown_Right = 1 +bs = MouthDimple_R = Smile_Right = 0.25 +bs = Sneer = Squint_Right = 0.5 +bs = Sneer = Squint_Left = 0.5 +bs = Sneer = NoseScrunch_Right = 0.75 +bs = Sneer = NoseScrunch_Left = 0.75 +bs = EyeSquint_L = Squint_Left = 1 +bs = EyeBlink_R = Blink_Right = 1 +bs = JawLeft = JawRotateY_Left = 0.5 +bs = BrowsD_R = BrowsDown_Right = 1 +bs = EyeSquint_R = Squint_Right = 1 +bs = Puff = CheekPuff_Right = 1 +bs = Puff = CheekPuff_Left = 1 +bs = LipsUpperClose = UpperLipIn = 1 +bs = JawOpen = MouthOpen = 0.69999999999999996 +bs = LipsUpperUp = UpperLipUp_Right = 0.69999999999999996 +bs = LipsUpperUp = UpperLipUp_Left = 0.69999999999999996 +bs = LipsLowerDown = LowerLipDown_Right = 0.69999999999999996 +bs = LipsLowerDown = LowerLipDown_Left = 0.69999999999999996 +bs = LipsLowerOpen = LowerLipOut = 1 +bs = EyeOpen_L = EyesWide_Left = 1 +bs = LipsPucker = MouthNarrow_Right = 1 +bs = LipsPucker = MouthNarrow_Left = 1 +bs = EyeOpen_R = EyesWide_Right = 1 +bs = JawRight = Jaw_Right = 1 +bs = MouthRight = Midmouth_Right = 1 +bs = ChinLowerRaise = Jaw_Up = 1 +bs = LipsUpperOpen = UpperLipOut = 1 +bs = BrowsU_C = BrowsUp_Right = 1 +bs = BrowsU_C = BrowsUp_Left = 1 +bs = JawFwd = JawForeward = 1 +bs = BrowsU_L = BrowsUp_Left = 1 +bs = MouthSmile_L = Smile_Left = 1 +bs = LipsLowerClose = LowerLipIn = 1 +bs = LipsFunnel = TongueUp = 1 +bs = LipsFunnel = MouthWhistle_NarrowAdjust_Right = 0.5 +bs = LipsFunnel = MouthWhistle_NarrowAdjust_Left = 0.5 +bs = LipsFunnel = MouthNarrow_Right = 1 +bs = LipsFunnel = MouthNarrow_Left = 1 +bs = LipsFunnel = Jaw_Down = 0.35999999999999999 +bs = LipsFunnel = JawForeward = 0.39000000000000001 +jointIndex = LeftHandIndex1 = 50 +jointIndex = LeftHandIndex2 = 51 +jointIndex = LeftHandIndex3 = 52 +jointIndex = LeftHandIndex4 = 53 +jointIndex = Spine1 = 12 +jointIndex = Spine2 = 13 +jointIndex = RightHandThumb1 = 18 +jointIndex = RightHandThumb2 = 19 +jointIndex = RightHandThumb3 = 20 +jointIndex = RightHandThumb4 = 21 +jointIndex = LeftFoot = 8 +jointIndex = LeftForeArm = 40 +jointIndex = Neck = 62 +jointIndex = Head = 63 +jointIndex = Hips = 0 +jointIndex = RightHandPinky1 = 30 +jointIndex = RightHandPinky2 = 31 +jointIndex = RightHandPinky3 = 32 +jointIndex = RightHandPinky4 = 33 +jointIndex = RightLeg = 2 +jointIndex = RightForeArm = 16 +jointIndex = LeftHandRing1 = 46 +jointIndex = LeftHandRing2 = 47 +jointIndex = LeftHandRing3 = 48 +jointIndex = LeftHandRing4 = 49 +jointIndex = LeftHandThumb1 = 54 +jointIndex = LeftHandThumb2 = 55 +jointIndex = LeftHandThumb3 = 56 +jointIndex = LeftHandThumb4 = 57 +jointIndex = HeadTop_End = 66 +jointIndex = LeftUpLeg = 6 +jointIndex = LeftToeBase = 9 +jointIndex = LeftHandPinky1 = 42 +jointIndex = LeftHandPinky2 = 43 +jointIndex = LeftHandPinky3 = 44 +jointIndex = LeftHandPinky4 = 45 +jointIndex = LeftLeg = 7 +jointIndex = RightEye = 65 +jointIndex = RightHand = 17 +jointIndex = RightToeBase = 4 +jointIndex = RightUpLeg = 1 +jointIndex = RightArm = 15 +jointIndex = RightHandRing1 = 26 +jointIndex = RightHandRing2 = 27 +jointIndex = RightHandRing3 = 28 +jointIndex = RightHandRing4 = 29 +jointIndex = RightHandIndex1 = 22 +jointIndex = RightHandIndex2 = 23 +jointIndex = RightHandIndex3 = 24 +jointIndex = RightHandIndex4 = 25 +jointIndex = LeftToe_End = 10 +jointIndex = LeftHandMiddle1 = 58 +jointIndex = LeftHandMiddle2 = 59 +jointIndex = LeftHandMiddle3 = 60 +jointIndex = LeftShoulder = 38 +jointIndex = LeftHandMiddle4 = 61 +jointIndex = RightFoot = 3 +jointIndex = LeftHand = 41 +jointIndex = RightHandMiddle1 = 34 +jointIndex = RightHandMiddle2 = 35 +jointIndex = RightHandMiddle3 = 36 +jointIndex = RightShoulder = 14 +jointIndex = LeftEye = 64 +jointIndex = RightHandMiddle4 = 37 +jointIndex = Body = 67 +jointIndex = LeftArm = 39 +jointIndex = RightToe_End = 5 +jointIndex = Spine = 11 diff --git a/interface/resources/meshes/being_of_light/being_of_light.fbx b/interface/resources/meshes/being_of_light/being_of_light.fbx new file mode 100644 index 0000000000..20e71abd6d Binary files /dev/null and b/interface/resources/meshes/being_of_light/being_of_light.fbx differ diff --git a/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_DiffuseMap.png b/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_DiffuseMap.png new file mode 100644 index 0000000000..8fcf588643 Binary files /dev/null and b/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_DiffuseMap.png differ diff --git a/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_EmissiveMap.png b/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_EmissiveMap.png new file mode 100644 index 0000000000..d836aebde8 Binary files /dev/null and b/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_EmissiveMap.png differ diff --git a/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_NormalMap.png b/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_NormalMap.png new file mode 100644 index 0000000000..f5000b86e9 Binary files /dev/null and b/interface/resources/meshes/being_of_light/textures/BaseMesh_BeingofLight_NormalMap.png differ diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml new file mode 100644 index 0000000000..d961285a46 --- /dev/null +++ b/interface/resources/qml/+android/Stats.qml @@ -0,0 +1,394 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import '.' + +Item { + id: stats + + anchors.leftMargin: 300 + objectName: "StatsItem" + property int modality: Qt.NonModal + implicitHeight: row.height + implicitWidth: row.width + + Component.onCompleted: { + stats.parentChanged.connect(fill); + fill(); + } + Component.onDestruction: { + stats.parentChanged.disconnect(fill); + } + + function fill() { + // This will cause a warning at shutdown, need to find another way to remove + // the warning other than filling the anchors to the parent + anchors.horizontalCenter = parent.horizontalCenter + } + + Hifi.Stats { + id: root + objectName: "Stats" + implicitHeight: row.height + implicitWidth: row.width + + anchors.horizontalCenter: parent.horizontalCenter + readonly property string bgColor: "#AA111111" + + Row { + id: row + spacing: 8 + Rectangle { + width: generalCol.width + 8; + height: generalCol.height + 8; + color: root.bgColor; + + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + + Column { + id: generalCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Servers: " + root.serverCount + } + StatText { + text: "Avatars: " + root.avatarCount + } + StatText { + text: "Game Rate: " + root.gameLoopRate + } + StatText { + visible: root.expanded + text: root.gameUpdateStats + } + StatText { + text: "Render Rate: " + root.renderrate.toFixed(2); + } + StatText { + text: "Present Rate: " + root.presentrate.toFixed(2); + } + StatText { + visible: root.expanded + text: " Present New Rate: " + root.presentnewrate.toFixed(2); + } + StatText { + visible: root.expanded + text: " Present Drop Rate: " + root.presentdroprate.toFixed(2); + } + StatText { + text: "Stutter Rate: " + root.stutterrate.toFixed(3); + visible: root.stutterrate != -1; + } + StatText { + text: "Missed Frame Count: " + root.appdropped; + visible: root.appdropped > 0; + } + StatText { + text: "Long Render Count: " + root.longrenders; + visible: root.longrenders > 0; + } + StatText { + text: "Long Submit Count: " + root.longsubmits; + visible: root.longsubmits > 0; + } + StatText { + text: "Long Frame Count: " + root.longframes; + visible: root.longframes > 0; + } + StatText { + text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount + } + StatText { + text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2) + } + StatText { + visible: root.expanded + text: "Asset Mbps In/Out: " + root.assetMbpsIn.toFixed(2) + "/" + root.assetMbpsOut.toFixed(2) + } + StatText { + visible: root.expanded + text: "Avatars Updated: " + root.updatedAvatarCount + } + StatText { + visible: root.expanded + text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount + } + } + } + + Rectangle { + width: pingCol.width + 8 + height: pingCol.height + 8 + color: root.bgColor; + visible: root.audioPing != -2 + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + Column { + id: pingCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Audio ping/loss: " + root.audioPing + "/" + root.audioPacketLoss + "%" + } + StatText { + text: "Avatar ping: " + root.avatarPing + } + StatText { + text: "Entities avg ping: " + root.entitiesPing + } + StatText { + text: "Asset ping: " + root.assetPing + } + StatText { + visible: root.expanded; + text: "Messages max ping: " + root.messagePing + } + } + } + + Rectangle { + width: geoCol.width + 8 + height: geoCol.height + 8 + color: root.bgColor; + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + Column { + id: geoCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Position: " + root.position.x.toFixed(1) + ", " + + root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1) + } + StatText { + text: "Speed: " + root.speed.toFixed(1) + } + StatText { + text: "Yaw: " + root.yaw.toFixed(1) + } + StatText { + visible: root.expanded; + text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " + + root.avatarMixerInPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Avatar Mixer Out: " + root.avatarMixerOutKbps + " kbps, " + + root.avatarMixerOutPps + "pps, " + + root.myAvatarSendRate.toFixed(2) + "hz"; + } + StatText { + visible: root.expanded; + text: "Audio Mixer In: " + root.audioMixerInKbps + " kbps, " + + root.audioMixerInPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " + + "Silent: " + root.audioSilentInboundPPS + " pps"; + } + StatText { + visible: root.expanded; + text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " + + root.audioMixerOutPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Audio Out Mic: " + root.audioOutboundPPS + " pps, " + + "Silent: " + root.audioSilentOutboundPPS + " pps"; + } + StatText { + visible: root.expanded; + text: "Audio Codec: " + root.audioCodec + " Noise Gate: " + + root.audioNoiseGate; + } + StatText { + visible: root.expanded; + text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps"; + } + StatText { + visible: root.expanded; + text: "Downloads: " + root.downloads + "/" + root.downloadLimit + + ", Pending: " + root.downloadsPending; + } + StatText { + visible: root.expanded; + text: "Processing: " + root.processing + + ", Pending: " + root.processingPending; + } + StatText { + visible: root.expanded && root.downloadUrls.length > 0; + text: "Download URLs:" + } + ListView { + width: geoCol.width + height: root.downloadUrls.length * 15 + + visible: root.expanded && root.downloadUrls.length > 0; + + model: root.downloadUrls + delegate: StatText { + visible: root.expanded; + text: modelData.length > 30 + ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) + : modelData + } + } + } + } + Rectangle { + width: octreeCol.width + 8 + height: octreeCol.height + 8 + color: root.bgColor; + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + hoverEnabled: true + } + Column { + id: octreeCol + spacing: 4; x: 4; y: 4; + StatText { + text: "Engine: " + root.engineFrameTime.toFixed(1) + " ms" + } + StatText { + text: "Batch: " + root.batchFrameTime.toFixed(1) + " ms" + } + StatText { + text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" + } + StatText { + text: "Triangles: " + root.triangles + + " / Material Switches: " + root.materialSwitches + } + StatText { + text: "GPU Free Memory: " + root.gpuFreeMemory + " MB"; + } + StatText { + text: "GPU Textures: "; + } + StatText { + text: " Count: " + root.gpuTextures; + } + StatText { + text: " Pressure State: " + root.gpuTextureMemoryPressureState; + } + StatText { + text: " Resource Allocated / Populated / Pending: "; + } + StatText { + text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB"; + } + StatText { + text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB"; + } + StatText { + text: " Framebuffer Memory: " + root.gpuTextureFramebufferMemory + " MB"; + } + StatText { + text: " External Memory: " + root.gpuTextureExternalMemory + " MB"; + } + StatText { + text: "GPU Buffers: " + } + StatText { + text: " Count: " + root.gpuBuffers; + } + StatText { + text: " Memory: " + root.gpuBufferMemory + " MB"; + } + StatText { + text: "GL Swapchain Memory: " + root.glContextSwapchainMemory + " MB"; + } + StatText { + text: "QML Texture Memory: " + root.qmlTextureMemory + " MB"; + } + StatText { + visible: root.expanded; + text: "Items rendered / considered: " + + root.itemRendered + " / " + root.itemConsidered; + } + StatText { + visible: root.expanded; + text: " out of view: " + root.itemOutOfView + + " too small: " + root.itemTooSmall; + } + StatText { + visible: root.expanded; + text: "Shadows rendered / considered: " + + root.shadowRendered + " / " + root.shadowConsidered; + } + StatText { + visible: root.expanded; + text: " out of view: " + root.shadowOutOfView + + " too small: " + root.shadowTooSmall; + } + StatText { + visible: !root.expanded + text: "Octree Elements Server: " + root.serverElements + + " Local: " + root.localElements; + } + StatText { + visible: root.expanded + text: "Octree Sending Mode: " + root.sendingMode; + } + StatText { + visible: root.expanded + text: "Octree Packets to Process: " + root.packetStats; + } + StatText { + visible: root.expanded + text: "Octree Elements - "; + } + StatText { + visible: root.expanded + text: "\tServer: " + root.serverElements + + " Internal: " + root.serverInternal + + " Leaves: " + root.serverLeaves; + } + StatText { + visible: root.expanded + text: "\tLocal: " + root.localElements + + " Internal: " + root.localInternal + + " Leaves: " + root.localLeaves; + } + StatText { + visible: root.expanded + text: "LOD: " + root.lodStatus; + } + } + } + } + + Rectangle { + y: 250 + visible: root.timingExpanded + width: perfText.width + 8 + height: perfText.height + 8 + color: root.bgColor; + StatText { + x: 4; y: 4 + id: perfText + font.family: root.monospaceFont + text: "------------------------------------------ Function " + + "--------------------------------------- --msecs- -calls--\n" + + root.timingStats; + } + } + + Connections { + target: root.parent + onWidthChanged: { + root.x = root.parent.width - root.width; + } + } + } + +} diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml index 45cf09925a..37984965d9 100644 --- a/interface/resources/qml/CurrentAPI.qml +++ b/interface/resources/qml/CurrentAPI.qml @@ -16,10 +16,9 @@ import "controls-uit" as HifiControls Item { id: root width: parent.width - height: parent.height + height: parent.height property var hideQtMethods: true - property var maxUpdateValues: 20 property var maxReloadValues: 200 property var apiMembers: [] @@ -30,7 +29,7 @@ Item { property Component keyboard Rectangle { - color: "white" + color: hifi.colors.baseGray width: parent.width height: parent.height } @@ -51,32 +50,22 @@ Item { Row { id: topBar anchors.left: parent.left - anchors.leftMargin: 8 + anchors.leftMargin: 30 + anchors.top: parent.top + anchors.topMargin: 30 width: parent.width - height: 50 - HifiControls.GlyphButton { - id: search - enabled: true - glyph: hifi.glyphs.search - color: hifi.colors.text - size: 48 - width: 50 - height: 50 - onClicked: { - addListElements(searchBar.text); - focus = true; - } - } + height: 40 HifiControls.GlyphButton { id: back; enabled: true; + color: hifi.buttons.black glyph: hifi.glyphs.backward - color: hifi.colors.text - size: 48 - width: 30 - height: 50 - anchors.margins: 2 + size: 40 + width: 40 + height: 40 + anchors.left: search.right + anchors.leftMargin: 12 onClicked: { var text = searchBar.text; var chain = text.split("."); @@ -99,17 +88,20 @@ Item { } } - TextField { - id: searchBar + HifiControls.TextField { + id: searchBar focus: true - font.pixelSize: 16 - width: 2*(parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3 - height: parent.height - font.family: ralewayRegular.name + isSearchField: true + width: parent.width - 112 + height: 40 + colorScheme: hifi.colorSchemes.dark + anchors.left: back.right + anchors.leftMargin: 10 + font.family: firaSansSemiBold.name placeholderText: "Search" onAccepted: { console.log("Enter Pressed"); - search.clicked(); + addListElements(searchBar.text); } onActiveFocusChanged: { if (activeFocus && HMD.mounted) { @@ -119,15 +111,27 @@ Item { } } - } + } + } - HifiControls.Button { + Row { + id: topBar2 + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.top: topBar.bottom + anchors.topMargin: 30 + width: parent.width -60 + height: 40 + + HifiControls.GlyphButton { id: addMember; enabled: true; - text: "+" - width: 50 - height: 50 - anchors.margins: 2 + color: hifi.buttons.black + glyph: hifi.glyphs.maximize + width: 40 + height: 40 + anchors.top: parent.top + anchors.left: parent.left onClicked: { addNewMember(); updateList.start(); @@ -138,36 +142,48 @@ Item { HifiControls.Button { id: evaluate; enabled: true; + color: hifi.buttons.black text: "Eval" - width: 50 - height: 50 - anchors.margins: 2 + width: 40 + height: 40 + anchors.left: addMember.right + anchors.leftMargin: 12 onClicked: { evaluateMember(); focus = true; } } - TextField { - id: valueBar - focus: true + + HifiControls.TextField { + id: valueBar + isSearchField: false font.pixelSize: 16 - width: (parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3 - height: parent.height - font.family: ralewayRegular.name + width: parent.width - 208 + height: 40 + colorScheme: hifi.colorSchemes.dark + font.family: firaSansSemiBold.name placeholderText: "Value" - textColor: "#4466DD" - anchors.margins: 2 + anchors.left: evaluate.right + anchors.leftMargin: 12 + onActiveFocusChanged: { + if (activeFocus && HMD.mounted) { + keyboard.raised = true; + } else { + keyboard.raised = false; + } + } } HifiControls.GlyphButton { id: reload; enabled: false; + color: hifi.buttons.black glyph: hifi.glyphs.reload - color: hifi.colors.text - size: 48 - width: 50 - height: 50 - anchors.margins: 2 + size: 40 + width: 40 + height: 40 + anchors.right: update.left + anchors.rightMargin: 12 onClicked: { reloadListValues(); focus = true; @@ -177,11 +193,12 @@ Item { HifiControls.GlyphButton { id: update; enabled: false; + color: hifi.buttons.black glyph: hifi.glyphs.playback_play - size: 48 - width: 50 - height: 50 - anchors.margins: 2 + size: 40 + width: 40 + height: 40 + anchors.right: parent.right onClicked: { if (isReloading) { update.glyph = hifi.glyphs.playback_play @@ -196,71 +213,104 @@ Item { } } } - - ListModel { - id: memberModel - } - Component { - id: memberDelegate - - Row { - id: memberRow - property var isMainKey: apiType === "class"; - spacing: 10 - Rectangle { - width: isMainKey ? 20 : 40; - height: parent.height - } - - RalewayRegular { - text: apiMember - size: !isMainKey ? 16 : 22 - MouseArea { - width: list.width - height: parent.height - onClicked: { - searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember; - valueBar.text = !apiValue ? "" : apiValue; - list.currentIndex = index; - evaluatingIdx = index; - } - onDoubleClicked: { - if (apiType === "class") { - addListElements(apiMember+"."); - } else { - isolateElement(evaluatingIdx); - } - - } - } - } - - - RalewayRegular { - text: apiType - size: 14 - color: hifi.colors.baseGrayHighlight - } - - RalewayRegular { - text: !apiValue ? "" : apiValue; - size: 16 - color: "#4466DD" - } - } - } - - Rectangle { id: membersBackground anchors { - left: parent.left; right: parent.right; top: topBar.bottom; bottom: parent.bottom; - margins: hifi.dimensions.contentMargin.x - bottomMargin: hifi.dimensions.contentSpacing.y + 40 + left: parent.left; right: parent.right; top: topBar2.bottom; bottom: bottomBar.top; + margins: 30 + } + color: hifi.colors.tableBackgroundDark + border.color: hifi.colors.lightGray + border.width: 2 + radius: 5 + + ListModel { + id: memberModel + } + + Component { + id: memberDelegate + Item { + id: item + width: parent.width + anchors.left: parent.left + height: 26 + clip: true + + Rectangle { + width: parent.width + height: parent.height + color: index % 2 == 0 ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd + anchors.verticalCenter: parent.verticalCenter + + Row { + id: memberRow + anchors.bottom: parent.bottom + anchors.verticalCenter: parent.verticalCenter + spacing: 10 + + FiraSansSemiBold { + property var isMainKey: apiType === "class"; + text: apiMember + size: isMainKey ? 17 : 15 + font.bold: true + anchors.verticalCenter: parent.verticalCenter + color: isMainKey ? hifi.colors.faintGray : hifi.colors.lightGrayText + MouseArea { + width: list.width + height: parent.height + onClicked: { + searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember; + valueBar.text = !apiValue ? "" : apiValue; + list.currentIndex = index; + evaluatingIdx = index; + } + onDoubleClicked: { + if (apiType === "class") { + addListElements(apiMember+"."); + } else { + isolateElement(evaluatingIdx); + } + } + } + } + + FiraSansRegular { + text: apiType + anchors.left: apiMember.right + anchors.verticalCenter: parent.verticalCenter + size: 13 + color: hifi.colors.lightGrayText + } + + FiraSansRegular { + text: !apiValue ? "" : apiValue; + anchors.left: apiType.right + anchors.verticalCenter: parent.verticalCenter + size: 14 + color: hifi.colors.primaryHighlight + } + } + } + } + } + + Component { + id: highlight + Rectangle { + anchors { + left: list.left + right: scrollBar.left + leftMargin: 2 + rightMargin: 2 + } + color: hifi.colors.primaryHighlight + radius: 4 + z: 10 + opacity: 0.5 + } } - color: "white" - radius: 4 ListView { id: list @@ -269,23 +319,16 @@ Item { left: parent.left right: scrollBar.left bottom: parent.bottom - margins: 4 + topMargin: 2 + leftMargin: 2 + bottomMargin: 2 } clip: true cacheBuffer: 4000 model: memberModel delegate: memberDelegate highlightMoveDuration: 0 - - highlight: Rectangle { - anchors { - left: parent ? parent.left : undefined - right: parent ? parent.right : undefined - leftMargin: hifi.dimensions.borderWidth - rightMargin: hifi.dimensions.borderWidth - } - color: "#BBDDFF" - } + highlight: highlight onMovementStarted: { scrollSlider.manual = true; } @@ -310,12 +353,11 @@ Item { top: parent.top right: parent.right bottom: parent.bottom - topMargin: 4 - bottomMargin: 4 + margins: 2 } - width: scrolling ? 18 : 0 - radius: 4 - color: hifi.colors.baseGrayShadow + width: 22 + height: parent.height - 4 + color: hifi.colors.tableScrollBackgroundDark MouseArea { anchors.fill: parent @@ -344,14 +386,12 @@ Item { y = index*(scrollBar.height - scrollSlider.height)/(list.count - 1); } - anchors { - right: parent.right - rightMargin: 3 - } - width: 12 - height: (list.height / list.contentHeight) * list.height - radius: width / 4 - color: "white" + anchors.right: parent.right + anchors.margins: 2 + width: 18 + height: ((list.height / list.contentHeight) * list.height) < 15 ? 15 : (list.height / list.contentHeight) * list.height + radius: 5 + color: hifi.colors.tableScrollHandleDark visible: scrollBar.scrolling; @@ -373,66 +413,75 @@ Item { } } } - - HifiControls.GlyphButton { - id: clipboard; - enabled: true; - glyph: hifi.glyphs.scriptNew - size: 38 - width: 50 - height: 50 + + Row { + id: bottomBar anchors.left: parent.left + anchors.leftMargin: 30 anchors.bottom: parent.bottom - anchors.margins: 2 - anchors.leftMargin: 8 - onClicked: { - var buffer = ""; - for (var i = 0; i < memberModel.count; i++) { - var datarow = memberModel.get(i); - buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue; - } - Window.copyToClipboard(buffer); - focus = true; - } - } - - HifiControls.Button { - id: debug; - enabled: true; - text: "Debug Script" - width: 120 - height: 50 - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 2 - anchors.rightMargin: 8 - onClicked: { - sendToScript({type: "selectScript"}); - } - } + anchors.bottomMargin: 30 + width: parent.width + height: 40 - HifiControls.CheckBox { - id: hideQt - boxSize: 25 - boxRadius: 3 - checked: true - anchors.left: clipboard.right - anchors.leftMargin: 8 - anchors.verticalCenter: clipboard.verticalCenter - anchors.margins: 2 - onClicked: { - hideQtMethods = checked; - addListElements(); + HifiControls.GlyphButton { + id: clipboard; + enabled: true; + color: hifi.buttons.black + glyph: hifi.glyphs.scriptNew + size: 25 + width: 40 + height: 40 + anchors.left: parent.left + onClicked: { + var buffer = ""; + for (var i = 0; i < memberModel.count; i++) { + var datarow = memberModel.get(i); + buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue; + } + Window.copyToClipboard(buffer); + focus = true; + } } - } - HifiControls.Label { - id: hideLabel - anchors.left: hideQt.right - anchors.verticalCenter: clipboard.verticalCenter - anchors.margins: 2 - font.pixelSize: 15 - text: "Hide Qt Methods" + HifiControls.CheckBox { + id: hideQt + colorScheme: hifi.checkbox.dark + boxSize: 25 + boxRadius: 3 + checked: true + anchors.left: clipboard.right + anchors.leftMargin: 10 + anchors.verticalCenter: clipboard.verticalCenter + onClicked: { + hideQtMethods = checked; + addListElements(); + } + } + + HifiControls.Label { + id: hideLabel + anchors.left: hideQt.right + anchors.verticalCenter: clipboard.verticalCenter + anchors.margins: 2 + font.pixelSize: 15 + text: "Hide Qt Methods" + } + + HifiControls.Button { + id: debug; + enabled: true; + color: hifi.buttons.black + text: "Debug Script" + width: 120 + height: 40 + anchors.right: parent.right + anchors.rightMargin: 60 + anchors.bottom: parent.bottom + + onClicked: { + sendToScript({type: "selectScript"}); + } + } } HifiControls.Keyboard { @@ -639,4 +688,4 @@ Item { } signal sendToScript(var message); -} \ No newline at end of file +} diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml deleted file mode 100644 index b1120058f9..0000000000 --- a/interface/resources/qml/ToolWindow.qml +++ /dev/null @@ -1,256 +0,0 @@ -// -// ToolWindow.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtWebEngine 1.1 -import QtWebChannel 1.0 -import Qt.labs.settings 1.0 - -import "windows" -import "controls-uit" -import "styles-uit" - - -ScrollingWindow { - id: toolWindow - resizable: true - objectName: "ToolWindow" - destroyOnCloseButton: false - destroyOnHidden: false - closable: true - shown: false - title: "Edit" - property alias tabView: tabView - implicitWidth: 520; implicitHeight: 695 - minSize: Qt.vector2d(456, 500) - - HifiConstants { id: hifi } - - onParentChanged: { - if (parent) { - x = 120; - y = 120; - } - } - - onShownChanged: { - keyboardEnabled = HMD.active; - } - - Settings { - category: "ToolWindow.Position" - property alias x: toolWindow.x - property alias y: toolWindow.y - } - - Item { - id: toolWindowTabViewItem - height: pane.scrollHeight - width: pane.contentWidth - anchors.left: parent.left - anchors.top: parent.top - - TabView { - id: tabView - width: pane.contentWidth - // Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view. - height: pane.scrollHeight - property int tabCount: 0 - - Repeater { - model: 4 - Tab { - // Force loading of the content even if the tab is not visible - // (required for letting the C++ code access the webview) - active: true - enabled: false - property string originalUrl: "" - - WebView { - id: webView - anchors.fill: parent - enabled: false - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - } - - onEnabledChanged: toolWindow.updateVisiblity() - } - } - } - - style: TabViewStyle { - - frame: Rectangle { // Background shown before content loads. - anchors.fill: parent - color: hifi.colors.baseGray - } - - frameOverlap: 0 - - tab: Rectangle { - implicitWidth: text.width - implicitHeight: 3 * text.height - color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark - - RalewayRegular { - id: text - text: styleData.title - font.capitalization: Font.AllUppercase - size: hifi.fontSizes.tabName - width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x - elide: Text.ElideRight - color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText - horizontalAlignment: Text.AlignHCenter - anchors.centerIn: parent - } - - Rectangle { // Separator. - width: 1 - height: parent.height - color: hifi.colors.black - anchors.left: parent.left - anchors.top: parent.top - visible: styleData.index > 0 - - Rectangle { - width: 1 - height: 1 - color: hifi.colors.baseGray - anchors.left: parent.left - anchors.bottom: parent.bottom - } - } - - Rectangle { // Active underline. - width: parent.width - (styleData.index > 0 ? 1 : 0) - height: 1 - anchors.right: parent.right - anchors.bottom: parent.bottom - color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray - } - } - - tabOverlap: 0 - } - } - } - - function updateVisiblity() { - if (visible) { - for (var i = 0; i < tabView.count; ++i) { - if (tabView.getTab(i).enabled) { - return; - } - } - shown = false; - } - } - - function findIndexForUrl(source) { - for (var i = 0; i < tabView.count; ++i) { - var tab = tabView.getTab(i); - if (tab.originalUrl === source) { - return i; - } - } - return -1; - } - - function findTabForUrl(source) { - var index = findIndexForUrl(source); - if (index < 0) { - return; - } - return tabView.getTab(index); - } - - function showTabForUrl(source, newVisible) { - var index = findIndexForUrl(source); - if (index < 0) { - return; - } - - var tab = tabView.getTab(index); - if (newVisible) { - toolWindow.shown = true - tab.enabled = true - } else { - tab.enabled = false; - updateVisiblity(); - } - } - - function findFreeTab() { - for (var i = 0; i < tabView.count; ++i) { - var tab = tabView.getTab(i); - if (tab && (!tab.originalUrl || tab.originalUrl === "")) { - return i; - } - } - return -1; - } - - function removeTabForUrl(source) { - var index = findIndexForUrl(source); - if (index < 0) { - return; - } - - var tab = tabView.getTab(index); - tab.title = ""; - tab.enabled = false; - tab.originalUrl = ""; - tab.item.url = "about:blank"; - tab.item.enabled = false; - tabView.tabCount--; - } - - function addWebTab(properties) { - if (!properties.source) { - console.warn("Attempted to open Web Tool Pane without URL"); - return; - } - - var existingTabIndex = findIndexForUrl(properties.source); - if (existingTabIndex >= 0) { - var tab = tabView.getTab(existingTabIndex); - return tab.item; - } - - var freeTabIndex = findFreeTab(); - if (freeTabIndex === -1) { - console.warn("Unable to add new tab"); - return; - } - - if (properties.width) { - tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x); - } - - if (properties.height) { - tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y); - } - - var tab = tabView.getTab(freeTabIndex); - tab.title = properties.title || "Unknown"; - tab.enabled = true; - tab.originalUrl = properties.source; - - var result = tab.item; - result.enabled = true; - tabView.tabCount++; - result.url = properties.source; - return result; - } -} diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index e636bfc27f..a21d1f8efd 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -24,13 +24,18 @@ TextField { property bool isSearchField: false property string label: "" property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0) + property bool hasDefocusedBorder: true; property bool hasRoundedBorder: false + property int roundedBorderRadius: 4 property bool error: false; property bool hasClearButton: false; + property string leftPermanentGlyph: ""; + property string centerPlaceholderGlyph: ""; placeholderText: textField.placeholderText FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; } font.family: firaSansSemiBold.name font.pixelSize: hifi.fontSizes.textFieldInput font.italic: textField.text == "" @@ -54,6 +59,7 @@ TextField { } style: TextFieldStyle { + id: style; textColor: { if (isLightColorScheme) { if (textField.activeFocus) { @@ -98,9 +104,28 @@ TextField { } } border.color: textField.error ? hifi.colors.redHighlight : - (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + (textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color)) border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 - radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0) + radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0) + + HiFiGlyphs { + text: textField.leftPermanentGlyph; + color: textColor; + size: hifi.fontSizes.textFieldSearchIcon; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + anchors.leftMargin: hifi.dimensions.textPadding - 2; + visible: text; + } + + HiFiGlyphs { + text: textField.centerPlaceholderGlyph; + color: textColor; + size: parent.height; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + visible: text && !textField.focus && textField.text === ""; + } HiFiGlyphs { text: hifi.glyphs.search @@ -132,7 +157,7 @@ TextField { placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - padding.left: (isSearchField ? textField.height - 2 : 0) + hifi.dimensions.textPadding + padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding } diff --git a/interface/resources/qml/hifi/+android/Desktop.qml b/interface/resources/qml/hifi/+android/Desktop.qml new file mode 100644 index 0000000000..99d792b664 --- /dev/null +++ b/interface/resources/qml/hifi/+android/Desktop.qml @@ -0,0 +1,63 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import Qt.labs.settings 1.0 + +import "../desktop" as OriginalDesktop +import ".." +import "." +import "./toolbars" + +OriginalDesktop.Desktop { + id: desktop + + MouseArea { + id: hoverWatch + anchors.fill: parent + hoverEnabled: true + propagateComposedEvents: true + scrollGestureEnabled: false // we don't need/want these + onEntered: ApplicationCompositor.reticleOverDesktop = true + onExited: ApplicationCompositor.reticleOverDesktop = false + acceptedButtons: Qt.NoButton + + + } + + + Component { id: toolbarBuilder; Toolbar { } } + // This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted. + // Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got + // wiped during startup. + Toolbar { + id: sysToolbar; + objectName: "com.highfidelity.interface.toolbar.system"; + // Magic: sysToolbar.x and y come from settings, and are bound before the properties specified here are applied. + x: sysToolbar.x; + y: sysToolbar.y; + } + property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar + map[sysToolbar.objectName] = sysToolbar; + return map; })({}); + + Component.onCompleted: { + } + + // Accept a download through the webview + property bool webViewProfileSetup: false + property string currentUrl: "" + property string adaptedPath: "" + property string tempDir: "" + + // Create or fetch a toolbar with the given name + function getToolbar(name) { + var result = toolbars[name]; + if (!result) { + result = toolbars[name] = toolbarBuilder.createObject(desktop, {}); + result.objectName = name; + } + return result; + } +} + + diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 896b3cf10e..8c732aac40 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -22,10 +22,6 @@ OriginalDesktop.Desktop { acceptedButtons: Qt.NoButton } - // The tool window, one instance - property alias toolWindow: toolWindow - ToolWindow { id: toolWindow } - Action { text: "Open Browser" shortcut: "Ctrl+B" diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 8576e26fcd..a9629c0e4e 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -102,7 +102,7 @@ Column { 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', - 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), + 'protocol=' + encodeURIComponent(Window.protocolSignature()), 'page=' + pageNumber ]; var url = metaverseBase + 'user_stories?' + options.join('&'); diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml deleted file mode 100644 index 11a6c99b0c..0000000000 --- a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml +++ /dev/null @@ -1,73 +0,0 @@ -// -// SendMoney.qml -// qml/hifi/commerce/wallet -// -// SendMoney -// -// Created by Zach Fox on 2017-08-18 -// Copyright 2017 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 -// - -import Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls - -// references XXX from root context - -Item { - HifiConstants { id: hifi; } - - id: root; - - Connections { - target: Commerce; - } - - // "Unavailable" - RalewayRegular { - text: "You currently cannot send money to other High Fidelity users."; - // Anchors - anchors.fill: parent; - // Text size - size: 24; - // Style - color: hifi.colors.faintGray; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - } - - // - // FUNCTION DEFINITIONS START - // - // - // Function Name: fromScript() - // - // Relevant Variables: - // None - // - // Arguments: - // message: The message sent from the JavaScript. - // Messages are in format "{method, params}", like json-rpc. - // - // Description: - // Called when a message is received from a script. - // - function fromScript(message) { - switch (message.method) { - default: - console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); - } - } - signal sendSignalToWallet(var msg); - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index ef2b007dc8..ae42b8e3e1 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -19,8 +19,7 @@ import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon - -// references XXX from root context +import "./sendMoney" Rectangle { HifiConstants { id: hifi; } @@ -316,18 +315,29 @@ Rectangle { Connections { onSendSignalToWallet: { - sendToScript(msg); + if (msg.method === 'transactionHistory_usernameLinkClicked') { + userInfoViewer.url = msg.usernameLink; + userInfoViewer.visible = true; + } else { + sendToScript(msg); + } } } } SendMoney { id: sendMoney; + z: 997; visible: root.activeView === "sendMoney"; - anchors.top: titleBarContainer.bottom; - anchors.bottom: tabButtonsContainer.top; - anchors.left: parent.left; - anchors.right: parent.right; + anchors.fill: parent; + parentAppTitleBarHeight: titleBarContainer.height; + parentAppNavBarHeight: tabButtonsContainer.height; + + Connections { + onSendSignalToWallet: { + sendToScript(msg); + } + } } Security { @@ -379,7 +389,7 @@ Rectangle { // Item { id: tabButtonsContainer; - visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange"; + visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendMoneyStep"; property int numTabs: 5; // Size width: root.width; @@ -497,7 +507,7 @@ Rectangle { Rectangle { id: sendMoneyButtonContainer; visible: !walletSetup.visible; - color: hifi.colors.black; + color: root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black; anchors.top: parent.top; anchors.left: exchangeMoneyButtonContainer.right; anchors.bottom: parent.bottom; @@ -513,7 +523,7 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: -2; // Style - color: hifi.colors.lightGray50; + color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; } RalewaySemiBold { @@ -528,12 +538,24 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 4; // Style - color: hifi.colors.lightGray50; + color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; wrapMode: Text.WordWrap; // Alignment horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignTop; } + + MouseArea { + id: sendMoneyTabMouseArea; + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.activeView = "sendMoney"; + tabButtonsContainer.resetTabButtonColors(); + } + onEntered: parent.color = hifi.colors.blueHighlight; + onExited: parent.color = root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black; + } } // "SECURITY" tab button @@ -665,9 +687,16 @@ Rectangle { // TAB BUTTONS END // + HifiControls.TabletWebView { + id: userInfoViewer; + z: 998; + anchors.fill: parent; + visible: false; + } + Item { id: keyboardContainer; - z: 998; + z: 999; visible: keyboard.raised; property bool punctuationMode: false; anchors { @@ -713,6 +742,13 @@ Rectangle { case 'inspectionCertificate_resetCert': // NOP break; + case 'updateConnections': + sendMoney.updateConnections(message.connections); + break; + case 'selectRecipient': + case 'updateSelectedRecipientUsername': + sendMoney.fromScript(message); + break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 780e08caf8..b980c13e3c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -19,8 +19,6 @@ import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls -// references XXX from root context - Item { HifiConstants { id: hifi; } @@ -32,6 +30,20 @@ Item { property int currentHistoryPage: 1; property var pagesAlreadyAdded: new Array(); + onVisibleChanged: { + if (visible) { + transactionHistoryModel.clear(); + Commerce.balance(); + initialHistoryReceived = false; + root.currentHistoryPage = 1; + root.noMoreHistoryData = false; + root.historyRequestPending = true; + Commerce.history(root.currentHistoryPage); + } else { + refreshTimer.stop(); + } + } + Connections { target: Commerce; @@ -189,20 +201,6 @@ Item { color: hifi.colors.white; // Alignment verticalAlignment: Text.AlignVCenter; - - onVisibleChanged: { - if (visible) { - transactionHistoryModel.clear(); - Commerce.balance(); - initialHistoryReceived = false; - root.currentHistoryPage = 1; - root.noMoreHistoryData = false; - root.historyRequestPending = true; - Commerce.history(root.currentHistoryPage); - } else { - refreshTimer.stop(); - } - } } // "balance" text below field @@ -384,8 +382,8 @@ Item { height: visible ? parent.height : 0; AnonymousProRegular { - id: dateText; - text: model.created_at ? getFormattedDate(model.created_at * 1000) : ""; + id: hfcText; + text: model.hfc_text || ''; // Style size: 18; anchors.left: parent.left; @@ -393,7 +391,6 @@ Item { anchors.topMargin: 15; width: 118; height: paintedHeight; - color: hifi.colors.blueAccent; wrapMode: Text.WordWrap; // Alignment horizontalAlignment: Text.AlignRight; @@ -401,20 +398,25 @@ Item { AnonymousProRegular { id: transactionText; - text: model.text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.text) : model.text) : ""; + text: model.transaction_text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.transaction_text) : model.transaction_text) : ""; size: 18; anchors.top: parent.top; anchors.topMargin: 15; - anchors.left: dateText.right; + anchors.left: hfcText.right; anchors.leftMargin: 20; anchors.right: parent.right; height: paintedHeight; color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight; + linkColor: hifi.colors.blueAccent; wrapMode: Text.WordWrap; font.strikeout: model.status === "invalidated"; onLinkActivated: { - sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link}); + if (link.indexOf("users/") !== -1) { + sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link}); + } else { + sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link}); + } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml new file mode 100644 index 0000000000..33cd43bb05 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml @@ -0,0 +1,128 @@ +// +// ConnectionItem.qml +// qml/hifi/commerce/wallet/sendMoney +// +// ConnectionItem +// +// Created by Zach Fox on 2018-01-09 +// Copyright 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import "../../../../styles-uit" +import "../../../../controls-uit" as HifiControlsUit +import "../../../../controls" as HifiControls +import "../../wallet" as HifiWallet + +Item { + HifiConstants { id: hifi; } + + id: root; + property bool isSelected: false; + property string userName; + property string profilePicUrl; + + height: 75; + width: parent.width; + + Rectangle { + id: mainContainer; + // Style + color: root.isSelected ? hifi.colors.faintGray80 : hifi.colors.white; + // Size + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + height: root.height; + + Item { + id: avatarImage; + visible: profileUrl !== "" && userName !== ""; + // Size + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 36; + height: 50; + width: visible ? height : 0; + clip: true; + Image { + id: userImage; + source: root.profilePicUrl !== "" ? ((0 === root.profilePicUrl.indexOf("http")) ? + root.profilePicUrl : (Account.metaverseServerURL + root.profilePicUrl)) : ""; + mipmap: true; + // Anchors + anchors.fill: parent + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Item { + width: userImage.width; + height: userImage.height; + Rectangle { + anchors.centerIn: parent; + width: userImage.width; // This works because userImage is square + height: width; + radius: width; + } + } + } + } + AnimatedImage { + source: "../../../../../icons/profilePicLoading.gif" + anchors.fill: parent; + visible: userImage.status != Image.Ready; + } + } + + RalewaySemiBold { + id: userName; + anchors.left: avatarImage.right; + anchors.leftMargin: 12; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: chooseButton.visible ? chooseButton.left : parent.right; + anchors.rightMargin: chooseButton.visible ? 10 : 0; + // Text size + size: 18; + // Style + color: hifi.colors.blueAccent; + text: root.userName; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + + // "Choose" button + HifiControlsUit.Button { + id: chooseButton; + visible: root.isSelected; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 28; + height: 35; + width: 100; + text: "CHOOSE"; + onClicked: { + var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl }; + sendToSendMoney(msg); + } + } + } + + // + // FUNCTION DEFINITIONS START + // + signal sendToSendMoney(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml new file mode 100644 index 0000000000..43636d47ca --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml @@ -0,0 +1,117 @@ +// +// RecipientDisplay.qml +// qml/hifi/commerce/wallet/sendMoney +// +// RecipientDisplay +// +// Created by Zach Fox on 2018-01-11 +// Copyright 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import "../../../../styles-uit" +import "../../../../controls-uit" as HifiControlsUit +import "../../../../controls" as HifiControls +import "../../common" as HifiCommerceCommon + +Item { + HifiConstants { id: hifi; } + + id: root; + + property bool isDisplayingNearby; // as opposed to 'connections' + property string displayName; + property string userName; + property string profilePic; + property string textColor: hifi.colors.white; + + Item { + visible: root.isDisplayingNearby; + anchors.fill: parent; + + RalewaySemiBold { + id: recipientDisplayName; + text: root.displayName; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.rightMargin: 12; + height: parent.height/2; + // Text size + size: 18; + // Style + color: root.textColor; + verticalAlignment: Text.AlignBottom; + elide: Text.ElideRight; + } + + RalewaySemiBold { + text: root.userName; + // Anchors + anchors.bottom: parent.bottom; + anchors.left: recipientDisplayName.anchors.left; + anchors.leftMargin: recipientDisplayName.anchors.leftMargin; + anchors.right: recipientDisplayName.anchors.right; + anchors.rightMargin: recipientDisplayName.anchors.rightMargin; + height: parent.height/2; + // Text size + size: 16; + // Style + color: root.textColor; + verticalAlignment: Text.AlignTop; + elide: Text.ElideRight; + } + } + + Item { + visible: !root.isDisplayingNearby; + anchors.fill: parent; + + Image { + id: userImage; + source: root.profilePic; + mipmap: true; + // Anchors + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height - 36; + width: height; + layer.enabled: true; + layer.effect: OpacityMask { + maskSource: Item { + width: userImage.width; + height: userImage.height; + Rectangle { + anchors.centerIn: parent; + width: userImage.width; // This works because userImage is square + height: width; + radius: width; + } + } + } + } + + RalewaySemiBold { + text: root.userName; + // Anchors + anchors.left: userImage.right; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height - 4; + // Text size + size: 16; + // Style + color: root.textColor; + verticalAlignment: Text.AlignVCenter; + elide: Text.ElideRight; + } + } +} diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml new file mode 100644 index 0000000000..f66781c919 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -0,0 +1,1575 @@ +// +// SendMoney.qml +// qml/hifi/commerce/wallet/sendMoney +// +// SendMoney +// +// Created by Zach Fox on 2018-01-09 +// Copyright 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import "../../../../styles-uit" +import "../../../../controls-uit" as HifiControlsUit +import "../../../../controls" as HifiControls +import "../../common" as HifiCommerceCommon + +Item { + HifiConstants { id: hifi; } + + id: root; + + property int parentAppTitleBarHeight; + property int parentAppNavBarHeight; + property string currentActiveView: "sendMoneyHome"; + property string nextActiveView: ""; + property bool shouldShowTopAndBottomOfWallet: chooseRecipientConnection.visible || + chooseRecipientNearby.visible || paymentSuccess.visible || paymentFailure.visible; + property bool shouldShowTopOfWallet: sendMoneyStep.visible; + property bool isCurrentlySendingMoney: false; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + x: 0; + y: (root.shouldShowTopAndBottomOfWallet && !root.shouldShowTopOfWallet) ? 0 : root.parentAppTitleBarHeight; + width: parent.width; + height: (root.shouldShowTopAndBottomOfWallet || root.shouldShowTopOfWallet) ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight; + propagateComposedEvents: false; + } + + Connections { + target: Commerce; + + onBalanceResult : { + balanceText.text = result.data.balance; + } + + onTransferHfcToNodeResult: { + root.isCurrentlySendingMoney = false; + + if (result.status === 'success') { + root.nextActiveView = 'paymentSuccess'; + } else { + root.nextActiveView = 'paymentFailure'; + } + } + + onTransferHfcToUsernameResult: { + root.isCurrentlySendingMoney = false; + + if (result.status === 'success') { + root.nextActiveView = 'paymentSuccess'; + } else { + root.nextActiveView = 'paymentFailure'; + } + } + } + + Connections { + target: GlobalServices + onMyUsernameChanged: { + usernameText.text = Account.username; + } + } + + Component.onCompleted: { + Commerce.balance(); + } + + onNextActiveViewChanged: { + if (root.currentActiveView === 'chooseRecipientNearby') { + sendSignalToWallet({method: 'disable_ChooseRecipientNearbyMode'}); + } + + root.currentActiveView = root.nextActiveView; + + if (root.currentActiveView === 'chooseRecipientConnection') { + // Refresh connections model + connectionsLoading.visible = false; + connectionsLoading.visible = true; + sendSignalToWallet({method: 'refreshConnections'}); + } else if (root.currentActiveView === 'sendMoneyHome') { + Commerce.balance(); + } else if (root.currentActiveView === 'chooseRecipientNearby') { + sendSignalToWallet({method: 'enable_ChooseRecipientNearbyMode'}); + } + } + + // Send Money Home BEGIN + Item { + id: sendMoneyHome; + z: 996; + visible: root.currentActiveView === "sendMoneyHome" || root.currentActiveView === "chooseRecipientConnection" || root.currentActiveView === "chooseRecipientNearby"; + anchors.fill: parent; + anchors.topMargin: root.parentAppTitleBarHeight; + anchors.bottomMargin: root.parentAppNavBarHeight; + + // Username Text + RalewayRegular { + id: usernameText; + text: Account.username; + // Text size + size: 24; + // Style + color: hifi.colors.white; + elide: Text.ElideRight; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2; + height: 80; + } + + // HFC Balance Container + Item { + id: hfcBalanceContainer; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.leftMargin: 20; + width: parent.width/2; + height: 80; + + // "HFC" balance label + HiFiGlyphs { + id: balanceLabel; + text: hifi.glyphs.hfc; + // Size + size: 40; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + // Style + color: hifi.colors.white; + } + + // Balance Text + FiraSansRegular { + id: balanceText; + text: "--"; + // Text size + size: 28; + // Anchors + anchors.top: balanceLabel.top; + anchors.bottom: balanceLabel.bottom; + anchors.left: balanceLabel.right; + anchors.leftMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 4; + // Style + color: hifi.colors.white; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + // "balance" text below field + RalewayRegular { + text: "BALANCE (HFC)"; + // Text size + size: 14; + // Anchors + anchors.top: balanceLabel.top; + anchors.topMargin: balanceText.paintedHeight + 20; + anchors.bottom: balanceLabel.bottom; + anchors.left: balanceText.left; + anchors.right: balanceText.right; + height: paintedHeight; + // Style + color: hifi.colors.white; + } + } + + // Send Money + Rectangle { + id: sendMoneyContainer; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + height: 440; + + + LinearGradient { + anchors.fill: parent; + start: Qt.point(0, 0); + end: Qt.point(0, height); + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.white } + GradientStop { position: 1.0; color: hifi.colors.faintGray } + } + } + + RalewaySemiBold { + id: sendMoneyText; + text: "Send Money To:"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.baseGray; + } + + Item { + id: connectionButton; + // Anchors + anchors.top: sendMoneyText.bottom; + anchors.topMargin: 40; + anchors.left: parent.left; + anchors.leftMargin: 75; + height: 95; + width: 95; + + Image { + anchors.top: parent.top; + source: "./images/connection.svg"; + height: 70; + width: parent.width; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignTop; + mipmap: true; + } + + RalewaySemiBold { + text: "Connection"; + // Anchors + anchors.bottom: parent.bottom; + height: 15; + width: parent.width; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + root.nextActiveView = "chooseRecipientConnection"; + filterBar.text = ""; + } + } + } + + Item { + id: nearbyButton; + // Anchors + anchors.top: sendMoneyText.bottom; + anchors.topMargin: connectionButton.anchors.topMargin; + anchors.right: parent.right; + anchors.rightMargin: connectionButton.anchors.leftMargin; + height: connectionButton.height; + width: connectionButton.width; + + Image { + anchors.top: parent.top; + source: "./images/nearby.svg"; + height: 70; + width: parent.width; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignTop; + mipmap: true; + } + + RalewaySemiBold { + text: "Someone Nearby"; + // Anchors + anchors.bottom: parent.bottom; + height: 15; + width: parent.width; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + root.nextActiveView = "chooseRecipientNearby"; + } + } + } + } + } + // Send Money Home END + + // Choose Recipient Connection BEGIN + Rectangle { + id: chooseRecipientConnection; + z: 997; + visible: root.currentActiveView === "chooseRecipientConnection"; + anchors.fill: parent; + color: "#80000000"; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + ListModel { + id: connectionsModel; + } + ListModel { + id: filteredConnectionsModel; + } + + Rectangle { + anchors.centerIn: parent; + width: parent.width - 30; + height: parent.height - 30; + color: "#FFFFFF"; + radius: 8; + + RalewaySemiBold { + id: chooseRecipientText_connections; + text: "Choose Recipient:"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 36; + width: paintedWidth; + height: 30; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + } + + HiFiGlyphs { + id: closeGlyphButton_connections; + text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; + size: 26; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + root.nextActiveView = "sendMoneyHome"; + } + } + } + + // + // FILTER BAR START + // + Item { + id: filterBarContainer; + // Size + height: 40; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 36; + anchors.right: parent.right; + anchors.rightMargin: 16; + anchors.top: chooseRecipientText_connections.bottom; + anchors.topMargin: 12; + + HifiControlsUit.TextField { + id: filterBar; + colorScheme: hifi.colorSchemes.faintGray; + hasClearButton: true; + hasRoundedBorder: true; + hasDefocusedBorder: false; + roundedBorderRadius: filterBar.height/2; + anchors.fill: parent; + centerPlaceholderGlyph: hifi.glyphs.search; + + onTextChanged: { + buildFilteredConnectionsModel(); + } + + onAccepted: { + focus = false; + } + } + } + // + // FILTER BAR END + // + + Item { + id: connectionsContainer; + anchors.top: filterBarContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + AnimatedImage { + id: connectionsLoading; + source: "../../../../../icons/profilePicLoading.gif" + width: 120; + height: width; + anchors.top: parent.top; + anchors.topMargin: 185; + anchors.horizontalCenter: parent.horizontalCenter; + } + + ListView { + id: connectionsList; + ScrollBar.vertical: ScrollBar { + policy: connectionsList.contentHeight > parent.parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded; + parent: connectionsList.parent; + anchors.top: connectionsList.top; + anchors.right: connectionsList.right; + anchors.bottom: connectionsList.bottom; + width: 20; + } + visible: !connectionsLoading.visible; + clip: true; + model: filteredConnectionsModel; + snapMode: ListView.SnapToItem; + // Anchors + anchors.fill: parent; + delegate: ConnectionItem { + isSelected: connectionsList.currentIndex === index; + userName: model.userName; + profilePicUrl: model.profileUrl; + anchors.topMargin: 6; + anchors.bottomMargin: 6; + + Connections { + onSendToSendMoney: { + sendMoneyStep.referrer = "connections"; + sendMoneyStep.selectedRecipientNodeID = ''; + sendMoneyStep.selectedRecipientDisplayName = 'connection'; + sendMoneyStep.selectedRecipientUserName = msg.userName; + sendMoneyStep.selectedRecipientProfilePic = msg.profilePicUrl; + + root.nextActiveView = "sendMoneyStep"; + } + } + + MouseArea { + enabled: connectionsList.currentIndex !== index; + anchors.fill: parent; + propagateComposedEvents: false; + onClicked: { + connectionsList.currentIndex = index; + } + } + } + } + } + } + } + // Choose Recipient Connection END + + // Choose Recipient Nearby BEGIN + Rectangle { + id: chooseRecipientNearby; + z: 997; + color: "#80000000"; + + property string selectedRecipient; + + visible: root.currentActiveView === "chooseRecipientNearby"; + anchors.fill: parent; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + Rectangle { + anchors.centerIn: parent; + width: parent.width - 30; + height: parent.height - 30; + color: "#FFFFFF"; + radius: 8; + + RalewaySemiBold { + text: "Choose Recipient:"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + } + + HiFiGlyphs { + id: closeGlyphButton_nearby; + text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; + size: 26; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + root.nextActiveView = "sendMoneyHome"; + resetSendMoneyData(); + } + } + } + + RalewaySemiBold { + id: selectionInstructions; + text: chooseRecipientNearby.selectedRecipient === "" ? "Trigger or click on\nsomeone nearby to select them" : + "Trigger or click on\nsomeone else to select again"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 100; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: anchors.leftMargin; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + wrapMode: Text.Wrap; + } + + Image { + anchors.top: selectionInstructions.bottom; + anchors.topMargin: 20; + anchors.bottom: selectionMadeContainer.top; + anchors.bottomMargin: 30; + source: "./images/p2p-nearby-unselected.svg"; + width: parent.width; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignVCenter; + mipmap: true; + } + + Rectangle { + id: selectionMadeContainer; + visible: chooseRecipientNearby.selectedRecipient !== ""; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 190; + color: "#F3F3F3"; + radius: 8; + + // Used to square off the top left and top right edges of this container + Rectangle { + color: "#F3F3F3"; + height: selectionMadeContainer.radius; + anchors.top: selectionMadeContainer.top; + anchors.left: selectionMadeContainer.left; + anchors.right: selectionMadeContainer.right; + } + + RalewaySemiBold { + id: sendToText; + text: "Send to:"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 36; + anchors.left: parent.left; + anchors.leftMargin: 36; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + } + + Image { + id: selectedImage; + anchors.top: parent.top; + anchors.topMargin: 24; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 32; + anchors.left: sendToText.right; + anchors.leftMargin: 4; + source: "./images/p2p-nearby-selected.svg"; + width: 50; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignVCenter; + mipmap: true; + } + + RalewaySemiBold { + id: avatarDisplayName; + text: '"' + AvatarList.getAvatar(chooseRecipientNearby.selectedRecipient).sessionDisplayName + '"'; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 34; + anchors.left: selectedImage.right; + anchors.leftMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.blueAccent; + } + + RalewaySemiBold { + id: avatarUserName; + text: sendMoneyStep.selectedRecipientUserName; + // Anchors + anchors.top: avatarDisplayName.bottom; + anchors.topMargin: 16; + anchors.left: selectedImage.right; + anchors.leftMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + height: paintedHeight; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + } + + // "CHOOSE" button + HifiControlsUit.Button { + id: chooseButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 20; + height: 40; + width: 110; + text: "CHOOSE"; + onClicked: { + sendMoneyStep.referrer = "nearby"; + sendMoneyStep.selectedRecipientNodeID = chooseRecipientNearby.selectedRecipient; + chooseRecipientNearby.selectedRecipient = ""; + + root.nextActiveView = "sendMoneyStep"; + } + } + } + } + } + // Choose Recipient Nearby END + + // Send Money Screen BEGIN + Item { + id: sendMoneyStep; + z: 996; + + property string referrer; // either "connections" or "nearby" + property string selectedRecipientNodeID; + property string selectedRecipientDisplayName; + property string selectedRecipientUserName; + property string selectedRecipientProfilePic; + + visible: root.currentActiveView === "sendMoneyStep"; + anchors.fill: parent; + anchors.topMargin: root.parentAppTitleBarHeight; + + RalewaySemiBold { + id: sendMoneyText_sendMoneyStep; + text: "Send Money"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.white; + } + + Item { + id: sendToContainer; + anchors.top: sendMoneyText_sendMoneyStep.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: sendToText_sendMoneyStep; + text: "Send to:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + } + + RecipientDisplay { + anchors.top: parent.top; + anchors.left: sendToText_sendMoneyStep.right; + anchors.right: changeButton.left; + anchors.rightMargin: 12; + height: parent.height; + + displayName: sendMoneyStep.selectedRecipientDisplayName; + userName: sendMoneyStep.selectedRecipientUserName; + profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? + sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; + isDisplayingNearby: sendMoneyStep.referrer === "nearby"; + } + + // "CHANGE" button + HifiControlsUit.Button { + id: changeButton; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + height: 35; + width: 100; + text: "CHANGE"; + onClicked: { + if (sendMoneyStep.referrer === "connections") { + root.nextActiveView = "chooseRecipientConnection"; + } else if (sendMoneyStep.referrer === "nearby") { + root.nextActiveView = "chooseRecipientNearby"; + } + resetSendMoneyData(); + } + } + } + + Item { + id: amountContainer; + anchors.top: sendToContainer.bottom; + anchors.topMargin: 2; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: amountText; + text: "Amount:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.TextField { + id: amountTextField; + colorScheme: hifi.colorSchemes.dark; + inputMethodHints: Qt.ImhDigitsOnly; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: amountText.right; + anchors.right: parent.right; + height: 50; + // Style + leftPermanentGlyph: hifi.glyphs.hfc; + activeFocusOnPress: true; + activeFocusOnTab: true; + + validator: IntValidator { bottom: 0; } + + onAccepted: { + optionalMessage.focus = true; + } + } + + FiraSansSemiBold { + visible: amountTextFieldError.text === ""; + text: "Balance: "; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.left: amountTextField.left; + anchors.right: sendMoneyBalanceText_HFC.left; + width: paintedWidth; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + HiFiGlyphs { + id: sendMoneyBalanceText_HFC; + visible: amountTextFieldError.text === ""; + text: hifi.glyphs.hfc; + // Size + size: 16; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.right: sendMoneyBalanceText.left; + width: paintedWidth; + height: 40; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + FiraSansSemiBold { + id: sendMoneyBalanceText; + visible: amountTextFieldError.text === ""; + text: balanceText.text; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.right: amountTextField.right; + width: paintedWidth; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + + RalewaySemiBold { + id: amountTextFieldError; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.left: amountTextField.left; + anchors.right: amountTextField.right; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + } + + Item { + id: messageContainer; + anchors.top: amountContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 140; + + FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; } + TextArea { + id: optionalMessage; + property int maximumLength: 72; + property string previousText: text; + placeholderText: "Optional Message (" + maximumLength + " character limit)"; + font.family: firaSansSemiBold.name; + font.pixelSize: 20; + // Anchors + anchors.fill: parent; + // Style + background: Rectangle { + anchors.fill: parent; + color: optionalMessage.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow; + border.width: optionalMessage.activeFocus ? 1 : 0; + border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; + } + color: hifi.colors.white; + textFormat: TextEdit.PlainText; + wrapMode: TextEdit.Wrap; + activeFocusOnPress: true; + activeFocusOnTab: true; + // Workaround for no max length on TextAreas + onTextChanged: { + if (text.length > maximumLength) { + var cursor = cursorPosition; + text = previousText; + if (cursor > text.length) { + cursorPosition = text.length; + } else { + cursorPosition = cursor-1; + } + } + previousText = text; + } + } + FiraSansSemiBold { + id: optionalMessageCharacterCount; + text: optionalMessage.text.length + "/" + optionalMessage.maximumLength; + // Anchors + anchors.top: optionalMessage.bottom; + anchors.topMargin: 4; + anchors.right: optionalMessage.right; + height: paintedHeight; + // Text size + size: 16; + // Style + color: optionalMessage.text.length === optionalMessage.maximumLength ? "#ea89a5" : hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + } + + HifiControlsUit.CheckBox { + id: sendPubliclyCheckbox; + visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN + text: "Send Publicly" + // Anchors + anchors.top: messageContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 16; + boxSize: 24; + } + + Item { + id: bottomBarContainer; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 20; + height: 60; + + // "CANCEL" button + HifiControlsUit.Button { + id: cancelButton_sendMoneyStep; + color: hifi.buttons.noneBorderlessWhite; + colorScheme: hifi.colorSchemes.dark; + anchors.left: parent.left; + anchors.leftMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: 150; + text: "CANCEL"; + onClicked: { + resetSendMoneyData(); + root.nextActiveView = "sendMoneyHome"; + } + } + + // "SEND" button + HifiControlsUit.Button { + id: sendButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.rightMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: 150; + text: "SUBMIT"; + onClicked: { + if (parseInt(amountTextField.text) > parseInt(balanceText.text)) { + amountTextField.focus = true; + amountTextField.error = true; + amountTextFieldError.text = "amount exceeds available funds"; + } else if (amountTextField.text === "" || parseInt(amountTextField.text) < 1) { + amountTextField.focus = true; + amountTextField.error = true; + amountTextFieldError.text = "invalid amount"; + } else { + amountTextFieldError.text = ""; + amountTextField.error = false; + root.isCurrentlySendingMoney = true; + amountTextField.focus = false; + optionalMessage.focus = false; + if (sendMoneyStep.referrer === "connections") { + Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text); + } else if (sendMoneyStep.referrer === "nearby") { + Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text); + } + } + } + } + } + } + // Send Money Screen END + + // Sending Money Overlay START + Rectangle { + id: sendingMoneyOverlay; + z: 998; + + visible: root.isCurrentlySendingMoney; + anchors.fill: parent; + color: Qt.rgba(0.0, 0.0, 0.0, 0.8); + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + AnimatedImage { + id: sendingMoneyImage; + source: "./images/loader.gif" + width: 96; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + + RalewaySemiBold { + text: "Sending"; + // Anchors + anchors.top: sendingMoneyImage.bottom; + anchors.topMargin: 4; + anchors.horizontalCenter: parent.horizontalCenter; + width: paintedWidth; + // Text size + size: 26; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + } + } + // Sending Money Overlay END + + // Payment Success BEGIN + Item { + id: paymentSuccess; + + visible: root.currentActiveView === "paymentSuccess"; + anchors.fill: parent; + + Rectangle { + anchors.centerIn: parent; + width: parent.width - 30; + height: parent.height - 30; + color: "#FFFFFF"; + + RalewaySemiBold { + id: paymentSentText; + text: "Payment Sent"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.baseGray; + } + + HiFiGlyphs { + id: closeGlyphButton_paymentSuccess; + text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; + size: 26; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + root.nextActiveView = "sendMoneyHome"; + resetSendMoneyData(); + } + } + } + + Item { + id: sendToContainer_paymentSuccess; + anchors.top: paymentSentText.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: sendToText_paymentSuccess; + text: "Sent To:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + RecipientDisplay { + anchors.top: parent.top; + anchors.left: sendToText_paymentSuccess.right; + anchors.right: parent.right; + height: parent.height; + textColor: hifi.colors.blueAccent; + + displayName: sendMoneyStep.selectedRecipientDisplayName; + userName: sendMoneyStep.selectedRecipientUserName; + profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? + sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; + isDisplayingNearby: sendMoneyStep.referrer === "nearby"; + } + } + + Item { + id: amountContainer_paymentSuccess; + anchors.top: sendToContainer_paymentSuccess.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: amountText_paymentSuccess; + text: "Amount:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + // "HFC" balance label + HiFiGlyphs { + id: amountSentLabel; + text: hifi.glyphs.hfc; + // Size + size: 32; + // Anchors + anchors.left: amountText_paymentSuccess.right; + anchors.verticalCenter: parent.verticalCenter; + height: 50; + // Style + color: hifi.colors.blueAccent; + } + + FiraSansSemiBold { + id: amountSentText; + text: amountTextField.text; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: amountSentLabel.right; + anchors.leftMargin: 20; + anchors.right: parent.right; + height: 50; + // Style + size: 22; + color: hifi.colors.blueAccent; + } + } + + RalewaySemiBold { + id: optionalMessage_paymentSuccess; + text: optionalMessage.text; + // Anchors + anchors.top: amountContainer_paymentSuccess.bottom; + anchors.left: parent.left; + anchors.leftMargin: 110; + anchors.right: parent.right; + anchors.rightMargin: 16; + anchors.bottom: closeButton.top; + anchors.bottomMargin: 40; + // Text size + size: 22; + // Style + color: hifi.colors.blueAccent; + wrapMode: Text.Wrap; + verticalAlignment: Text.AlignTop; + } + + // "Close" button + HifiControlsUit.Button { + id: closeButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 80; + height: 50; + width: 120; + text: "Close"; + onClicked: { + root.nextActiveView = "sendMoneyHome"; + resetSendMoneyData(); + } + } + } + } + // Payment Success END + + // Payment Failure BEGIN + Item { + id: paymentFailure; + + visible: root.currentActiveView === "paymentFailure"; + anchors.fill: parent; + + Rectangle { + anchors.centerIn: parent; + width: parent.width - 30; + height: parent.height - 30; + color: "#FFFFFF"; + + RalewaySemiBold { + id: paymentFailureText; + text: "Payment Failed"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.baseGray; + } + + HiFiGlyphs { + id: closeGlyphButton_paymentFailure; + text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; + size: 26; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + root.nextActiveView = "sendMoneyHome"; + resetSendMoneyData(); + } + } + } + + RalewaySemiBold { + id: paymentFailureDetailText; + text: "The recipient you specified was unable to receive your payment."; + anchors.top: paymentFailureText.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.Wrap; + } + + Item { + id: sendToContainer_paymentFailure; + anchors.top: paymentFailureDetailText.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: sentToText_paymentFailure; + text: "Sent To:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + RecipientDisplay { + anchors.top: parent.top; + anchors.left: sentToText_paymentFailure.right; + anchors.right: parent.right; + height: parent.height; + textColor: hifi.colors.baseGray; + + displayName: sendMoneyStep.selectedRecipientDisplayName; + userName: sendMoneyStep.selectedRecipientUserName; + profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? + sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; + isDisplayingNearby: sendMoneyStep.referrer === "nearby"; + } + } + + Item { + id: amountContainer_paymentFailure; + anchors.top: sendToContainer_paymentFailure.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: amountText_paymentFailure; + text: "Amount:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + // "HFC" balance label + HiFiGlyphs { + id: amountSentLabel_paymentFailure; + text: hifi.glyphs.hfc; + // Size + size: 32; + // Anchors + anchors.left: amountText_paymentFailure.right; + anchors.verticalCenter: parent.verticalCenter; + height: 50; + // Style + color: hifi.colors.baseGray; + } + + FiraSansSemiBold { + id: amountSentText_paymentFailure; + text: amountTextField.text; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: amountSentLabel_paymentFailure.right; + anchors.leftMargin: 20; + anchors.right: parent.right; + height: 50; + // Style + size: 22; + color: hifi.colors.baseGray; + } + } + + RalewaySemiBold { + id: optionalMessage_paymentFailuire; + text: optionalMessage.text; + // Anchors + anchors.top: amountContainer_paymentFailure.bottom; + anchors.left: parent.left; + anchors.leftMargin: 110; + anchors.right: parent.right; + anchors.rightMargin: 16; + anchors.bottom: closeButton_paymentFailure.top; + anchors.bottomMargin: 40; + // Text size + size: 22; + // Style + color: hifi.colors.baseGray; + wrapMode: Text.Wrap; + verticalAlignment: Text.AlignTop; + } + + // "Close" button + HifiControlsUit.Button { + id: closeButton_paymentFailure; + color: hifi.buttons.noneBorderless; + colorScheme: hifi.colorSchemes.dark; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 80; + height: 50; + width: 120; + text: "Cancel"; + onClicked: { + root.nextActiveView = "sendMoneyHome"; + resetSendMoneyData(); + } + } + + // "Retry" button + HifiControlsUit.Button { + id: retryButton_paymentFailure; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.rightMargin: 12; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 80; + height: 50; + width: 120; + text: "Retry"; + onClicked: { + root.isCurrentlySendingMoney = true; + if (sendMoneyStep.referrer === "connections") { + Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text); + } else if (sendMoneyStep.referrer === "nearby") { + Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text); + } + } + } + } + } + // Payment Failure END + + + // + // FUNCTION DEFINITIONS START + // + + function updateConnections(connections) { + connectionsModel.clear(); + connectionsModel.append(connections); + buildFilteredConnectionsModel(); + connectionsLoading.visible = false; + } + + function buildFilteredConnectionsModel() { + filteredConnectionsModel.clear(); + for (var i = 0; i < connectionsModel.count; i++) { + if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { + filteredConnectionsModel.append(connectionsModel.get(i)); + } + } + } + + function resetSendMoneyData() { + amountTextField.focus = false; + optionalMessage.focus = false; + amountTextFieldError.text = ""; + amountTextField.error = false; + chooseRecipientNearby.selectedRecipient = ""; + sendMoneyStep.selectedRecipientNodeID = ""; + sendMoneyStep.selectedRecipientDisplayName = ""; + sendMoneyStep.selectedRecipientUserName = ""; + sendMoneyStep.selectedRecipientProfilePic = ""; + amountTextField.text = ""; + optionalMessage.text = ""; + } + + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + case 'selectRecipient': + if (message.isSelected) { + chooseRecipientNearby.selectedRecipient = message.id[0]; + sendMoneyStep.selectedRecipientDisplayName = message.displayName; + sendMoneyStep.selectedRecipientUserName = message.userName; + } else { + chooseRecipientNearby.selectedRecipient = ""; + sendMoneyStep.selectedRecipientDisplayName = ''; + sendMoneyStep.selectedRecipientUserName = ''; + } + break; + case 'updateSelectedRecipientUsername': + sendMoneyStep.selectedRecipientUserName = message.userName; + break; + default: + console.log('SendMoney: Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg new file mode 100644 index 0000000000..7c5403fda3 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif new file mode 100644 index 0000000000..0536bd1884 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif differ diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg new file mode 100644 index 0000000000..dec87e658d --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg new file mode 100644 index 0000000000..59635a99b1 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg new file mode 100644 index 0000000000..bba07b9567 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/interface/resources/qml/hifi/overlays/ImageOverlay.qml b/interface/resources/qml/hifi/overlays/ImageOverlay.qml index 6899c38e67..cbcf6c7910 100644 --- a/interface/resources/qml/hifi/overlays/ImageOverlay.qml +++ b/interface/resources/qml/hifi/overlays/ImageOverlay.qml @@ -7,7 +7,7 @@ import "." Overlay { id: root - Image { + AnimatedImage { id: image property bool scaleFix: true property real xStart: 0 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 84cd1f1f13..4cf09a56c3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -14,17 +14,20 @@ #include #include -#include #include #include #include #include #include +#include + +#include #include #include #include #include +#include #include #include @@ -49,6 +52,7 @@ #include +#include #include #include #include @@ -157,7 +161,7 @@ #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" #include "scripting/DesktopScriptingInterface.h" -#include "scripting/GlobalServicesScriptingInterface.h" +#include "scripting/AccountServicesScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" @@ -209,9 +213,8 @@ #include "commerce/QmlCommerce.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" +#include -// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU -// FIXME seems to be broken. #if defined(Q_OS_WIN) #include @@ -226,11 +229,17 @@ #undef QT_BOOTSTRAPPED #endif +// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU +// FIXME seems to be broken. extern "C" { _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; } #endif +#if defined(Q_OS_ANDROID) +#include +#endif + enum ApplicationEvent { // Execute a lambda function Lambda = QEvent::User + 1, @@ -240,7 +249,6 @@ enum ApplicationEvent { Idle, }; - class RenderEventHandler : public QObject { using Parent = QObject; Q_OBJECT @@ -304,9 +312,19 @@ static QTimer locationUpdateTimer; static QTimer identityPacketTimer; static QTimer pingTimer; -static const QString DISABLE_WATCHDOG_FLAG("HIFI_DISABLE_WATCHDOG"); +#if defined(Q_OS_ANDROID) +static bool DISABLE_WATCHDOG = true; +#else +static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" }; static bool DISABLE_WATCHDOG = QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); +#endif +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; @@ -503,7 +521,13 @@ public: } if (message->message == WM_DEVICECHANGE) { - Midi::USBchanged(); // re-scan the MIDI bus + const float MIN_DELTA_SECONDS = 2.0f; // de-bounce signal + static float lastTriggerTime = 0.0f; + const float deltaSeconds = secTimestampNow() - lastTriggerTime; + lastTriggerTime = secTimestampNow(); + if (deltaSeconds > MIN_DELTA_SECONDS) { + Midi::USBchanged(); // re-scan the MIDI bus + } } } return false; @@ -530,6 +554,26 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt #ifdef Q_OS_WIN OutputDebugStringA(logMessage.toLocal8Bit().constData()); OutputDebugStringA("\n"); +#elif defined Q_OS_ANDROID + const char * local=logMessage.toStdString().c_str(); + switch (type) { + case QtDebugMsg: + __android_log_write(ANDROID_LOG_DEBUG,"Interface",local); + break; + case QtInfoMsg: + __android_log_write(ANDROID_LOG_INFO,"Interface",local); + break; + case QtWarningMsg: + __android_log_write(ANDROID_LOG_WARN,"Interface",local); + break; + case QtCriticalMsg: + __android_log_write(ANDROID_LOG_ERROR,"Interface",local); + break; + case QtFatalMsg: + default: + __android_log_write(ANDROID_LOG_FATAL,"Interface",local); + abort(); + } #endif qApp->getLogger()->addMessage(qPrintable(logMessage + "\n")); } @@ -573,8 +617,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { } }; reportAndQuit("--protocolVersion", [&](FILE* fp) { - DependencyManager::set(); - auto version = DependencyManager::get()->protocolVersion(); + auto version = protocolVersionsSignatureBase64(); fputs(version.toLatin1().data(), fp); }); reportAndQuit("--version", [&](FILE* fp) { @@ -594,6 +637,24 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir); } + // FIXME fix the OSX installer to install the resources.rcc binary instead of resource files and remove + // this conditional exclusion +#if !defined(Q_OS_OSX) + { +#if defined(Q_OS_ANDROID) + const QString resourcesBinaryFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources.rcc"; +#else + const QString resourcesBinaryFile = QCoreApplication::applicationDirPath() + "/resources.rcc"; +#endif + if (!QFile::exists(resourcesBinaryFile)) { + throw std::runtime_error("Unable to find primary resources"); + } + if (!QResource::registerResource(resourcesBinaryFile)) { + throw std::runtime_error("Unable to load primary resources"); + } + } +#endif + Setting::init(); // Tell the plugin manager about our statically linked plugins @@ -632,15 +693,16 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); DependencyManager::set(ScriptEngine::CLIENT_SCRIPT); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(NodeType::Agent, listenPort); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -729,8 +791,10 @@ OverlayID _keyboardFocusHighlightID{ UNKNOWN_OVERLAY_ID }; // // So instead we create a new offscreen context to share with the QGLWidget, // 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 Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; @@ -776,6 +840,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _sampleSound(nullptr) { + auto steamClient = PluginManager::getInstance()->getSteamClientPlugin(); setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning())); setProperty(hifi::properties::CRASHED, _previousSessionCrashed); @@ -996,8 +1061,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get().data(), &AddressManager::storeCurrentAddress); // Inititalize sample before registering - QFileInfo infSample = QFileInfo(PathUtils::resourcesPath() + "sounds/sample.wav"); - _sampleSound = DependencyManager::get()->getSound(QUrl::fromLocalFile(infSample.absoluteFilePath())); + _sampleSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/sample.wav")); auto scriptEngines = DependencyManager::get().data(); scriptEngines->registerScriptInitializer([this](ScriptEnginePointer engine){ @@ -1082,9 +1146,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. - DependencyManager::get()->initializeShapePipelines(); - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. static const QString TESTER = "HIFI_TESTER"; @@ -1730,7 +1791,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo static int lastCountOfNearbyAvatars = -1; QTimer* checkNearbyAvatarsTimer = new QTimer(this); checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS); // 10 seconds, Qt::CoarseTimer ok - connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() { + connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, []() { auto avatarManager = DependencyManager::get(); int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getWorldPosition(), NEARBY_AVATAR_RADIUS_METERS) - 1; @@ -1769,9 +1830,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo PROFILE_RANGE(render, "Process Default Skybox"); auto textureCache = DependencyManager::get(); - auto skyboxUrl = PathUtils::resourcesPath().toStdString() + "images/Default-Sky-9-cubemap.ktx"; + QFileSelector fileSelector; + fileSelector.setExtraSelectors(FileUtils::getFileSelectors()); + auto skyboxUrl = fileSelector.select(PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.ktx"); - _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl); + _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl.toStdString()); _defaultSkyboxAmbientTexture = _defaultSkyboxTexture; _defaultSkybox->setCubemap(_defaultSkyboxTexture); @@ -1782,8 +1845,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return entityServerNode && !isPhysicsEnabled(); }); - QFileInfo infSnap = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav"); - _snapshotSound = DependencyManager::get()->getSound(QUrl::fromLocalFile(infSnap.absoluteFilePath())); + _snapshotSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/snap.wav")); QVariant testProperty = property(hifi::properties::TEST); qDebug() << testProperty; @@ -2093,6 +2155,11 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); DependencyManager::destroy(); + // The PointerManager must be destroyed before the PickManager because when a Pointer is deleted, + // it accesses the PickManager to delete its associated Pick + DependencyManager::destroy(); + DependencyManager::destroy(); + qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; } @@ -2176,11 +2243,16 @@ void Application::initializeGL() { } _glWidget->makeCurrent(); - _chromiumShareContext = new OffscreenGLCanvas(); - _chromiumShareContext->setObjectName("ChromiumShareContext"); - _chromiumShareContext->create(_glWidget->qglContext()); - _chromiumShareContext->makeCurrent(); - qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + +#if !defined(DISABLE_QML) + if (!nsightActive()) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + _chromiumShareContext->create(_glWidget->qglContext()); + _chromiumShareContext->makeCurrent(); + qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + } +#endif _glWidget->makeCurrent(); gpu::Context::init(); @@ -2197,13 +2269,17 @@ void Application::initializeGL() { // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; - bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); _renderEngine->addJob("UpdateScene"); - _renderEngine->addJob("SecondaryCameraJob", cullFunctor); - _renderEngine->addJob("RenderMainView", cullFunctor, isDeferred); +#ifndef Q_OS_ANDROID + _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); +#endif + _renderEngine->addJob("RenderMainView", cullFunctor, !DISABLE_DEFERRED); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); + // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. + DependencyManager::get()->initializeShapePipelines(); + _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->setObjectName("MainThreadContext"); _offscreenContext->create(_glWidget->qglContext()); @@ -2287,7 +2363,7 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" }, QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" }, QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" }, - QUrl{ "hifi/commerce/wallet/SendMoney.qml" }, + QUrl{ "hifi/commerce/wallet/sendMoney/SendMoney.qml" }, QUrl{ "hifi/commerce/wallet/Wallet.qml" }, QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, QUrl{ "hifi/commerce/wallet/WalletSetup.qml" }, @@ -2308,8 +2384,7 @@ void Application::initializeUi() { 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(QString("hifi/Desktop.qml")); - + 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); @@ -2376,9 +2451,11 @@ void Application::initializeUi() { surfaceContext->setContextProperty("SoundCache", DependencyManager::get().data()); surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); - surfaceContext->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance()); + surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); + surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); - surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); surfaceContext->setContextProperty("FaceTracker", DependencyManager::get().data()); surfaceContext->setContextProperty("AvatarManager", DependencyManager::get().data()); surfaceContext->setContextProperty("UndoStack", &_undoStackScriptingInterface); @@ -3902,12 +3979,18 @@ void Application::idle() { PROFILE_COUNTER_IF_CHANGED(app, "pendingProcessing", int, DependencyManager::get()->getStat("PendingProcessing").toInt()); auto renderConfig = _renderEngine->getConfiguration(); PROFILE_COUNTER_IF_CHANGED(render, "gpuTime", float, (float)_gpuContext->getFrameTimerGPUAverage()); + auto opaqueRangeTimer = renderConfig->getConfig("OpaqueRangeTimer"); + auto linearDepth = renderConfig->getConfig("LinearDepth"); + auto surfaceGeometry = renderConfig->getConfig("SurfaceGeometry"); + auto renderDeferred = renderConfig->getConfig("RenderDeferred"); + auto toneAndPostRangeTimer = renderConfig->getConfig("ToneAndPostRangeTimer"); + PROFILE_COUNTER(render_detail, "gpuTimes", { - { "OpaqueRangeTimer", renderConfig->getConfig("OpaqueRangeTimer")->property("gpuRunTime") }, - { "LinearDepth", renderConfig->getConfig("LinearDepth")->property("gpuRunTime") }, - { "SurfaceGeometry", renderConfig->getConfig("SurfaceGeometry")->property("gpuRunTime") }, - { "RenderDeferred", renderConfig->getConfig("RenderDeferred")->property("gpuRunTime") }, - { "ToneAndPostRangeTimer", renderConfig->getConfig("ToneAndPostRangeTimer")->property("gpuRunTime") } + { "OpaqueRangeTimer", opaqueRangeTimer ? opaqueRangeTimer->property("gpuRunTime") : 0 }, + { "LinearDepth", linearDepth ? linearDepth->property("gpuRunTime") : 0 }, + { "SurfaceGeometry", surfaceGeometry ? surfaceGeometry->property("gpuRunTime") : 0 }, + { "RenderDeferred", renderDeferred ? renderDeferred->property("gpuRunTime") : 0 }, + { "ToneAndPostRangeTimer", toneAndPostRangeTimer ? toneAndPostRangeTimer->property("gpuRunTime") : 0 } }); PROFILE_RANGE(app, __FUNCTION__); @@ -4272,9 +4355,9 @@ void Application::init() { // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); - - DependencyManager::get()->init(); - + if (!DISABLE_DEFERRED) { + DependencyManager::get()->init(); + } DependencyManager::get()->init(); _timerStart.start(); @@ -4349,8 +4432,9 @@ void Application::updateLOD(float deltaTime) const { float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); - float maxRenderTime = glm::max(gpuTime, glm::max(presentTime, engineRunTime)); - DependencyManager::get()->autoAdjustLOD(maxRenderTime, deltaTime); + auto lodManager = DependencyManager::get(); + lodManager->setRenderTimes(presentTime, engineRunTime, gpuTime); + lodManager->autoAdjustLOD(deltaTime); } else { DependencyManager::get()->resetLODAdjust(); } @@ -5752,10 +5836,13 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); +#if !defined(Q_OS_ANDROID) scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); +#endif scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get().data()); @@ -5775,10 +5862,11 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get().data()); scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("Account", GlobalServicesScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface); - scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + scriptEngine->registerGlobalObject("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + scriptEngine->registerGlobalObject("AccountServices", AccountServicesScriptingInterface::getInstance()); qScriptRegisterMetaType(scriptEngine.data(), DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get().data()); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 087a93cffe..73547cdb5e 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -26,43 +26,50 @@ Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DO LODManager::LODManager() { } -float LODManager::getLODDecreaseFPS() { +float LODManager::getLODDecreaseFPS() const { if (qApp->isHMDMode()) { return getHMDLODDecreaseFPS(); } return getDesktopLODDecreaseFPS(); } -float LODManager::getLODIncreaseFPS() { +float LODManager::getLODIncreaseFPS() const { if (qApp->isHMDMode()) { return getHMDLODIncreaseFPS(); } return getDesktopLODIncreaseFPS(); } -// We use a "time-weighted running average" of the renderTime and compare it against min/max thresholds +// We use a "time-weighted running average" of the maxRenderTime and compare it against min/max thresholds // to determine if we should adjust the level of detail (LOD). // // A time-weighted running average has a timescale which determines how fast the average tracks the measured // value in real-time. Given a step-function in the mesured value, and assuming measurements happen // faster than the runningAverage is computed, the error between the value and its runningAverage will be // reduced by 1/e every timescale of real-time that passes. -const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.1f; // sec +const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec // // Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its // thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage to settle // to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few // multiples of the running average timescale: -const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec +const uint64_t LOD_AUTO_ADJUST_PERIOD = 4 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; -void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { - // compute time-weighted running average renderTime +void LODManager::setRenderTimes(float presentTime, float engineRunTime, float gpuTime) { + _presentTime = presentTime; + _engineRunTime = engineRunTime; + _gpuTime = gpuTime; +} + +void LODManager::autoAdjustLOD(float realTimeDelta) { + float maxRenderTime = glm::max(glm::max(_presentTime, _engineRunTime), _gpuTime); + // compute time-weighted running average maxRenderTime // Note: we MUST clamp the blend to 1.0 for stability float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; - _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * renderTime; // msec + _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec if (!_automaticLODAdjust) { // early exit return; @@ -84,6 +91,10 @@ void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { << "targetFPS =" << getLODDecreaseFPS() << "octreeSizeScale =" << _octreeSizeScale; emit LODDecreased(); + // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime + // to provide an FPS just above the decrease threshold. It will drift close to its + // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. + _avgRenderTime = (float)MSECS_PER_SECOND / (getLODDecreaseFPS() + 1.0f); } _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } @@ -105,6 +116,10 @@ void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { << "targetFPS =" << getLODDecreaseFPS() << "octreeSizeScale =" << _octreeSizeScale; emit LODIncreased(); + // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime + // to provide an FPS just below the increase threshold. It will drift close to its + // true value after a few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. + _avgRenderTime = (float)MSECS_PER_SECOND / (getLODIncreaseFPS() - 1.0f); } _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } @@ -119,11 +134,6 @@ void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { if (lodToolsDialog) { lodToolsDialog->reloadSliders(); } - // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime - // to be at middle of target zone. It will drift close to its true value within - // about three few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. - float expectedFPS = 0.5f * (getLODIncreaseFPS() + getLODDecreaseFPS()); - _avgRenderTime = MSECS_PER_SECOND / expectedFPS; } } @@ -131,6 +141,18 @@ void LODManager::resetLODAdjust() { _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } +float LODManager::getLODLevel() const { + // simpleLOD is a linearized and normalized number that represents how much LOD is being applied. + // It ranges from: + // 1.0 = normal (max) level of detail + // 0.0 = min level of detail + // In other words: as LOD "drops" the value of simpleLOD will also "drop", and it cannot go lower than 0.0. + const float LOG_MIN_LOD_RATIO = logf(ADJUST_LOD_MIN_SIZE_SCALE / ADJUST_LOD_MAX_SIZE_SCALE); + float power = logf(_octreeSizeScale / ADJUST_LOD_MAX_SIZE_SCALE); + float simpleLOD = (LOG_MIN_LOD_RATIO - power) / LOG_MIN_LOD_RATIO; + return simpleLOD; +} + const float MIN_DECREASE_FPS = 0.5f; void LODManager::setDesktopLODDecreaseFPS(float fps) { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index cf38342db0..ca6be9380b 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -37,7 +37,7 @@ class AABox; class LODManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - + public: Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; } Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } @@ -49,34 +49,56 @@ public: Q_INVOKABLE void setHMDLODDecreaseFPS(float value); Q_INVOKABLE float getHMDLODDecreaseFPS() const; Q_INVOKABLE float getHMDLODIncreaseFPS() const; - + // User Tweakable LOD Items Q_INVOKABLE QString getLODFeedbackText(); Q_INVOKABLE void setOctreeSizeScale(float sizeScale); Q_INVOKABLE float getOctreeSizeScale() const { return _octreeSizeScale; } - + Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust); Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } - - Q_INVOKABLE float getLODDecreaseFPS(); - Q_INVOKABLE float getLODIncreaseFPS(); - + + Q_INVOKABLE float getLODDecreaseFPS() const; + Q_INVOKABLE float getLODIncreaseFPS() const; + + Q_PROPERTY(float presentTime READ getPresentTime) + Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float gpuTime READ getGPUTime) + Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) + Q_PROPERTY(float fps READ getMaxTheoreticalFPS) + Q_PROPERTY(float lodLevel READ getLODLevel) + + Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) + Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) + + float getPresentTime() const { return _presentTime; } + float getEngineRunTime() const { return _engineRunTime; } + float getGPUTime() const { return _gpuTime; } + static bool shouldRender(const RenderArgs* args, const AABox& bounds); - void autoAdjustLOD(float renderTime, float realTimeDelta); - + void setRenderTimes(float presentTime, float engineRunTime, float gpuTime); + void autoAdjustLOD(float realTimeDelta); + void loadSettings(); void saveSettings(); void resetLODAdjust(); - + + float getAverageRenderTime() const { return _avgRenderTime; }; + float getMaxTheoreticalFPS() const { return (float)MSECS_PER_SECOND / _avgRenderTime; }; + float getLODLevel() const; + signals: void LODIncreased(); void LODDecreased(); - + private: LODManager(); - + bool _automaticLODAdjust = true; - float _avgRenderTime { 0.0f }; + float _presentTime { 0.0f }; // msec + float _engineRunTime { 0.0f }; // msec + float _gpuTime { 0.0f }; // msec + float _avgRenderTime { 0.0f }; // msec float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3072ecf240..2a33ee6f7b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -574,8 +574,6 @@ Menu::Menu() { avatar.get(), SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true, - avatar.get(), SLOT(setUseAnimPreAndPostRotations(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true, avatar.get(), SLOT(setEnableInverseKinematics(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 854f8d8c2b..11a27ff7f5 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -194,7 +194,6 @@ namespace MenuOption { const QString TurnWithHead = "Turn using Head"; const QString UseAudioForMouth = "Use Audio for Mouth"; const QString UseCamera = "Use Camera"; - const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations"; const QString VelocityFilter = "Velocity Filter"; const QString VisibleToEveryone = "Everyone"; const QString VisibleToFriends = "Friends"; diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 4cfa4d6156..5db34c9441 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -203,10 +203,14 @@ public: } }; -void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) { +void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) { const auto cachedArg = task.addJob("SecondaryCamera"); const auto items = task.addJob("FetchCullSort", cullFunctor); assert(items.canCast()); - task.addJob("RenderDeferredTask", items); + if (!isDeferred) { + task.addJob("Forward", items); + } else { + task.addJob("RenderDeferredTask", items); + } task.addJob("EndSecondaryCamera", cachedArg); } \ No newline at end of file diff --git a/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h index 38d75c391e..026b72d865 100644 --- a/interface/src/SecondaryCamera.h +++ b/interface/src/SecondaryCamera.h @@ -71,7 +71,7 @@ public: using JobModel = render::Task::Model; SecondaryCameraRenderTask() {} void configure(const Config& config) {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true); }; #endif diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e863a58e14..2e633986a0 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -537,6 +537,7 @@ void MyAvatar::simulate(float deltaTime) { // we've achived our final adjusted position and rotation for the avatar // and all of its joints, now update our attachements. Avatar::simulateAttachments(deltaTime); + relayJointDataToChildren(); if (!_skeletonModel->hasSkeleton()) { // All the simulation that can be done has been done @@ -1061,11 +1062,6 @@ void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene()); } -void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) { - AnimClip::usePreAndPostPoseFromAnim = isEnabled; - reset(true); -} - void MyAvatar::setEnableInverseKinematics(bool isEnabled) { _skeletonModel->getRig().setEnableInverseKinematics(isEnabled); } @@ -1801,7 +1797,7 @@ void MyAvatar::initAnimGraph() { } else if (!_fstAnimGraphOverrideUrl.isEmpty()) { graphUrl = _fstAnimGraphOverrideUrl; } else { - graphUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json"); + graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); } _skeletonModel->getRig().initAnimGraph(graphUrl); @@ -1929,7 +1925,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _prevShouldDrawHead = shouldDrawHead; } -const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f; +const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f; bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const { return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getModelScale()); @@ -2023,8 +2019,7 @@ void MyAvatar::updateOrientation(float deltaTime) { _smoothOrientationTimer = 0.0f; } - getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); - + Head* head = getHead(); auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); if (headPose.isValid()) { glm::quat localOrientation = headPose.rotation * Quaternions::Y_180; @@ -2036,6 +2031,10 @@ void MyAvatar::updateOrientation(float deltaTime) { head->setBaseYaw(YAW(euler)); head->setBasePitch(PITCH(euler)); head->setBaseRoll(ROLL(euler)); + } else { + head->setBaseYaw(0.0f); + head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); + head->setBaseRoll(0.0f); } } @@ -2100,7 +2099,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { _actionMotorVelocity = motorSpeed * direction; } else { // we're interacting with a floor --> simple horizontal speed and exponential decay - _actionMotorVelocity = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_WALKING_SPEED * direction; + _actionMotorVelocity = getSensorToWorldScale() * _walkSpeed.get() * direction; } float boomChange = getDriveKey(ZOOM); @@ -2692,6 +2691,14 @@ float MyAvatar::getUserEyeHeight() const { return userHeight - userHeight * ratio; } +float MyAvatar::getWalkSpeed() const { + return _walkSpeed.get(); +} + +void MyAvatar::setWalkSpeed(float value) { + _walkSpeed.set(value); +} + glm::vec3 MyAvatar::getPositionForAudio() { switch (_audioListenerMode) { case AudioListenerMode::FROM_HEAD: diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ab74460d4e..58b49b61ff 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -163,6 +163,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT) + Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed); + const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -557,6 +559,9 @@ public: const QUuid& getSelfID() const { return AVATAR_SELF_ID; } + void setWalkSpeed(float value); + float getWalkSpeed() const; + public slots: void increaseSize(); void decreaseSize(); @@ -594,7 +599,6 @@ public slots: bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); - void setUseAnimPreAndPostRotations(bool isEnabled); void setEnableInverseKinematics(bool isEnabled); QUrl getAnimGraphOverrideUrl() const; // thread-safe @@ -841,6 +845,9 @@ private: // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; + + // max unscaled forward movement speed + ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 15830636f0..10ddd4c110 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -17,6 +17,7 @@ #include "Ledger.h" #include "CommerceLogging.h" #include +#include // inventory answers {status: 'success', data: {assets: [{id: "guid", title: "name", preview: "url"}....]}} // balance answers {status: 'success', data: {balance: integer}} @@ -46,6 +47,8 @@ Handler(buy) Handler(receiveAt) Handler(balance) Handler(inventory) +Handler(transferHfcToNode) +Handler(transferHfcToUsername) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -116,23 +119,69 @@ void Ledger::inventory(const QStringList& keys) { keysQuery("inventory", "inventorySuccess", "inventoryFailure"); } -QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) { - int money = moneyValue.toInt(); - int certs = certsValue.toInt(); - if (money <= 0 && certs <= 0) { - return QString(); +QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) { + int sent = sentValue.toInt(); + int received = receivedValue.toInt(); + if (sent <= 0 && received <= 0) { + return QString("0 HFC"); } - QString result(QString(" %2").arg(color, label)); - if (money > 0) { - result += QString(" %1 HFC").arg(money); - } - if (certs > 0) { - if (money > 0) { - result += QString(","); + QString result; + if (sent > 0) { + result += QString("-%1 HFC").arg(sent); + if (received > 0) { + result += QString("
"); } - result += QString((certs == 1) ? " %1 certificate" : " %1 certificates").arg(certs); } - return result + QString("
"); + if (received > 0) { + result += QString("%1 HFC").arg(received); + } + return result; +} +static const QString USER_PAGE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/users/"; +static const QString PLACE_PAGE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/places/"; +static const QStringList KNOWN_USERS(QStringList() << "highfidelity" << "marketplace"); +QString userLink(const QString& username, const QString& placename) { + if (username.isEmpty()) { + if (placename.isEmpty()) { + return QString("someone"); + } else { + return QString("someone nearby").arg(PLACE_PAGE_BASE_URL, placename); + } + } + if (KNOWN_USERS.contains(username)) { + return username; + } + return QString("%2").arg(USER_PAGE_BASE_URL, username); +} + +QString transactionString(const QJsonObject& valueObject) { + int sentCerts = valueObject["sent_certs"].toInt(); + int receivedCerts = valueObject["received_certs"].toInt(); + int sent = valueObject["sent_money"].toInt(); + int dateInteger = valueObject["created_at"].toInt(); + QString message = valueObject["message"].toString(); + QDateTime createdAt(QDateTime::fromSecsSinceEpoch(dateInteger, Qt::UTC)); + QString result; + + if (sentCerts <= 0 && receivedCerts <= 0 && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) { + // this is an hfc transfer. + if (sent > 0) { + QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); + result += QString("Money sent to %1").arg(recipient); + } else { + QString sender = userLink(valueObject["sender_name"].toString(), valueObject["place_name"].toString()); + result += QString("Money from %1").arg(sender); + } + if (!message.isEmpty()) { + result += QString("
with memo: \"%1\"").arg(message); + } + } else { + result += valueObject["message"].toString(); + } + + // no matter what we append a smaller date to the bottom of this... + result += QString("
%1").arg(createdAt.toLocalTime().toString(Qt::DefaultLocaleShortDate)); + return result; } static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; @@ -155,16 +204,13 @@ void Ledger::historySuccess(QNetworkReply& reply) { // TODO: do this with 0 copies if possible for (auto it = historyArray.begin(); it != historyArray.end(); it++) { + // We have 2 text fields to synthesize, the one on the left is a listing + // of the HFC in/out of your wallet. The one on the right contains an explaination + // of the transaction. That could be just the memo (if it is a regular purchase), or + // more text (plus the optional memo) if an hfc transfer auto valueObject = (*it).toObject(); - QString sent = amountString("sent", "EA4C5F", valueObject["sent_money"], valueObject["sent_certs"]); - QString received = amountString("received", "1FC6A6", valueObject["received_money"], valueObject["received_certs"]); - - // turns out on my machine, toLocalTime convert to some weird timezone, yet the - // systemTimeZone is correct. To avoid a strange bug with other's systems too, lets - // be explicit - QDateTime createdAt = QDateTime::fromSecsSinceEpoch(valueObject["created_at"].toInt(), Qt::UTC); - QDateTime localCreatedAt = createdAt.toTimeZone(QTimeZone::systemTimeZone()); - valueObject["text"] = QString("%1%2%3").arg(valueObject["message"].toString(), sent, received); + valueObject["hfc_text"] = hfcString(valueObject["sent_money"], valueObject["received_money"]); + valueObject["transaction_text"] = transactionString(valueObject); newHistoryArray.push_back(valueObject); } // now copy the rest of the json -- this is inefficient @@ -198,11 +244,17 @@ void Ledger::accountSuccess(QNetworkReply& reply) { auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8()); auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8()); auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8()); + QString remotePublicKey = data["public_key"].toString(); wallet->setSalt(salt); wallet->setIv(iv); wallet->setCKey(ckey); + QStringList localPublicKeys = wallet->listPublicKeys(); + if (remotePublicKey.isEmpty() && !localPublicKeys.isEmpty()) { + receiveAt(localPublicKeys.first(), ""); + } + // none of the hfc account info should be emitted emit accountResult(QJsonObject{ {"status", "success"} }); } @@ -261,3 +313,26 @@ void Ledger::certificateInfo(const QString& certificateId) { request["certificate_id"] = certificateId; send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request); } + +void Ledger::transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage) { + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["node_id"] = nodeID; + transaction["quantity"] = amount; + transaction["message"] = optionalMessage; + transaction["place_name"] = DependencyManager::get()->getPlaceName(); + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_node", "transferHfcToNodeSuccess", "transferHfcToNodeFailure"); +} + +void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage) { + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["username"] = username; + transaction["quantity"] = amount; + transaction["message"] = optionalMessage; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure"); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 318006b645..1b56c893ab 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -33,6 +33,8 @@ public: void account(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); + void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage); + void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage); enum CertificateStatus { CERTIFICATE_STATUS_UNKNOWN = 0, @@ -51,6 +53,8 @@ signals: void accountResult(QJsonObject result); void locationUpdateResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void transferHfcToNodeResult(QJsonObject result); + void transferHfcToUsernameResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -71,6 +75,10 @@ public slots: void updateLocationFailure(QNetworkReply& reply); void certificateInfoSuccess(QNetworkReply& reply); void certificateInfoFailure(QNetworkReply& reply); + void transferHfcToNodeSuccess(QNetworkReply& reply); + void transferHfcToNodeFailure(QNetworkReply& reply); + void transferHfcToUsernameSuccess(QNetworkReply& reply); + void transferHfcToUsernameFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 58548eb3ef..c9caa393ce 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -29,6 +29,8 @@ QmlCommerce::QmlCommerce() { connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); + connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult); + connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { @@ -137,3 +139,27 @@ void QmlCommerce::certificateInfo(const QString& certificateId) { auto ledger = DependencyManager::get(); ledger->certificateInfo(certificateId); } + +void QmlCommerce::transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + return emit buyResult(result); + } + QString key = keys[0]; + ledger->transferHfcToNode(key, nodeID, amount, optionalMessage); +} + +void QmlCommerce::transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + return emit buyResult(result); + } + QString key = keys[0]; + ledger->transferHfcToUsername(key, username, amount, optionalMessage); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index c04ede86c4..b261ee6de6 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -45,6 +45,9 @@ signals: void updateCertificateStatus(const QString& certID, uint certStatus); + void transferHfcToNodeResult(QJsonObject result); + void transferHfcToUsernameResult(QJsonObject result); + protected: Q_INVOKABLE void getWalletStatus(); @@ -65,6 +68,9 @@ protected: Q_INVOKABLE void account(); Q_INVOKABLE void certificateInfo(const QString& certificateId); + + Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage); + Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 5c07bebc23..80ec78f118 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -53,6 +52,14 @@ int main(int argc, const char* argv[]) { QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); #endif +#if defined(USE_GLES) && defined(Q_OS_WIN) + // When using GLES on Windows, we can't create normal GL context in Qt, so + // we force Qt to use angle. This will cause the QML to be unable to be used + // in the output window, so QML should be disabled. + qputenv("QT_ANGLE_PLATFORM", "d3d11"); + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); +#endif + disableQtBearerPoll(); // Fixes wifi ping spikes QElapsedTimer startupTime; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index fad6fe47ae..8c0fb59106 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -44,7 +44,7 @@ OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) { QVariantMap overlayProperties; // TODO: make these configurable per pointer overlayProperties["name"] = "stylus"; - overlayProperties["url"] = PathUtils::resourcesPath() + "/meshes/tablet-stylus-fat.fbx"; + overlayProperties["url"] = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx"; overlayProperties["loadPriority"] = 10.0f; overlayProperties["solid"] = true; overlayProperties["visible"] = false; diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp deleted file mode 100644 index fd54198b3c..0000000000 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// AccountScriptingInterface.cpp -// interface/src/scripting -// -// Created by Stojce Slavkovski on 6/07/14. -// Copyright 2014 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 "AccountManager.h" - -#include "AccountScriptingInterface.h" -#include "GlobalServicesScriptingInterface.h" - -AccountScriptingInterface* AccountScriptingInterface::getInstance() { - static AccountScriptingInterface sharedInstance; - return &sharedInstance; -} - -bool AccountScriptingInterface::isLoggedIn() { - return GlobalServicesScriptingInterface::getInstance()->isLoggedIn(); -} - -void AccountScriptingInterface::logOut() { - GlobalServicesScriptingInterface::getInstance()->logOut(); -} - -bool AccountScriptingInterface::loggedIn() const { - return GlobalServicesScriptingInterface::getInstance()->loggedIn(); -} - -QString AccountScriptingInterface::getUsername() { - return GlobalServicesScriptingInterface::getInstance()->getUsername(); -} diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h deleted file mode 100644 index 10d33ffa36..0000000000 --- a/interface/src/scripting/AccountScriptingInterface.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// AccountScriptingInterface.h -// interface/src/scripting -// -// Created by Stojce Slavkovski on 6/07/14. -// Copyright 2014 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_AccountScriptingInterface_h -#define hifi_AccountScriptingInterface_h - -#include - -class AccountScriptingInterface : public QObject { - Q_OBJECT - - /**jsdoc - * @namespace Account - * @property username {String} username if user is logged in, otherwise it returns "Unknown user" - */ - Q_PROPERTY(QString username READ getUsername) - Q_PROPERTY(bool loggedIn READ loggedIn) - -signals: - - /**jsdoc - * Triggered when username has changed. - * @function Account.usernameChanged - * @return {Signal} - */ - void usernameChanged(); - void loggedInChanged(bool loggedIn); - -public slots: - static AccountScriptingInterface* getInstance(); - - /**jsdoc - * Returns the username for the currently logged in High Fidelity metaverse account. - * @function Account.getUsername - * @return {string} username if user is logged in, otherwise it returns "Unknown user" - */ - QString getUsername(); - - /**jsdoc - * Determine if the user is logged into the High Fidleity metaverse. - * @function Account.isLoggedIn - * @return {bool} true when user is logged into the High Fidelity metaverse. - */ - bool isLoggedIn(); - void logOut(); - -public: - AccountScriptingInterface(QObject* parent = nullptr) {} - bool loggedIn() const; -}; - -#endif // hifi_AccountScriptingInterface_h diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp similarity index 71% rename from interface/src/scripting/GlobalServicesScriptingInterface.cpp rename to interface/src/scripting/AccountServicesScriptingInterface.cpp index 305bfa3e79..fc293098bb 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -1,5 +1,5 @@ // -// GlobalServicesScriptingInterface.cpp +// AccountServicesScriptingInterface.cpp // interface/src/scripting // // Created by Thijs Wenker on 9/10/14. @@ -14,41 +14,41 @@ #include "DiscoverabilityManager.h" #include "ResourceCache.h" -#include "GlobalServicesScriptingInterface.h" +#include "AccountServicesScriptingInterface.h" -GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { +AccountServicesScriptingInterface::AccountServicesScriptingInterface() { auto accountManager = DependencyManager::get(); - connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::onUsernameChanged); - connect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); - connect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); + connect(accountManager.data(), &AccountManager::usernameChanged, this, &AccountServicesScriptingInterface::onUsernameChanged); + connect(accountManager.data(), &AccountManager::logoutComplete, this, &AccountServicesScriptingInterface::loggedOut); + connect(accountManager.data(), &AccountManager::loginComplete, this, &AccountServicesScriptingInterface::connected); _downloading = false; QTimer* checkDownloadTimer = new QTimer(this); - connect(checkDownloadTimer, &QTimer::timeout, this, &GlobalServicesScriptingInterface::checkDownloadInfo); + connect(checkDownloadTimer, &QTimer::timeout, this, &AccountServicesScriptingInterface::checkDownloadInfo); const int CHECK_DOWNLOAD_INTERVAL = MSECS_PER_SECOND / 2; checkDownloadTimer->start(CHECK_DOWNLOAD_INTERVAL); auto discoverabilityManager = DependencyManager::get(); connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, - this, &GlobalServicesScriptingInterface::discoverabilityModeChanged); + this, &AccountServicesScriptingInterface::discoverabilityModeChanged); _loggedIn = isLoggedIn(); emit loggedInChanged(_loggedIn); } -GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() { +AccountServicesScriptingInterface::~AccountServicesScriptingInterface() { auto accountManager = DependencyManager::get(); - disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::onUsernameChanged); - disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); - disconnect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); + disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &AccountServicesScriptingInterface::onUsernameChanged); + disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &AccountServicesScriptingInterface::loggedOut); + disconnect(accountManager.data(), &AccountManager::loginComplete, this, &AccountServicesScriptingInterface::connected); } -GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() { - static GlobalServicesScriptingInterface sharedInstance; +AccountServicesScriptingInterface* AccountServicesScriptingInterface::getInstance() { + static AccountServicesScriptingInterface sharedInstance; return &sharedInstance; } -const QString GlobalServicesScriptingInterface::getUsername() const { +const QString AccountServicesScriptingInterface::getUsername() const { auto accountManager = DependencyManager::get(); if (accountManager->isLoggedIn()) { return accountManager->getAccountInfo().getUsername(); @@ -57,31 +57,31 @@ const QString GlobalServicesScriptingInterface::getUsername() const { } } -bool GlobalServicesScriptingInterface::isLoggedIn() { +bool AccountServicesScriptingInterface::isLoggedIn() { auto accountManager = DependencyManager::get(); return accountManager->isLoggedIn(); } -bool GlobalServicesScriptingInterface::checkAndSignalForAccessToken() { +bool AccountServicesScriptingInterface::checkAndSignalForAccessToken() { auto accountManager = DependencyManager::get(); return accountManager->checkAndSignalForAccessToken(); } -void GlobalServicesScriptingInterface::logOut() { +void AccountServicesScriptingInterface::logOut() { auto accountManager = DependencyManager::get(); return accountManager->logout(); } -void GlobalServicesScriptingInterface::loggedOut() { - emit GlobalServicesScriptingInterface::disconnected(QString("logout")); +void AccountServicesScriptingInterface::loggedOut() { + emit AccountServicesScriptingInterface::disconnected(QString("logout")); } -QString GlobalServicesScriptingInterface::getFindableBy() const { +QString AccountServicesScriptingInterface::getFindableBy() const { auto discoverabilityManager = DependencyManager::get(); return DiscoverabilityManager::findableByString(discoverabilityManager->getDiscoverabilityMode()); } -void GlobalServicesScriptingInterface::setFindableBy(const QString& discoverabilityMode) { +void AccountServicesScriptingInterface::setFindableBy(const QString& discoverabilityMode) { auto discoverabilityManager = DependencyManager::get(); if (discoverabilityMode.toLower() == "none") { discoverabilityManager->setDiscoverabilityMode(Discoverability::None); @@ -96,11 +96,11 @@ void GlobalServicesScriptingInterface::setFindableBy(const QString& discoverabil } } -void GlobalServicesScriptingInterface::discoverabilityModeChanged(Discoverability::Mode discoverabilityMode) { +void AccountServicesScriptingInterface::discoverabilityModeChanged(Discoverability::Mode discoverabilityMode) { emit findableByChanged(DiscoverabilityManager::findableByString(discoverabilityMode)); } -void GlobalServicesScriptingInterface::onUsernameChanged(const QString& username) { +void AccountServicesScriptingInterface::onUsernameChanged(const QString& username) { _loggedIn = (username != QString()); emit myUsernameChanged(username); emit loggedInChanged(_loggedIn); @@ -135,7 +135,7 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR result.pending = object.property("pending").toVariant().toFloat(); } -DownloadInfoResult GlobalServicesScriptingInterface::getDownloadInfo() { +DownloadInfoResult AccountServicesScriptingInterface::getDownloadInfo() { DownloadInfoResult result; foreach(const auto& resource, ResourceCache::getLoadingRequests()) { result.downloading.append(resource->getProgress() * 100.0f); @@ -144,7 +144,7 @@ DownloadInfoResult GlobalServicesScriptingInterface::getDownloadInfo() { return result; } -void GlobalServicesScriptingInterface::checkDownloadInfo() { +void AccountServicesScriptingInterface::checkDownloadInfo() { DownloadInfoResult downloadInfo = getDownloadInfo(); bool downloading = downloadInfo.downloading.count() > 0 || downloadInfo.pending > 0; @@ -155,7 +155,7 @@ void GlobalServicesScriptingInterface::checkDownloadInfo() { } } -void GlobalServicesScriptingInterface::updateDownloadInfo() { +void AccountServicesScriptingInterface::updateDownloadInfo() { emit downloadInfoChanged(getDownloadInfo()); } diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h similarity index 84% rename from interface/src/scripting/GlobalServicesScriptingInterface.h rename to interface/src/scripting/AccountServicesScriptingInterface.h index 93d35e9ce8..012c37305d 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -1,5 +1,5 @@ // -// GlobalServicesScriptingInterface.h +// AccountServicesScriptingInterface.h // interface/src/scripting // // Created by Thijs Wenker on 9/10/14. @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_GlobalServicesScriptingInterface_h -#define hifi_GlobalServicesScriptingInterface_h +#ifndef hifi_AccountServicesScriptingInterface_h +#define hifi_AccountServicesScriptingInterface_h #include #include @@ -32,7 +32,7 @@ Q_DECLARE_METATYPE(DownloadInfoResult) QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result); void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoResult& result); -class GlobalServicesScriptingInterface : public QObject { +class AccountServicesScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged) @@ -41,7 +41,7 @@ class GlobalServicesScriptingInterface : public QObject { Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL) public: - static GlobalServicesScriptingInterface* getInstance(); + static AccountServicesScriptingInterface* getInstance(); const QString getUsername() const; bool loggedIn() const { return _loggedIn; } @@ -74,11 +74,11 @@ signals: void loggedInChanged(bool loggedIn); private: - GlobalServicesScriptingInterface(); - ~GlobalServicesScriptingInterface(); + AccountServicesScriptingInterface(); + ~AccountServicesScriptingInterface(); bool _downloading; bool _loggedIn{ false }; }; -#endif // hifi_GlobalServicesScriptingInterface_h +#endif // hifi_AccountServicesScriptingInterface_h diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 72b6f401e9..c13b5b2e0d 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -39,8 +39,7 @@ AssetMappingsScriptingInterface::AssetMappingsScriptingInterface() { void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJSValue callback) { auto assetClient = DependencyManager::get(); auto request = assetClient->createSetMappingRequest(path, hash); - - connect(request, &SetMappingRequest::finished, this, [this, callback](SetMappingRequest* request) mutable { + connect(request, &SetMappingRequest::finished, this, [callback](SetMappingRequest* request) mutable { if (callback.isCallable()) { QJSValueList args { request->getErrorString(), request->getPath() }; callback.call(args); @@ -48,15 +47,13 @@ void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJS request->deleteLater(); }); - request->start(); } void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback) { auto assetClient = DependencyManager::get(); auto request = assetClient->createGetMappingRequest(path); - - connect(request, &GetMappingRequest::finished, this, [this, callback](GetMappingRequest* request) mutable { + connect(request, &GetMappingRequest::finished, this, [callback](GetMappingRequest* request) mutable { auto hash = request->getHash(); if (callback.isCallable()) { diff --git a/interface/src/scripting/LimitlessConnection.h b/interface/src/scripting/LimitlessConnection.h index ee049aff8e..9ed39bd653 100644 --- a/interface/src/scripting/LimitlessConnection.h +++ b/interface/src/scripting/LimitlessConnection.h @@ -12,9 +12,11 @@ #ifndef hifi_LimitlessConnection_h #define hifi_LimitlessConnection_h +#include +#include +#include + #include -#include -#include class LimitlessConnection : public QObject { Q_OBJECT diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index e36b84ac96..fc45555bf9 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -277,7 +277,8 @@ void WindowScriptingInterface::browseAsync(const QString& title, const QString& if (!result.isEmpty()) { setPreviousBrowseLocation(QFileInfo(result).absolutePath()); } - emit openFileChanged(result); + emit browseChanged(result); + emit openFileChanged(result); // Deprecated signal; to be removed in due course. }); } @@ -390,6 +391,10 @@ QString WindowScriptingInterface::checkVersion() { return QCoreApplication::applicationVersion(); } +QString WindowScriptingInterface::protocolSignature() { + return protocolVersionsSignatureBase64(); +} + int WindowScriptingInterface::getInnerWidth() { return qApp->getDeviceSize().x; } @@ -411,6 +416,11 @@ int WindowScriptingInterface::getY() { } void WindowScriptingInterface::copyToClipboard(const QString& text) { + if (QThread::currentThread() != qApp->thread()) { + QMetaObject::invokeMethod(this, "copyToClipboard", Q_ARG(QString, text)); + return; + } + qDebug() << "Copying"; QApplication::clipboard()->setText(text); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index bfad5644bf..dc71611c5b 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -197,18 +197,19 @@ public slots: /**jsdoc * Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A - * {@link Window.openFileChanged|openFileChanged} signal is emitted when a file is chosen; no signal is emitted if the user + * {@link Window.browseChanged|browseChanged} signal is emitted when a file is chosen; no signal is emitted if the user * cancels the dialog. + * @deprecated A deprecated {@link Window.openFileChanged|openFileChanged} signal is also emitted when a file is chosen. * @function Window.browseAsync * @param {string} title="" - The title to display at the top of the dialog. * @param {string} directory="" - The initial directory to start browsing at. * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. * @example Ask the user to choose an image file without waiting for the answer. - * function onOpenFileChanged(filename) { + * function onBrowseChanged(filename) { * print("File: " + filename); * } - * Window.openFileChanged.connect(onOpenFileChanged); + * Window.browseChanged.connect(onBrowseChanged); * * Window.browseAsync("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)"); * print("Script continues without waiting"); @@ -305,6 +306,13 @@ public slots: */ QString checkVersion(); + /**jsdoc + * Get the signature for Interface's protocol version. + * @function Window.protocolSignature + * @returns {string} A string uniquely identifying the version of the metaverse protocol that Interface is using. + */ + QString protocolSignature(); + /**jsdoc * Copies text to the operating system's clipboard. * @function Window.copyToClipboard @@ -652,9 +660,18 @@ signals: */ void saveFileChanged(QString filename); + /**jsdoc + * Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog. + * @function Window.browseChanged + * @param {string} filename - The path and name of the file the user chose in the dialog. + * @returns {Signal} + */ + void browseChanged(QString filename); + /**jsdoc * Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog. * @function Window.openFileChanged + * @deprecated This signal is being replaced with {@link Window.browseChanged|browseChanged} and will be removed. * @param {string} filename - The path and name of the file the user chose in the dialog. * @returns {Signal} */ diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 52b53a3298..7cce12c8e0 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "ApplicationOverlay.h" + #include #include @@ -17,12 +19,10 @@ #include #include #include -#include #include "AudioClient.h" #include "audio/AudioScope.h" #include "Application.h" -#include "ApplicationOverlay.h" #include "Util.h" #include "ui/Stats.h" diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 0d30123c61..b36b4f02df 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -13,6 +13,7 @@ #define hifi_ApplicationOverlay_h #include +#include // Handles the drawing of the overlays to the screen diff --git a/interface/src/ui/ResourceImageItem.cpp b/interface/src/ui/ResourceImageItem.cpp index 5b7c1896fe..f14f4ca78e 100644 --- a/interface/src/ui/ResourceImageItem.cpp +++ b/interface/src/ui/ResourceImageItem.cpp @@ -10,10 +10,9 @@ #include "ResourceImageItem.h" +#include + #include -#include -#include -#include #include @@ -89,11 +88,9 @@ QOpenGLFramebufferObject* ResourceImageItemRenderer::createFramebufferObject(con } void ResourceImageItemRenderer::render() { - auto f = QOpenGLContext::currentContext()->extraFunctions(); - if (_fenceSync) { - f->glWaitSync(_fenceSync, 0, GL_TIMEOUT_IGNORED); - f->glDeleteSync(_fenceSync); + glWaitSync(_fenceSync, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(_fenceSync); _fenceSync = 0; } if (_ready) { diff --git a/interface/src/ui/ResourceImageItem.h b/interface/src/ui/ResourceImageItem.h index 985ab5a66e..d154588094 100644 --- a/interface/src/ui/ResourceImageItem.h +++ b/interface/src/ui/ResourceImageItem.h @@ -14,6 +14,8 @@ #include "Application.h" +#include + #include #include #include diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index f991420fe8..c157898e74 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -42,8 +42,9 @@ using namespace std; static Stats* INSTANCE{ nullptr }; +#if !defined (Q_OS_ANDROID) QString getTextureMemoryPressureModeString(); - +#endif Stats* Stats::getInstance() { if (!INSTANCE) { Stats::registerType(); @@ -359,7 +360,9 @@ void Stats::updateStats(bool force) { STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize())); STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize())); STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize())); +#if !defined(Q_OS_ANDROID) STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString()); +#endif STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize())); STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load()); STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load()); diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index a7c586df1f..5e38f28a06 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -386,8 +386,6 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index c3a772e565..f13f782482 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -154,8 +154,6 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 679a4afbd2..621c19944b 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -132,8 +132,6 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index ca7af3a08b..df93245922 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -208,8 +208,6 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index a8425d482f..7200abf74e 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -275,8 +275,6 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ec586771af..4804a2db3d 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -306,8 +306,6 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and @@ -566,9 +564,9 @@ void ModelOverlay::animate() { rotationMat * fbxJoints[index].postTransform); auto& jointData = jointsData[j]; jointData.translation = extractTranslation(finalMat); - jointData.translationSet = true; + jointData.translationIsDefaultPose = false; jointData.rotation = glmExtractRotation(finalMat); - jointData.rotationSet = true; + jointData.rotationIsDefaultPose = false; } } // Set the data in the model diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 5cf9fc7c8b..3c952b8338 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -31,8 +31,7 @@ Overlay::Overlay() : _alphaPulse(0.0f), _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), - _visible(true), - _anchor(NO_ANCHOR) + _visible(true) { } @@ -49,8 +48,7 @@ Overlay::Overlay(const Overlay* overlay) : _alphaPulse(overlay->_alphaPulse), _colorPulse(overlay->_colorPulse), _color(overlay->_color), - _visible(overlay->_visible), - _anchor(overlay->_anchor) + _visible(overlay->_visible) { } @@ -92,13 +90,6 @@ void Overlay::setProperties(const QVariantMap& properties) { bool visible = properties["visible"].toBool(); setVisible(visible); } - - if (properties["anchor"].isValid()) { - QString property = properties["anchor"].toString(); - if (property == "MyAvatar") { - setAnchor(MY_AVATAR); - } - } } // JSDoc for copying to @typedefs of overlay types that inherit Overlay. @@ -119,8 +110,6 @@ void Overlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. */ QVariant Overlay::getProperty(const QString& property) { if (property == "type") { @@ -150,9 +139,6 @@ QVariant Overlay::getProperty(const QString& property) { if (property == "visible") { return _visible; } - if (property == "anchor") { - return _anchor == MY_AVATAR ? "MyAvatar" : ""; - } return QVariant(); } diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 9b07f24600..e9271f3c3f 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -26,11 +26,6 @@ class Overlay : public QObject { Q_OBJECT public: - enum Anchor { - NO_ANCHOR, - MY_AVATAR - }; - typedef std::shared_ptr Pointer; typedef render::Payload Payload; typedef std::shared_ptr PayloadPointer; @@ -63,7 +58,6 @@ public: virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; xColor getColor(); float getAlpha(); - Anchor getAnchor() const { return _anchor; } float getPulseMax() const { return _pulseMax; } float getPulseMin() const { return _pulseMin; } @@ -78,7 +72,6 @@ public: void setDrawHUDLayer(bool drawHUDLayer); void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } - void setAnchor(Anchor anchor) { _anchor = anchor; } void setPulseMax(float value) { _pulseMax = value; } void setPulseMin(float value) { _pulseMin = value; } @@ -118,7 +111,6 @@ protected: xColor _color; bool _visible; // should the overlay be drawn at all - Anchor _anchor; unsigned int _stackOrder { 0 }; diff --git a/interface/src/ui/overlays/OverlayPanel.cpp b/interface/src/ui/overlays/OverlayPanel.cpp deleted file mode 100644 index 06480109ce..0000000000 --- a/interface/src/ui/overlays/OverlayPanel.cpp +++ /dev/null @@ -1,190 +0,0 @@ -// -// OverlayPanel.cpp -// interface/src/ui/overlays -// -// Created by Zander Otavka on 7/2/15. -// Copyright 2014 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 "OverlayPanel.h" - -#if OVERLAY_PANELS - -#include -#include -#include -#include - -#include "avatar/AvatarManager.h" -#include "avatar/MyAvatar.h" -#include "Base3DOverlay.h" - -PropertyBinding::PropertyBinding(QString avatar, QUuid entity) : - avatar(avatar), - entity(entity) -{ -} - -QVariant propertyBindingToVariant(const PropertyBinding& value) { - QVariantMap obj; - - if (value.avatar == "MyAvatar") { - obj["avatar"] = "MyAvatar"; - } else if (!value.entity.isNull()) { - obj["entity"] = value.entity; - } - - return obj; -} - -void propertyBindingFromVariant(const QVariant& objectVar, PropertyBinding& value) { - auto object = objectVar.toMap(); - auto avatar = object["avatar"]; - auto entity = object["entity"]; - - if (avatar.isValid() && !avatar.isNull()) { - value.avatar = avatar.toString(); - } else if (entity.isValid() && !entity.isNull()) { - value.entity = entity.toUuid(); - } -} - - -void OverlayPanel::addChild(OverlayID childId) { - if (!_children.contains(childId)) { - _children.append(childId); - } -} - -void OverlayPanel::removeChild(OverlayID childId) { - if (_children.contains(childId)) { - _children.removeOne(childId); - } -} - -QVariant OverlayPanel::getProperty(const QString &property) { - if (property == "anchorPosition") { - return vec3toVariant(getAnchorPosition()); - } - if (property == "anchorPositionBinding") { - return propertyBindingToVariant(PropertyBinding(_anchorPositionBindMyAvatar ? - "MyAvatar" : "", - _anchorPositionBindEntity)); - } - if (property == "anchorRotation") { - return quatToVariant(getAnchorRotation()); - } - if (property == "anchorRotationBinding") { - return propertyBindingToVariant(PropertyBinding(_anchorRotationBindMyAvatar ? - "MyAvatar" : "", - _anchorRotationBindEntity)); - } - if (property == "anchorScale") { - return vec3toVariant(getAnchorScale()); - } - if (property == "visible") { - return getVisible(); - } - if (property == "children") { - QVariantList array; - for (int i = 0; i < _children.length(); i++) { - array.append(OverlayIDtoScriptValue(nullptr, _children[i]).toVariant()); - } - return array; - } - - auto value = Billboardable::getProperty(property); - if (value.isValid()) { - return value; - } - return PanelAttachable::getProperty(property); -} - -void OverlayPanel::setProperties(const QVariantMap& properties) { - PanelAttachable::setProperties(properties); - Billboardable::setProperties(properties); - - auto anchorPosition = properties["anchorPosition"]; - if (anchorPosition.isValid()) { - setAnchorPosition(vec3FromVariant(anchorPosition)); - } - - auto anchorPositionBinding = properties["anchorPositionBinding"]; - if (anchorPositionBinding.isValid()) { - PropertyBinding binding = {}; - propertyBindingFromVariant(anchorPositionBinding, binding); - _anchorPositionBindMyAvatar = binding.avatar == "MyAvatar"; - _anchorPositionBindEntity = binding.entity; - } - - auto anchorRotation = properties["anchorRotation"]; - if (anchorRotation.isValid()) { - setAnchorRotation(quatFromVariant(anchorRotation)); - } - - auto anchorRotationBinding = properties["anchorRotationBinding"]; - if (anchorRotationBinding.isValid()) { - PropertyBinding binding = {}; - propertyBindingFromVariant(anchorPositionBinding, binding); - _anchorRotationBindMyAvatar = binding.avatar == "MyAvatar"; - _anchorRotationBindEntity = binding.entity; - } - - auto anchorScale = properties["anchorScale"]; - if (anchorScale.isValid()) { - setAnchorScale(vec3FromVariant(anchorScale)); - } - - auto visible = properties["visible"]; - if (visible.isValid()) { - setVisible(visible.toBool()); - } -} - -void OverlayPanel::applyTransformTo(Transform& transform, bool force) { - if (force || usecTimestampNow() > _transformExpiry) { - PanelAttachable::applyTransformTo(transform, true); - if (!getParentPanel()) { - if (_anchorPositionBindMyAvatar) { - transform.setTranslation(DependencyManager::get()->getMyAvatar() - ->getPosition()); - } else if (!_anchorPositionBindEntity.isNull()) { - EntityTreePointer entityTree = DependencyManager::get()->getEntityTree(); - entityTree->withReadLock([&] { - EntityItemPointer foundEntity = entityTree->findEntityByID(_anchorPositionBindEntity); - if (foundEntity) { - transform.setTranslation(foundEntity->getPosition()); - } - }); - } else { - transform.setTranslation(getAnchorPosition()); - } - - if (_anchorRotationBindMyAvatar) { - transform.setRotation(DependencyManager::get()->getMyAvatar() - ->getOrientation()); - } else if (!_anchorRotationBindEntity.isNull()) { - EntityTreePointer entityTree = DependencyManager::get()->getEntityTree(); - entityTree->withReadLock([&] { - EntityItemPointer foundEntity = entityTree->findEntityByID(_anchorRotationBindEntity); - if (foundEntity) { - transform.setRotation(foundEntity->getRotation()); - } - }); - } else { - transform.setRotation(getAnchorRotation()); - } - - transform.setScale(getAnchorScale()); - - transform.postTranslate(getOffsetPosition()); - transform.postRotate(getOffsetRotation()); - transform.postScale(getOffsetScale()); - } - pointTransformAtCamera(transform, getOffsetRotation()); - } -} -#endif \ No newline at end of file diff --git a/interface/src/ui/overlays/OverlayPanel.h b/interface/src/ui/overlays/OverlayPanel.h deleted file mode 100644 index cff2bc224d..0000000000 --- a/interface/src/ui/overlays/OverlayPanel.h +++ /dev/null @@ -1,86 +0,0 @@ -// -// OverlayPanel.h -// interface/src/ui/overlays -// -// Created by Zander Otavka on 7/2/15. -// Copyright 2014 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_OverlayPanel_h -#define hifi_OverlayPanel_h - -#include - -#include -#include -#include - -#include "PanelAttachable.h" -#include "Billboardable.h" -#include "Overlay.h" - -#if OVERLAY_PANELS -class PropertyBinding { -public: - PropertyBinding() {} - PropertyBinding(QString avatar, QUuid entity); - QString avatar; - QUuid entity; -}; - -QVariant propertyBindingToVariant(const PropertyBinding& value); -void propertyBindingFromVariant(const QVariant& object, PropertyBinding& value); - - -class OverlayPanel : public QObject, public PanelAttachable, public Billboardable { - Q_OBJECT - -public: - typedef std::shared_ptr Pointer; - - void init(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; } - - // getters - glm::vec3 getAnchorPosition() const { return _anchorTransform.getTranslation(); } - glm::quat getAnchorRotation() const { return _anchorTransform.getRotation(); } - glm::vec3 getAnchorScale() const { return _anchorTransform.getScale(); } - bool getVisible() const { return _visible; } - - // setters - void setAnchorPosition(const glm::vec3& position) { _anchorTransform.setTranslation(position); } - void setAnchorRotation(const glm::quat& rotation) { _anchorTransform.setRotation(rotation); } - void setAnchorScale(float scale) { _anchorTransform.setScale(scale); } - void setAnchorScale(const glm::vec3& scale) { _anchorTransform.setScale(scale); } - void setVisible(bool visible) { _visible = visible; } - - const QList& getChildren() { return _children; } - void addChild(OverlayID childId); - void removeChild(OverlayID childId); - OverlayID popLastChild() { return _children.takeLast(); } - - void setProperties(const QVariantMap& properties); - QVariant getProperty(const QString& property); - - virtual void applyTransformTo(Transform& transform, bool force = false) override; - -private: - Transform _anchorTransform; - - bool _anchorPositionBindMyAvatar = false; - QUuid _anchorPositionBindEntity; - - bool _anchorRotationBindMyAvatar = false; - QUuid _anchorRotationBindEntity; - - bool _visible = true; - QList _children; - - QScriptEngine* _scriptEngine; -}; - -#endif - -#endif // hifi_OverlayPanel_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 052ef0b6d8..7f897aee4a 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -68,16 +68,10 @@ void Overlays::cleanupAllOverlays() { foreach(Overlay::Pointer overlay, overlaysWorld) { _overlaysToDelete.push_back(overlay); } -#if OVERLAY_PANELS - _panels.clear(); -#endif cleanupOverlaysToDelete(); } void Overlays::init() { -#if OVERLAY_PANELS - _scriptEngine = new QScriptEngine(); -#endif } void Overlays::update(float deltatime) { @@ -300,12 +294,6 @@ OverlayID Overlays::cloneOverlay(OverlayID id) { if (thisOverlay) { OverlayID cloneId = addOverlay(Overlay::Pointer(thisOverlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); -#if OVERLAY_PANELS - auto attachable = std::dynamic_pointer_cast(thisOverlay); - if (attachable && attachable->getParentPanel()) { - attachable->getParentPanel()->addChild(cloneId); - } -#endif return cloneId; } @@ -381,15 +369,6 @@ void Overlays::deleteOverlay(OverlayID id) { } } -#if OVERLAY_PANELS - auto attachable = std::dynamic_pointer_cast(overlayToDelete); - if (attachable && attachable->getParentPanel()) { - attachable->getParentPanel()->removeChild(id); - attachable->setParentPanel(nullptr); - } -#endif - - _overlaysToDelete.push_back(overlayToDelete); emit overlayDeleted(id); } @@ -424,49 +403,6 @@ QObject* Overlays::getOverlayObject(OverlayID id) { return nullptr; } -#if OVERLAY_PANELS -OverlayID Overlays::getParentPanel(OverlayID childId) const { - Overlay::Pointer overlay = getOverlay(childId); - auto attachable = std::dynamic_pointer_cast(overlay); - if (attachable) { - return _panels.key(attachable->getParentPanel()); - } else if (_panels.contains(childId)) { - return _panels.key(getPanel(childId)->getParentPanel()); - } - return UNKNOWN_OVERLAY_ID; -} - -void Overlays::setParentPanel(OverlayID childId, OverlayID panelId) { - auto attachable = std::dynamic_pointer_cast(getOverlay(childId)); - if (attachable) { - if (_panels.contains(panelId)) { - auto panel = getPanel(panelId); - panel->addChild(childId); - attachable->setParentPanel(panel); - } else { - auto panel = attachable->getParentPanel(); - if (panel) { - panel->removeChild(childId); - attachable->setParentPanel(nullptr); - } - } - } else if (_panels.contains(childId)) { - OverlayPanel::Pointer child = getPanel(childId); - if (_panels.contains(panelId)) { - auto panel = getPanel(panelId); - panel->addChild(childId); - child->setParentPanel(panel); - } else { - auto panel = child->getParentPanel(); - if (panel) { - panel->removeChild(childId); - child->setParentPanel(0); - } - } - } -} -#endif - OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) { if (!_enabled) { return UNKNOWN_OVERLAY_ID; @@ -717,62 +653,6 @@ QSizeF Overlays::textSize(OverlayID id, const QString& text) { return QSizeF(0.0f, 0.0f); } -#if OVERLAY_PANELS -OverlayID Overlays::addPanel(OverlayPanel::Pointer panel) { - QWriteLocker lock(&_lock); - - OverlayID thisID = QUuid::createUuid(); - _panels[thisID] = panel; - - return thisID; -} - -OverlayID Overlays::addPanel(const QVariant& properties) { - OverlayPanel::Pointer panel = std::make_shared(); - panel->init(_scriptEngine); - panel->setProperties(properties.toMap()); - return addPanel(panel); -} - -void Overlays::editPanel(OverlayID panelId, const QVariant& properties) { - if (_panels.contains(panelId)) { - _panels[panelId]->setProperties(properties.toMap()); - } -} - -OverlayPropertyResult Overlays::getPanelProperty(OverlayID panelId, const QString& property) { - OverlayPropertyResult result; - if (_panels.contains(panelId)) { - OverlayPanel::Pointer thisPanel = getPanel(panelId); - QReadLocker lock(&_lock); - result.value = thisPanel->getProperty(property); - } - return result; -} - - -void Overlays::deletePanel(OverlayID panelId) { - OverlayPanel::Pointer panelToDelete; - - { - QWriteLocker lock(&_lock); - if (_panels.contains(panelId)) { - panelToDelete = _panels.take(panelId); - } else { - return; - } - } - - while (!panelToDelete->getChildren().isEmpty()) { - OverlayID childId = panelToDelete->popLastChild(); - deleteOverlay(childId); - deletePanel(childId); - } - - emit panelDeleted(panelId); -} -#endif - bool Overlays::isAddedOverlay(OverlayID id) { if (QThread::currentThread() != thread()) { bool result; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 04c0d01fa2..fd57869048 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -27,7 +27,6 @@ #include "Overlay.h" #include "PanelAttachable.h" -#include "OverlayPanel.h" class PickRay; @@ -93,9 +92,6 @@ public: void enable(); Overlay::Pointer getOverlay(OverlayID id) const; -#if OVERLAY_PANELS - OverlayPanel::Pointer getPanel(OverlayID id) const { return _panels[id]; } -#endif /// adds an overlay that's already been created OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } @@ -468,30 +464,6 @@ public slots: */ bool isAddedOverlay(OverlayID id); -#if OVERLAY_PANELS - OverlayID getParentPanel(OverlayID childId) const; - void setParentPanel(OverlayID childId, OverlayID panelId); - - /// adds a panel that has already been created - OverlayID addPanel(OverlayPanel::Pointer panel); - - /// creates and adds a panel based on a set of properties - OverlayID addPanel(const QVariant& properties); - - /// edit the properties of a panel - void editPanel(OverlayID panelId, const QVariant& properties); - - /// get a property of a panel - OverlayPropertyResult getPanelProperty(OverlayID panelId, const QString& property); - - /// deletes a panel and all child overlays - void deletePanel(OverlayID panelId); - - /// return true if there is a panel with that id else false - bool isAddedPanel(OverlayID id) { return _panels.contains(id); } - -#endif - /**jsdoc * Generate a mouse press event on an overlay. * @function Overlays.sendMousePressOnOverlay @@ -612,10 +584,6 @@ signals: */ void overlayDeleted(OverlayID id); -#if OVERLAY_PANELS - void panelDeleted(OverlayID id); -#endif - /**jsdoc * Triggered when a mouse press event occurs on an overlay. Only occurs for 3D overlays (unless you use * {@link Overlays.sendMousePressOnOverlay|sendMousePressOnOverlay} for a 2D overlay). @@ -732,15 +700,9 @@ private: QMap _overlaysHUD; QMap _overlaysWorld; -#if OVERLAY_PANELS - QMap _panels; -#endif QList _overlaysToDelete; unsigned int _stackOrder { 1 }; -#if OVERLAY_PANELS - QScriptEngine* _scriptEngine; -#endif bool _enabled = true; PointerEvent calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray, RayToOverlayIntersectionResult rayPickResult, diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index fceb261503..449ac62998 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -65,23 +65,7 @@ namespace render { } template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) { if (args) { - if (overlay->getAnchor() == Overlay::MY_AVATAR) { - auto batch = args->_batch; - auto avatar = DependencyManager::get()->getMyAvatar(); - glm::quat myAvatarRotation = avatar->getWorldOrientation(); - glm::vec3 myAvatarPosition = avatar->getWorldPosition(); - float angle = glm::degrees(glm::angle(myAvatarRotation)); - glm::vec3 axis = glm::axis(myAvatarRotation); - float myAvatarScale = avatar->getModelScale(); - Transform transform = Transform(); - transform.setTranslation(myAvatarPosition); - transform.setRotation(glm::angleAxis(angle, axis)); - transform.setScale(myAvatarScale); - batch->setModelTransform(transform); - overlay->render(args); - } else { - overlay->render(args); - } + overlay->render(args); } } template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay) { diff --git a/interface/src/ui/overlays/PanelAttachable.cpp b/interface/src/ui/overlays/PanelAttachable.cpp index bcd32b2850..b53474390c 100644 --- a/interface/src/ui/overlays/PanelAttachable.cpp +++ b/interface/src/ui/overlays/PanelAttachable.cpp @@ -13,18 +13,8 @@ #include -#include "OverlayPanel.h" - bool PanelAttachable::getParentVisible() const { -#if OVERLAY_PANELS - if (getParentPanel()) { - return getParentPanel()->getVisible() && getParentPanel()->getParentVisible(); - } else { - return true; - } -#else return true; -#endif } // JSDoc for copying to @typedefs of overlay types that inherit PanelAttachable. @@ -67,15 +57,6 @@ bool PanelAttachable::applyTransformTo(Transform& transform, bool force) { if (force || usecTimestampNow() > _transformExpiry) { const quint64 TRANSFORM_UPDATE_PERIOD = 100000; // frequency is 10 Hz _transformExpiry = usecTimestampNow() + TRANSFORM_UPDATE_PERIOD; -#if OVERLAY_PANELS - if (getParentPanel()) { - getParentPanel()->applyTransformTo(transform, true); - transform.postTranslate(getOffsetPosition()); - transform.postRotate(getOffsetRotation()); - transform.postScale(getOffsetScale()); - return true; - } -#endif } return false; } diff --git a/interface/src/ui/overlays/PanelAttachable.h b/interface/src/ui/overlays/PanelAttachable.h index 1598aa4700..95faf38cf2 100644 --- a/interface/src/ui/overlays/PanelAttachable.h +++ b/interface/src/ui/overlays/PanelAttachable.h @@ -30,8 +30,6 @@ #ifndef hifi_PanelAttachable_h #define hifi_PanelAttachable_h -#define OVERLAY_PANELS 0 - #include #include @@ -44,18 +42,12 @@ class OverlayPanel; class PanelAttachable { public: // getters -#if OVERLAY_PANELS - std::shared_ptr getParentPanel() const { return _parentPanel; } -#endif glm::vec3 getOffsetPosition() const { return _offset.getTranslation(); } glm::quat getOffsetRotation() const { return _offset.getRotation(); } glm::vec3 getOffsetScale() const { return _offset.getScale(); } bool getParentVisible() const; // setters -#if OVERLAY_PANELS - void setParentPanel(std::shared_ptr panel) { _parentPanel = panel; } -#endif void setOffsetPosition(const glm::vec3& position) { _offset.setTranslation(position); } void setOffsetRotation(const glm::quat& rotation) { _offset.setRotation(rotation); } void setOffsetScale(float scale) { _offset.setScale(scale); } @@ -71,9 +63,6 @@ protected: quint64 _transformExpiry = 0; private: -#if OVERLAY_PANELS - std::shared_ptr _parentPanel = nullptr; -#endif Transform _offset; }; diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index c535b7e503..e765f3fc18 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -127,8 +127,6 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 1ae0dc6fd3..97342a80ab 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -128,8 +128,6 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 137d273c7d..3021aa4404 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -47,8 +47,6 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index f036516f1e..bed20be698 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -224,8 +224,6 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 96b68f5ae0..8121b985cb 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -50,7 +50,7 @@ #include "ui/DomainConnectionModel.h" #include "ui/AvatarInputs.h" #include "avatar/AvatarManager.h" -#include "scripting/GlobalServicesScriptingInterface.h" +#include "scripting/AccountServicesScriptingInterface.h" #include #include "ui/Snapshot.h" #include "SoundCache.h" @@ -192,7 +192,10 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance()); + + _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); // 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" @@ -208,7 +211,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("DCModel", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); @@ -482,8 +484,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. - * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and - * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 1118e21c91..2ead4558fe 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -13,8 +13,6 @@ #include "AnimationLogging.h" #include "AnimUtil.h" -bool AnimClip::usePreAndPostPoseFromAnim = true; - AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) : AnimNode(AnimNode::Type::Clip, id), _startFrame(startFrame), @@ -138,14 +136,8 @@ void AnimClip::copyFromNetworkAnim() { if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { AnimPose preRot, postRot; - if (usePreAndPostPoseFromAnim) { - preRot = animSkeleton.getPreRotationPose(animJoint); - postRot = animSkeleton.getPostRotationPose(animJoint); - } else { - // In order to support Blender, which does not have preRotation FBX support, we use the models defaultPose as the reference frame for the animations. - preRot = AnimPose(glm::vec3(1.0f), _skeleton->getRelativeBindPose(skeletonJoint).rot(), glm::vec3()); - postRot = AnimPose::identity; - } + preRot = animSkeleton.getPreRotationPose(animJoint); + postRot = animSkeleton.getPostRotationPose(animJoint); // cancel out scale preRot.scale() = glm::vec3(1.0f); diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index c7e7ebf3ee..717972ca26 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -25,8 +25,6 @@ class AnimClip : public AnimNode { public: friend class AnimTests; - static bool usePreAndPostPoseFromAnim; - AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag); virtual ~AnimClip() override; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index c8388c3b12..e9f9f8818f 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1255,7 +1255,7 @@ void AnimInverseKinematics::initConstraints() { // / / // O--O O--O - loadDefaultPoses(_skeleton->getRelativeBindPoses()); + loadDefaultPoses(_skeleton->getRelativeDefaultPoses()); int numJoints = (int)_defaultRelativePoses.size(); diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 070949ab3b..46b3cf1c28 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -33,7 +33,7 @@ AnimManipulator::~AnimManipulator() { } const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) { - return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeBindPoses()); + return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeDefaultPoses()); } const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 804ffb0583..2bce16c5ca 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -56,14 +56,6 @@ int AnimSkeleton::getChainDepth(int jointIndex) const { } } -const AnimPose& AnimSkeleton::getAbsoluteBindPose(int jointIndex) const { - return _absoluteBindPoses[jointIndex]; -} - -const AnimPose& AnimSkeleton::getRelativeBindPose(int jointIndex) const { - return _relativeBindPoses[jointIndex]; -} - const AnimPose& AnimSkeleton::getRelativeDefaultPose(int jointIndex) const { return _relativeDefaultPoses[jointIndex]; } @@ -164,8 +156,6 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) _joints = joints; _jointsSize = (int)joints.size(); // build a cache of bind poses - _absoluteBindPoses.reserve(_jointsSize); - _relativeBindPoses.reserve(_jointsSize); // build a chache of default poses _absoluteDefaultPoses.reserve(_jointsSize); @@ -192,28 +182,6 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) } else { _absoluteDefaultPoses.push_back(relDefaultPose); } - - // build relative and absolute bind poses - if (_joints[i].bindTransformFoundInCluster) { - // Use the FBXJoint::bindTransform, which is absolute model coordinates - // i.e. not relative to it's parent. - AnimPose absoluteBindPose(_joints[i].bindTransform); - _absoluteBindPoses.push_back(absoluteBindPose); - if (parentIndex >= 0) { - AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse(); - _relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose); - } else { - _relativeBindPoses.push_back(absoluteBindPose); - } - } else { - // use default transform instead - _relativeBindPoses.push_back(relDefaultPose); - if (parentIndex >= 0) { - _absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relDefaultPose); - } else { - _absoluteBindPoses.push_back(relDefaultPose); - } - } } for (int i = 0; i < _jointsSize; i++) { @@ -251,8 +219,6 @@ void AnimSkeleton::dump(bool verbose) const { qCDebug(animation) << " {"; qCDebug(animation) << " index =" << i; qCDebug(animation) << " name =" << getJointName(i); - qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i); - qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i); qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i); qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); if (verbose) { @@ -287,8 +253,6 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const { qCDebug(animation) << " {"; qCDebug(animation) << " index =" << i; qCDebug(animation) << " name =" << getJointName(i); - qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i); - qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i); qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i); qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); qCDebug(animation) << " pose =" << poses[i]; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 99c9a148f7..664358f414 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -30,13 +30,6 @@ public: int getNumJoints() const; int getChainDepth(int jointIndex) const; - // absolute pose, not relative to parent - const AnimPose& getAbsoluteBindPose(int jointIndex) const; - - // relative to parent pose - const AnimPose& getRelativeBindPose(int jointIndex) const; - const AnimPoseVec& getRelativeBindPoses() const { return _relativeBindPoses; } - // the default poses are the orientations of the joints on frame 0. const AnimPose& getRelativeDefaultPose(int jointIndex) const; const AnimPoseVec& getRelativeDefaultPoses() const { return _relativeDefaultPoses; } @@ -72,8 +65,6 @@ protected: std::vector _joints; int _jointsSize { 0 }; - AnimPoseVec _absoluteBindPoses; - AnimPoseVec _relativeBindPoses; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; AnimPoseVec _relativePreRotationPoses; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index daa1c2618f..b1b41775a8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -179,7 +179,7 @@ void Rig::restoreRoleAnimation(const QString& role) { } else { qCWarning(animation) << "Rig::restoreRoleAnimation could not find role " << role; } - + auto statesIter = _roleAnimStates.find(role); if (statesIter != _roleAnimStates.end()) { _roleAnimStates.erase(statesIter); @@ -1050,52 +1050,6 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons } } -void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority, - const QVector& freeLineage, glm::mat4 rootTransform) { - ASSERT(false); -} - -bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector& freeLineage) { - ASSERT(false); - return false; -} - -float Rig::getLimbLength(int jointIndex, const QVector& freeLineage, - const glm::vec3 scale, const QVector& fbxJoints) const { - ASSERT(false); - return 1.0f; -} - -glm::quat Rig::setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority) { - ASSERT(false); - return glm::quat(); -} - -glm::vec3 Rig::getJointDefaultTranslationInConstrainedFrame(int jointIndex) { - ASSERT(false); - return glm::vec3(); -} - -glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, float mix) { - ASSERT(false); - return glm::quat(); -} - -bool Rig::getJointRotationInConstrainedFrame(int jointIndex, glm::quat& quatOut) const { - ASSERT(false); - return false; -} - -void Rig::clearJointStatePriorities() { - ASSERT(false); -} - -glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { - ASSERT(false); - return glm::quat(); -} - - void Rig::updateFromEyeParameters(const EyeParameters& params) { updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade); @@ -1751,16 +1705,16 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { // rotations are in absolute rig frame. glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); data.rotation = _internalPoseSet._absolutePoses[i].rot(); - data.rotationSet = !isEqual(data.rotation, defaultAbsRot); + data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); // translations are in relative frame but scaled so that they are in meters, // instead of geometry units. glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans(); - data.translationSet = !isEqual(data.translation, defaultRelTrans); + data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans); } else { - data.translationSet = false; - data.rotationSet = false; + data.translationIsDefaultPose = true; + data.rotationIsDefaultPose = true; } } } @@ -1785,11 +1739,11 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); for (int i = 0; i < numJoints; i++) { const JointData& data = jointDataVec.at(i); - if (data.rotationSet) { + if (data.rotationIsDefaultPose) { + rotations.push_back(absoluteDefaultPoses[i].rot()); + } else { // JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame rotations.push_back(rigToGeometryRot * data.rotation); - } else { - rotations.push_back(absoluteDefaultPoses[i].rot()); } } @@ -1805,11 +1759,11 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { const JointData& data = jointDataVec.at(i); _internalPoseSet._relativePoses[i].scale() = Vectors::ONE; _internalPoseSet._relativePoses[i].rot() = rotations[i]; - if (data.translationSet) { + if (data.translationIsDefaultPose) { + _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); + } else { // JointData translations are in scaled relative-frame so we scale back to regular relative-frame _internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation; - } else { - _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 6968d7c3af..87277af754 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -172,36 +172,6 @@ public: // Regardless of who started the animations or how many, update the joints. void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform); - // legacy - void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority, - const QVector& freeLineage, glm::mat4 rootTransform); - - // legacy - bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector& freeLineage); - - // legacy - float getLimbLength(int jointIndex, const QVector& freeLineage, - const glm::vec3 scale, const QVector& fbxJoints) const; - - // legacy - glm::quat setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority); - - // legacy - glm::vec3 getJointDefaultTranslationInConstrainedFrame(int jointIndex); - - // legacy - glm::quat setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, - float priority, float mix = 1.0f); - - // legacy - bool getJointRotationInConstrainedFrame(int jointIndex, glm::quat& rotOut) const; - - // legacy - glm::quat getJointDefaultRotationInParentFrame(int jointIndex); - - // legacy - void clearJointStatePriorities(); - void updateFromControllerParameters(const ControllerParameters& params, float dt); void updateFromEyeParameters(const EyeParameters& params); @@ -345,7 +315,7 @@ protected: float firstFrame; float lastFrame; }; - + struct RoleAnimState { RoleAnimState() {} RoleAnimState(const QString& roleId, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index c532e7659f..500a24763d 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -32,6 +32,8 @@ #include #include #include +#include "ModelEntityItem.h" +#include "RenderableModelEntityItem.h" #include "Logging.h" @@ -326,13 +328,15 @@ void Avatar::updateAvatarEntities() { AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); if (!recentlyDettachedAvatarEntities.empty()) { // only lock this thread when absolutely necessary + AvatarEntityMap avatarEntityData; _avatarEntitiesLock.withReadLock([&] { - foreach (auto entityID, recentlyDettachedAvatarEntities) { - if (!_avatarEntityData.contains(entityID)) { - entityTree->deleteEntity(entityID, true, true); - } - } + avatarEntityData = _avatarEntityData; }); + foreach (auto entityID, recentlyDettachedAvatarEntities) { + if (!avatarEntityData.contains(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } + } // remove stale data hashes foreach (auto entityID, recentlyDettachedAvatarEntities) { @@ -347,6 +351,65 @@ void Avatar::updateAvatarEntities() { setAvatarEntityDataChanged(false); } +void Avatar::relayJointDataToChildren() { + forEachChild([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + auto modelEntity = std::dynamic_pointer_cast(child); + if (modelEntity) { + if (modelEntity->getRelayParentJoints()) { + if (!modelEntity->getJointMapCompleted() || _reconstructSoftEntitiesJointMap) { + QStringList modelJointNames = modelEntity->getJointNames(); + int numJoints = modelJointNames.count(); + std::vector map; + map.reserve(numJoints); + for (int jointIndex = 0; jointIndex < numJoints; jointIndex++) { + QString jointName = modelJointNames.at(jointIndex); + int avatarJointIndex = getJointIndex(jointName); + glm::quat jointRotation; + glm::vec3 jointTranslation; + if (avatarJointIndex < 0) { + jointRotation = modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); + jointTranslation = modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + map.push_back(-1); + } else { + int jointIndex = getJointIndex(jointName); + jointRotation = getJointRotation(jointIndex); + jointTranslation = getJointTranslation(jointIndex); + map.push_back(avatarJointIndex); + } + modelEntity->setLocalJointRotation(jointIndex, jointRotation); + modelEntity->setLocalJointTranslation(jointIndex, jointTranslation); + } + modelEntity->setJointMap(map); + } else { + QStringList modelJointNames = modelEntity->getJointNames(); + int numJoints = modelJointNames.count(); + for (int jointIndex = 0; jointIndex < numJoints; jointIndex++) { + int avatarJointIndex = modelEntity->avatarJointIndex(jointIndex); + glm::quat jointRotation; + glm::vec3 jointTranslation; + if (avatarJointIndex >=0) { + jointRotation = getJointRotation(avatarJointIndex); + jointTranslation = getJointTranslation(avatarJointIndex); + } else { + jointRotation = modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); + jointTranslation = modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + } + modelEntity->setLocalJointRotation(jointIndex, jointRotation); + modelEntity->setLocalJointTranslation(jointIndex, jointTranslation); + } + } + Transform avatarTransform = _skeletonModel->getTransform(); + avatarTransform.setScale(_skeletonModel->getScale()); + modelEntity->setOverrideTransform(avatarTransform, _skeletonModel->getOffset()); + modelEntity->simulateRelayedJoints(); + } + } + } + }); + _reconstructSoftEntitiesJointMap = false; +} + void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); @@ -379,6 +442,7 @@ void Avatar::simulate(float deltaTime, bool inView) { } head->setScale(getModelScale()); head->simulate(deltaTime); + relayJointDataToChildren(); } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. _skeletonModel->simulate(deltaTime, false); @@ -727,10 +791,19 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { // virtual void Avatar::simulateAttachments(float deltaTime) { + assert(_attachmentModels.size() == _attachmentModelsTexturesLoaded.size()); PerformanceTimer perfTimer("attachments"); for (int i = 0; i < (int)_attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); auto& model = _attachmentModels.at(i); + bool texturesLoaded = _attachmentModelsTexturesLoaded.at(i); + + // Watch for texture loading + if (!texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + _attachmentModelsTexturesLoaded[i] = true; + model->updateRenderItems(); + } + int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; @@ -1197,6 +1270,7 @@ void Avatar::setModelURLFinished(bool success) { invalidateJointIndicesCache(); _isAnimatingScale = true; + _reconstructSoftEntitiesJointMap = true; if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts @@ -1254,6 +1328,7 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { while ((int)_attachmentModels.size() > attachmentData.size()) { auto attachmentModel = _attachmentModels.back(); _attachmentModels.pop_back(); + _attachmentModelsTexturesLoaded.pop_back(); _attachmentsToRemove.push_back(attachmentModel); } @@ -1261,11 +1336,16 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { if (i == (int)_attachmentModels.size()) { // if number of attachments has been increased, we need to allocate a new model _attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar())); - } - else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { + _attachmentModelsTexturesLoaded.push_back(false); + } else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { // if the attachment has changed type, we need to re-allocate a new one. _attachmentsToRemove.push_back(_attachmentModels[i]); _attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar()); + _attachmentModelsTexturesLoaded[i] = false; + } + // If the model URL has changd, we need to wait for the textures to load + if (_attachmentModels[i]->getURL() != attachmentData[i].modelURL) { + _attachmentModelsTexturesLoaded[i] = false; } _attachmentModels[i]->setURL(attachmentData[i].modelURL); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 39081ba44b..c2b404a925 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -306,6 +306,7 @@ protected: glm::vec3 _skeletonOffset; std::vector> _attachmentModels; + std::vector _attachmentModelsTexturesLoaded; std::vector> _attachmentsToRemove; std::vector> _attachmentsToDelete; @@ -330,6 +331,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; + void relayJointDataToChildren(); void fade(render::Transaction& transaction, render::Transition::Type type); @@ -382,6 +384,7 @@ protected: bool _isAnimatingScale { false }; bool _mustFadeIn { false }; bool _isFading { false }; + bool _reconstructSoftEntitiesJointMap { false }; float _modelScale { 1.0f }; static int _jointConesID; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index a13ef07cd4..1112ccde60 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -237,30 +237,14 @@ bool SkeletonModel::getRightHandPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getRightHandJointIndex(), position); } -bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) { - return restoreJointPosition(getLeftHandJointIndex(), fraction, priority); -} - bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position); } -float SkeletonModel::getLeftArmLength() const { - return getLimbLength(getLeftHandJointIndex()); -} - -bool SkeletonModel::restoreRightHandPosition(float fraction, float priority) { - return restoreJointPosition(getRightHandJointIndex(), fraction, priority); -} - bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position); } -float SkeletonModel::getRightArmLength() const { - return getLimbLength(getRightHandJointIndex()); -} - bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { return isActive() && getJointPositionInWorldFrame(getFBXGeometry().headJointIndex, headPosition); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index f911ad0c5a..d82fce7412 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -57,11 +57,6 @@ public: /// \return true whether or not the position was found bool getRightHandPosition(glm::vec3& position) const; - /// Restores some fraction of the default position of the left hand. - /// \param fraction the fraction of the default position to restore - /// \return whether or not the left hand joint was found - bool restoreLeftHandPosition(float fraction = 1.0f, float priority = 1.0f); - /// Gets the position of the left shoulder. /// \return whether or not the left shoulder joint was found bool getLeftShoulderPosition(glm::vec3& position) const; @@ -69,18 +64,10 @@ public: /// Returns the extended length from the left hand to its last free ancestor. float getLeftArmLength() const; - /// Restores some fraction of the default position of the right hand. - /// \param fraction the fraction of the default position to restore - /// \return whether or not the right hand joint was found - bool restoreRightHandPosition(float fraction = 1.0f, float priority = 1.0f); - /// Gets the position of the right shoulder. /// \return whether or not the right shoulder joint was found bool getRightShoulderPosition(glm::vec3& position) const; - /// Returns the extended length from the right hand to its first free ancestor. - float getRightArmLength() const; - /// Returns the position of the head joint. /// \return whether or not the head was found bool getHeadPosition(glm::vec3& headPosition) const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 94df5bf7f4..c2aadb8723 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "AvatarLogging.h" @@ -77,6 +78,16 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) { return totalSize; } +size_t AvatarDataPacket::maxJointDefaultPoseFlagsSize(size_t numJoints) { + const size_t bitVectorSize = calcBitVectorSize((int)numJoints); + size_t totalSize = sizeof(uint8_t); // numJoints + + // one set of bits for rotation and one for translation + const size_t NUM_BIT_VECTORS_IN_DEFAULT_POSE_FLAGS_SECTION = 2; + totalSize += NUM_BIT_VECTORS_IN_DEFAULT_POSE_FLAGS_SECTION * bitVectorSize; + + return totalSize; +} AvatarData::AvatarData() : SpatiallyNestable(NestableType::Avatar, QUuid()), @@ -103,7 +114,7 @@ AvatarData::~AvatarData() { QUrl AvatarData::_defaultFullAvatarModelUrl = {}; // In C++, if this initialization were in the AvatarInfo, every file would have it's own copy, even for class vars. const QUrl& AvatarData::defaultFullAvatarModelUrl() { if (_defaultFullAvatarModelUrl.isEmpty()) { - _defaultFullAvatarModelUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "/meshes/defaultAvatar_full.fst"); + _defaultFullAvatarModelUrl = PathUtils::resourcesUrl("/meshes/defaultAvatar_full.fst"); } return _defaultFullAvatarModelUrl; } @@ -272,6 +283,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasFaceTrackerInfo = false; bool hasJointData = false; + bool hasJointDefaultPoseFlags = false; if (sendPALMinimum) { hasAudioLoudness = true; @@ -290,12 +302,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = sendAll || !sendMinimum; + hasJointDefaultPoseFlags = hasJointData; } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getNumSummedBlendshapeCoefficients()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0); + (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0) + + (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); @@ -314,7 +328,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) - | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0); + | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0); memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); destinationBuffer += sizeof(packetStateFlags); @@ -541,14 +556,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData[i]; + const JointData& last = lastSentJointData[i]; - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - bool largeEnoughRotation = fabsf(glm::dot(data.rotation, lastSentJointData[i].rotation)) < minRotationDOT; + if (!data.rotationIsDefaultPose) { + if (sendAll || last.rotationIsDefaultPose || last.rotation != data.rotation) { - if (sendAll || lastSentJointData[i].rotation != data.rotation) { - if (sendAll || !cullSmallChanges || largeEnoughRotation) { - if (data.rotationSet) { + bool largeEnoughRotation = true; + if (cullSmallChanges) { + // The dot product for smaller rotations is a smaller number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT; + } + + if (sendAll || !cullSmallChanges || largeEnoughRotation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG rotationSentCount++; @@ -557,8 +577,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { localSentJointDataOut[i].rotation = data.rotation; + localSentJointDataOut[i].rotationIsDefaultPose = false; } - } } } @@ -588,11 +608,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float maxTranslationDimension = 0.0; for (int i = 0; i < _jointData.size(); i++) { const JointData& data = _jointData[i]; - if (sendAll || lastSentJointData[i].translation != data.translation) { - if (sendAll || - !cullSmallChanges || - glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - if (data.translationSet) { + + if (!data.translationIsDefaultPose) { + if (sendAll || lastSentJointData[i].translation != data.translation) { + if (sendAll || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { validity |= (1 << validityBit); #ifdef WANT_DEBUG translationSentCount++; @@ -606,8 +625,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { localSentJointDataOut[i].translation = data.translation; + localSentJointDataOut[i].translationIsDefaultPose = false; } - } } } @@ -655,6 +674,30 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + if (hasJointDefaultPoseFlags) { + auto startSection = destinationBuffer; + QReadLocker readLock(&_jointDataLock); + + // write numJoints + int numJoints = _jointData.size(); + *destinationBuffer++ = (uint8_t)numJoints; + + // write rotationIsDefaultPose bits + destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { + return _jointData[i].rotationIsDefaultPose; + }); + + // write translationIsDefaultPose bits + destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) { + return _jointData[i].translationIsDefaultPose; + }); + + if (outboundDataRateOut) { + size_t numBytes = destinationBuffer - startSection; + outboundDataRateOut->jointDefaultPoseFlagsRate.increment(numBytes); + } + } + int avatarDataSize = destinationBuffer - startPosition; if (avatarDataSize > (int)byteArraySize) { @@ -664,6 +707,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent return avatarDataByteArray.left(avatarDataSize); } + // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation void AvatarData::doneEncoding(bool cullSmallChanges) { // The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData. @@ -674,7 +718,7 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { if (_lastSentJointData[i].rotation != data.rotation) { if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) { - if (data.rotationSet) { + if (!data.rotationIsDefaultPose) { _lastSentJointData[i].rotation = data.rotation; } } @@ -682,7 +726,7 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { if (_lastSentJointData[i].translation != data.translation) { if (!cullSmallChanges || glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) { - if (data.translationSet) { + if (!data.translationIsDefaultPose) { _lastSentJointData[i].translation = data.translation; } } @@ -730,6 +774,7 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { + // lazily allocate memory for HeadData in case we're not an Avatar instance lazyInitHeadData(); @@ -745,18 +790,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { #define HAS_FLAG(B,F) ((B & F) == F) - bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION); - bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX); - bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION); - bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE); - bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION); - bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS); - bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX); - bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS); - bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO); - bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION); - bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO); - bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA); + bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION); + bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX); + bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION); + bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE); + bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION); + bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS); + bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX); + bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS); + bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO); + bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION); + bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO); + bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA); + bool hasJointDefaultPoseFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS); quint64 now = usecTimestampNow(); @@ -1055,7 +1101,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (validRotations[i]) { sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); _hasNewJointData = true; - data.rotationSet = true; + data.rotationIsDefaultPose = false; } } @@ -1090,7 +1136,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (validTranslations[i]) { sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); _hasNewJointData = true; - data.translationSet = true; + data.translationIsDefaultPose = false; } } @@ -1110,6 +1156,32 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _jointDataUpdateRate.increment(); } + if (hasJointDefaultPoseFlags) { + auto startSection = sourceBuffer; + + QWriteLocker writeLock(&_jointDataLock); + + PACKET_READ_CHECK(JointDefaultPoseFlagsNumJoints, sizeof(uint8_t)); + int numJoints = (int)*sourceBuffer++; + + _jointData.resize(numJoints); + + size_t bitVectorSize = calcBitVectorSize(numJoints); + PACKET_READ_CHECK(JointDefaultPoseFlagsRotationFlags, bitVectorSize); + sourceBuffer += readBitVector(sourceBuffer, numJoints, [&](int i, bool value) { + _jointData[i].rotationIsDefaultPose = value; + }); + + PACKET_READ_CHECK(JointDefaultPoseFlagsTranslationFlags, bitVectorSize); + sourceBuffer += readBitVector(sourceBuffer, numJoints, [&](int i, bool value) { + _jointData[i].translationIsDefaultPose = value; + }); + + int numBytesRead = sourceBuffer - startSection; + _jointDefaultPoseFlagsRate.increment(numBytesRead); + _jointDefaultPoseFlagsUpdateRate.increment(); + } + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); @@ -1146,6 +1218,8 @@ float AvatarData::getDataRate(const QString& rateName) const { return _faceTrackerRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointData") { return _jointDataRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "jointDefaultPoseFlagsRate") { + return _jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "globalPositionOutbound") { return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "localPositionOutbound") { @@ -1170,6 +1244,8 @@ float AvatarData::getDataRate(const QString& rateName) const { return _outboundDataRate.faceTrackerRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointDataOutbound") { return _outboundDataRate.jointDataRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "jointDefaultPoseFlagsOutbound") { + return _outboundDataRate.jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT; } return 0.0f; } @@ -1236,9 +1312,9 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v } JointData& data = _jointData[index]; data.rotation = rotation; - data.rotationSet = true; + data.rotationIsDefaultPose = false; data.translation = translation; - data.translationSet = true; + data.translationIsDefaultPose = false; } void AvatarData::clearJointData(int index) { @@ -1294,7 +1370,8 @@ void AvatarData::setJointData(const QString& name, const glm::quat& rotation, co auto& jointData = _jointData[index]; jointData.rotation = rotation; jointData.translation = translation; - jointData.rotationSet = jointData.translationSet = true; + jointData.rotationIsDefaultPose = false; + jointData.translationIsDefaultPose = false; }); } @@ -1304,7 +1381,7 @@ void AvatarData::setJointRotation(const QString& name, const glm::quat& rotation writeLockWithNamedJointIndex(name, [&](int index) { auto& data = _jointData[index]; data.rotation = rotation; - data.rotationSet = true; + data.rotationIsDefaultPose = false; }); } @@ -1314,7 +1391,7 @@ void AvatarData::setJointTranslation(const QString& name, const glm::vec3& trans writeLockWithNamedJointIndex(name, [&](int index) { auto& data = _jointData[index]; data.translation = translation; - data.translationSet = true; + data.translationIsDefaultPose = false; }); } @@ -1328,7 +1405,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) { } JointData& data = _jointData[index]; data.rotation = rotation; - data.rotationSet = true; + data.rotationIsDefaultPose = false; } void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { @@ -1341,7 +1418,7 @@ void AvatarData::setJointTranslation(int index, const glm::vec3& translation) { } JointData& data = _jointData[index]; data.translation = translation; - data.translationSet = true; + data.translationIsDefaultPose = false; } void AvatarData::clearJointData(const QString& name) { @@ -1397,7 +1474,7 @@ void AvatarData::setJointRotations(const QVector& jointRotations) { for (int i = 0; i < size; ++i) { auto& data = _jointData[i]; data.rotation = jointRotations[i]; - data.rotationSet = true; + data.rotationIsDefaultPose = false; } } @@ -1419,7 +1496,7 @@ void AvatarData::setJointTranslations(const QVector& jointTranslation for (int i = 0; i < size; ++i) { auto& data = _jointData[i]; data.translation = jointTranslations[i]; - data.translationSet = true; + data.translationIsDefaultPose = false; } } @@ -1996,9 +2073,9 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { if (json.isArray()) { QJsonArray array = json.toArray(); result.rotation = quatFromJsonValue(array[0]); - result.rotationSet = true; + result.rotationIsDefaultPose = false; result.translation = vec3FromJsonValue(array[1]); - result.translationSet = true; + result.translationIsDefaultPose = false; } return result; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d7dd2837cb..92c505f5c3 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -113,18 +113,19 @@ namespace AvatarDataPacket { // Packet State Flags - we store the details about the existence of other records in this bitset: // AvatarGlobalPosition, Avatar face tracker, eye tracking, and existence of using HasFlags = uint16_t; - const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0; - const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1; - const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2; - const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3; - const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4; - const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5; - const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6; - const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7; - const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8; - const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9; - const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10; - const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11; + const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0; + const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1; + const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2; + const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3; + const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4; + const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5; + const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6; + const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7; + const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8; + const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9; + const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10; + const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11; + const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 12; const size_t AVATAR_HAS_FLAGS_SIZE = 2; using SixByteQuat = uint8_t[6]; @@ -256,6 +257,15 @@ namespace AvatarDataPacket { }; */ size_t maxJointDataSize(size_t numJoints); + + /* + struct JointDefaultPoseFlags { + uint8_t numJoints; + uint8_t rotationIsDefaultPoseBits[ceil(numJoints / 8)]; + uint8_t translationIsDefaultPoseBits[ceil(numJoints / 8)]; + }; + */ + size_t maxJointDefaultPoseFlagsSize(size_t numJoints); } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -321,6 +331,7 @@ public: RateCounter<> parentInfoRate; RateCounter<> faceTrackerRate; RateCounter<> jointDataRate; + RateCounter<> jointDefaultPoseFlagsRate; }; class AvatarPriority { @@ -358,7 +369,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName NOTIFY displayNameChanged) // sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients. // The result is unique among all avatars present at the time. - Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName) + Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged) Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) @@ -685,6 +696,7 @@ public: signals: void displayNameChanged(); + void sessionDisplayNameChanged(); void lookAtSnappingChanged(bool enabled); void sessionUUIDChanged(); @@ -809,6 +821,7 @@ protected: RateCounter<> _parentInfoRate; RateCounter<> _faceTrackerRate; RateCounter<> _jointDataRate; + RateCounter<> _jointDefaultPoseFlagsRate; // Some rate data for incoming data updates RateCounter<> _parseBufferUpdateRate; @@ -824,6 +837,7 @@ protected: RateCounter<> _parentInfoUpdateRate; RateCounter<> _faceTrackerUpdateRate; RateCounter<> _jointDataUpdateRate; + RateCounter<> _jointDefaultPoseFlagsUpdateRate; // Some rate data for outgoing data AvatarDataRate _outboundDataRate; diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 64cd534c8b..1fd001e536 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -15,6 +15,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : _avatarData(avatarData) { QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged); + QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged); QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged); } diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 46dfb5325f..68074b838d 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -44,7 +44,7 @@ class ScriptAvatarData : public QObject { // Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) - Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) + Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName NOTIFY sessionDisplayNameChanged) Q_PROPERTY(bool isReplicated READ getIsReplicated) Q_PROPERTY(bool lookAtSnappingEnabled READ getLookAtSnappingEnabled NOTIFY lookAtSnappingChanged) @@ -131,6 +131,7 @@ public: signals: void displayNameChanged(); + void sessionDisplayNameChanged(); void lookAtSnappingChanged(bool enabled); public slots: diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e646ba27f5..40dcf1b8c7 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -10,16 +10,14 @@ #include #include +#include + #include #include #include -#include #include -#include -#if defined(Q_OS_MAC) -#include -#endif +#include #include #include @@ -27,7 +25,6 @@ #include #include -#include #include #include @@ -130,6 +127,8 @@ public: OpenGLDisplayPlugin* currentPlugin{ nullptr }; Q_ASSERT(_context); _context->makeCurrent(); + CHECK_GL_ERROR(); + _context->doneCurrent(); while (!_shutdown) { if (_pendingMainThreadOperation) { PROFILE_RANGE(render, "MainThreadOp") @@ -171,20 +170,15 @@ public: QThread::setPriority(newPlugin->getPresentPriority()); bool wantVsync = newPlugin->wantVsync(); _context->makeCurrent(); -#if defined(Q_OS_WIN) - wglSwapIntervalEXT(wantVsync ? 1 : 0); - hasVsync = wglGetSwapIntervalEXT() != 0; -#elif defined(Q_OS_MAC) - GLint interval = wantVsync ? 1 : 0; + CHECK_GL_ERROR(); +#if defined(Q_OS_MAC) newPlugin->swapBuffers(); - CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); - newPlugin->swapBuffers(); - CGLGetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); - hasVsync = interval != 0; -#else - // TODO: Fill in for linux - Q_UNUSED(wantVsync); #endif + gl::setSwapInterval(wantVsync ? 1 : 0); +#if defined(Q_OS_MAC) + newPlugin->swapBuffers(); +#endif + hasVsync = gl::getSwapInterval() != 0; newPlugin->setVsyncEnabled(hasVsync); newPlugin->customizeContext(); CHECK_GL_ERROR(); @@ -284,6 +278,12 @@ bool OpenGLDisplayPlugin::activate() { DependencyManager::set(); presentThread = DependencyManager::get(); presentThread->setObjectName("Presentation Thread"); + if (!widget->context()->makeCurrent()) { + throw std::runtime_error("Failed to make context current"); + } + CHECK_GL_ERROR(); + widget->context()->doneCurrent(); + presentThread->setContext(widget->context()); // Start execution presentThread->start(); @@ -884,6 +884,7 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() { } void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) { +#if !defined(USE_GLES) auto glBackend = const_cast(*this).getGLBackend(); withMainThreadContext([&] { GLuint sourceTexture = glBackend->getTextureID(networkTexture->getGPUTexture()); @@ -924,11 +925,13 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne } else { newY = (target->height() - newHeight) / 2; } + glBlitNamedFramebuffer(fbo[0], fbo[1], 0, 0, texWidth, texHeight, newX, newY, newX + newWidth, newY + newHeight, GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT, GL_NEAREST); // don't delete the textures! glDeleteFramebuffers(2, fbo); *fenceSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); }); +#endif } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index d67c0b8663..1176471b40 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -8,7 +8,6 @@ #pragma once #include "DisplayPlugin.h" -#include #include #include diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 0a5704f6da..ce38b5ae00 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,6 +33,7 @@ #include "../Logging.h" #include "../CompositorHelper.h" +#include "DesktopPreviewProvider.h" #include "render-utils/hmd_ui_vert.h" #include "render-utils/hmd_ui_frag.h" @@ -254,17 +255,9 @@ void HmdDisplayPlugin::internalPresent() { swapBuffers(); } else if (_clearPreviewFlag) { - QImage image; - if (_vsyncEnabled) { - image = QImage(PathUtils::resourcesPath() + "images/preview.png"); - } else { - image = QImage(PathUtils::resourcesPath() + "images/preview-disabled.png"); - } - image = image.mirrored(); - image = image.convertToFormat(QImage::Format_RGBA8888); - if (!_previewTexture) { - _previewTexture = gpu::Texture::createStrict( + QImage image = DesktopPreviewProvider::getInstance()->getPreviewDisabledImage(_vsyncEnabled); + _previewTexture = gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, @@ -274,7 +267,6 @@ void HmdDisplayPlugin::internalPresent() { _previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); _previewTexture->assignStoredMip(0, image.byteCount(), image.constBits()); _previewTexture->setAutoGenerateMips(true); - } auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions())); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 3f42a353d1..2e39e2e498 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -708,6 +708,26 @@ void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) } } +void RenderableModelEntityItem::setJointMap(std::vector jointMap) { + if (jointMap.size() > 0) { + _jointMap = jointMap; + _jointMapCompleted = true; + return; + } + + _jointMapCompleted = false; +}; + +int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) { + int result = -1; + int mapSize = (int) _jointMap.size(); + if (modelJointIndex >=0 && modelJointIndex < mapSize) { + result = _jointMap[modelJointIndex]; + } + + return result; +} + bool RenderableModelEntityItem::contains(const glm::vec3& point) const { auto model = getModel(); if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) { @@ -813,6 +833,10 @@ bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int ind return setLocalJointTranslation(index, jointRelativePose.trans()); } +bool RenderableModelEntityItem::getJointMapCompleted() { + return _jointMapCompleted; +} + glm::quat RenderableModelEntityItem::getLocalJointRotation(int index) const { auto model = getModel(); if (model) { @@ -835,6 +859,13 @@ glm::vec3 RenderableModelEntityItem::getLocalJointTranslation(int index) const { return glm::vec3(); } +void RenderableModelEntityItem::setOverrideTransform(const Transform& transform, const glm::vec3& offset) { + auto model = getModel(); + if (model) { + model->overrideModelTransformAndOffset(transform, offset); + } +} + bool RenderableModelEntityItem::setLocalJointRotation(int index, const glm::quat& rotation) { autoResizeJointArrays(); bool result = false; @@ -929,6 +960,26 @@ bool RenderableModelEntityItem::getMeshes(MeshProxyList& result) { return !result.isEmpty(); } +void RenderableModelEntityItem::simulateRelayedJoints() { + ModelPointer model = getModel(); + if (model && model->isLoaded()) { + copyAnimationJointDataToModel(); + model->simulate(0.0f); + model->updateRenderItems(); + } +} + +void RenderableModelEntityItem::stopModelOverrideIfNoParent() { + auto model = getModel(); + if (model) { + bool overriding = model->isOverridingModelTransformAndOffset(); + QUuid parentID = getParentID(); + if (overriding && (!_relayParentJoints || parentID.isNull())) { + model->stopTransformAndOffsetOverride(); + } + } +} + void RenderableModelEntityItem::copyAnimationJointDataToModel() { auto model = getModel(); if (!model || !model->isLoaded()) { @@ -999,7 +1050,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { return; } - QVector jointsData; + QVector jointsData; const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy int frameCount = frames.size(); @@ -1280,6 +1331,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } entity->updateModelBounds(); + entity->stopModelOverrideIfNoParent(); if (model->isVisible() != _visible) { // FIXME: this seems like it could be optimized if we tracked our last known visible state in @@ -1342,8 +1394,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); + if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); + //else the joint have been mapped before but we have a new animation to load + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { + _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); + _jointMappingCompleted = false; + mapJoints(entity, model->getJointNames()); } if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) { animate(entity); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 1f3c4af054..b3988e0239 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -81,8 +81,14 @@ public: void setCollisionShape(const btCollisionShape* shape) override; virtual bool contains(const glm::vec3& point) const override; + void stopModelOverrideIfNoParent(); virtual bool shouldBePhysical() const override; + void simulateRelayedJoints(); + bool getJointMapCompleted(); + void setJointMap(std::vector jointMap); + int avatarJointIndex(int modelJointIndex); + void setOverrideTransform(const Transform& transform, const glm::vec3& offset); // these are in the frame of this object (model space) virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; @@ -90,7 +96,6 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override; virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override; - virtual glm::quat getLocalJointRotation(int index) const override; virtual glm::vec3 getLocalJointTranslation(int index) const override; virtual bool setLocalJointRotation(int index, const glm::quat& rotation) override; @@ -119,7 +124,9 @@ private: void getCollisionGeometryResource(); GeometryResource::Pointer _compoundShapeResource; + bool _jointMapCompleted { false }; bool _originalTexturesRead { false }; + std::vector _jointMap; QVariantMap _originalTextures; bool _dimensionsInitialized { true }; bool _needsJointSimulation { false }; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 9981245e6f..bca339dd18 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -26,7 +26,7 @@ static std::weak_ptr _texturedPipeline; // FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions static int32_t PARTICLE_UNIFORM_SLOT { 11 }; -static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key) { +static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { auto texturedPipeline = _texturedPipeline.lock(); if (!texturedPipeline) { auto state = std::make_shared(); @@ -40,10 +40,13 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co auto fragShader = textured_particle_frag::getShader(); auto program = gpu::Shader::createProgram(vertShader, fragShader); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("particleBuffer"), PARTICLE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); _texturedPipeline = texturedPipeline = gpu::Pipeline::create(program, state); + + batch.runLambda([program] { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("particleBuffer"), PARTICLE_UNIFORM_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); + }); } return std::make_shared(texturedPipeline, nullptr, nullptr, nullptr); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 8a53c9fba5..d1881f3e2a 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -46,7 +46,7 @@ struct PolyLineUniforms { glm::vec3 color; }; -static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { +static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch) { if (!polylinePipeline) { auto VS = paintStroke_vert::getShader(); auto PS = paintStroke_frag::getShader(); @@ -56,15 +56,21 @@ static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlum auto fadePS = gpu::Shader::createPixel(std::string(paintStroke_fade_frag)); gpu::ShaderPointer fadeProgram = gpu::Shader::createProgram(fadeVS, fadePS); #endif - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); - slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); + batch.runLambda([program #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); - slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT + 1)); - gpu::Shader::makeProgram(*fadeProgram, slotBindings); + , fadeProgram #endif + ] { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("originalTexture"), PAINTSTROKE_TEXTURE_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("polyLineBuffer"), PAINTSTROKE_UNIFORM_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); +#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT + slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), PAINTSTROKE_TEXTURE_SLOT + 1)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), PAINTSTROKE_UNIFORM_SLOT + 1)); + gpu::Shader::makeProgram(*fadeProgram, slotBindings); +#endif + }); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMask(*state); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index d2277f989d..019ef0e414 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1459,7 +1459,7 @@ static gpu::PipelinePointer _pipelines[2]; static gpu::PipelinePointer _wireframePipelines[2]; static gpu::Stream::FormatPointer _vertexFormat; -ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key) { +ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch) { if (!_pipelines[0]) { gpu::ShaderPointer vertexShaders[2] = { polyvox_vert::getShader(), polyvox_fade_vert::getShader() }; gpu::ShaderPointer pixelShaders[2] = { polyvox_frag::getShader(), polyvox_fade_frag::getShader() }; @@ -1487,7 +1487,10 @@ ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const Sha // Two sets of pipelines: normal and fading for (auto i = 0; i < 2; i++) { gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShaders[i], pixelShaders[i]); - gpu::Shader::makeProgram(*program, slotBindings); + + batch.runLambda([program, slotBindings] { + gpu::Shader::makeProgram(*program, slotBindings); + }); _pipelines[i] = gpu::Pipeline::create(program, state); _wireframePipelines[i] = gpu::Pipeline::create(program, wireframeState); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 722ad416e4..fe5213baa8 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -2086,6 +2086,10 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi EntityDynamicPointer action = _objectActions[actionID]; auto removedActionType = action->getType(); + action->setOwnerEntity(nullptr); + action->setIsMine(false); + _objectActions.remove(actionID); + if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) { _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar @@ -2101,9 +2105,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi // because they should have been set correctly when the action was added // and/or when children were linked } - action->setOwnerEntity(nullptr); - action->setIsMine(false); - _objectActions.remove(actionID); if (simulation) { action->removeFromSimulation(simulation); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ecfb7b5dcd..f9559a375b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -368,6 +368,7 @@ public: void* getPhysicsInfo() const { return _physicsInfo; } void setPhysicsInfo(void* data) { _physicsInfo = data; } + EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; virtual SpatialParentTree* getParentTree() const override; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 8bfce7e2d2..e2a5ddf8b5 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -395,6 +395,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape); CHECK_PROPERTY_CHANGE(PROP_DPI, dpi); + CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); @@ -527,6 +528,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); } if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) { @@ -720,7 +722,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionless, bool, setCollisionless); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ignoreForCollisions, bool, setCollisionless, getCollisionless); // legacy support COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionMask, uint8_t, setCollisionMask); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(collidesWith, CollisionMask); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(collidesWith, CollisionMask); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(collisionsWillMove, bool, setDynamic, getDynamic); // legacy support COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic); COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight); @@ -735,7 +737,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight); COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, xColor, setTextColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(backgroundColor, xColor, setBackgroundColor); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(shapeType, ShapeType); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(shapeType, ShapeType); COPY_PROPERTY_FROM_QSCRIPTVALUE(maxParticles, quint32, setMaxParticles); COPY_PROPERTY_FROM_QSCRIPTVALUE(lifespan, float, setLifespan); COPY_PROPERTY_FROM_QSCRIPTVALUE(isEmitting, bool, setIsEmitting); @@ -755,6 +757,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusSpread, float, setRadiusSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -772,10 +775,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(hazeMode, HazeMode); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(keyLightMode, KeyLightMode); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(ambientLightMode, AmbientLightMode); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(skyboxMode, SkyboxMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(keyLightMode, KeyLightMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); @@ -1163,6 +1166,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector); + ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool); ADD_PROPERTY_TO_MAP(PROP_SHAPE, Shape, shape, QString); @@ -1386,6 +1390,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, properties.getJointRotations()); APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet()); APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, properties.getJointTranslations()); + APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints()); } if (properties.getType() == EntityTypes::Light) { @@ -1745,6 +1750,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS, QVector, setJointRotations); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS, QVector, setJointTranslations); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); } if (properties.getType() == EntityTypes::Light) { @@ -2089,6 +2095,7 @@ void EntityItemProperties::markAllChanged() { _owningAvatarIDChanged = true; _dpiChanged = true; + _relayParentJointsChanged = true; } // The minimum bounding box for the entity. @@ -2455,6 +2462,9 @@ QList EntityItemProperties::listChangedProperties() { if (jointTranslationsChanged()) { out += "jointTranslations"; } + if (relayParentJointsChanged()) { + out += "relayParentJoints"; + } if (queryAACubeChanged()) { out += "queryAACube"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index c3d04dc7ad..3e0770f386 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -255,6 +255,7 @@ public: DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); + DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS); static QString getComponentModeString(uint32_t mode); static QString getComponentModeAsString(uint32_t mode); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 49bce37fbd..eb09a64628 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -92,4 +92,6 @@ const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30; const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid(); +const bool ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS = false; + #endif // hifi_EntityItemPropertiesDefaults_h diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 278d945ab0..f291973e52 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -367,7 +367,7 @@ inline xColor xColor_convertFromScriptValue(const QScriptValue& v, bool& isValid } \ } -#define COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(P, S) \ +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \ QScriptValue P = object.property(#P); \ if (P.isValid()) { \ QString newValue = P.toVariant().toString(); \ diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 7fd72bf0ee..90438ab01c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -40,6 +40,7 @@ enum EntityPropertyList { PROP_ANIMATION_FRAME_INDEX, PROP_ANIMATION_PLAYING, PROP_ANIMATION_ALLOW_TRANSLATION, + PROP_RELAY_PARENT_JOINTS, // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 46c1c9a4c7..a615beefa9 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -62,7 +62,7 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotations, getJointRotations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations); - + COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); _animationProperties.getProperties(properties); return properties; } @@ -80,6 +80,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); bool somethingChangedInAnimations = _animationProperties.setProperties(properties); @@ -115,6 +116,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); int bytesFromAnimation; withWriteLock([&] { @@ -155,6 +157,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_JOINT_ROTATIONS; requestedProperties += PROP_JOINT_TRANSLATIONS_SET; requestedProperties += PROP_JOINT_TRANSLATIONS; + requestedProperties += PROP_RELAY_PARENT_JOINTS; return requestedProperties; } @@ -173,6 +176,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); + APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints()); withReadLock([&] { _animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -448,7 +452,7 @@ void ModelEntityItem::resizeJointArrays(int newSize) { }); } -void ModelEntityItem::setAnimationJointsData(const QVector& jointsData) { +void ModelEntityItem::setAnimationJointsData(const QVector& jointsData) { resizeJointArrays(jointsData.size()); _jointDataLock.withWriteLock([&] { for (auto index = 0; index < jointsData.size(); ++index) { @@ -586,6 +590,18 @@ QString ModelEntityItem::getModelURL() const { }); } +void ModelEntityItem::setRelayParentJoints(bool relayJoints) { + withWriteLock([&] { + _relayParentJoints = relayJoints; + }); +} + +bool ModelEntityItem::getRelayParentJoints() const { + return resultWithReadLock([&] { + return _relayParentJoints; + }); +} + QString ModelEntityItem::getCompoundShapeURL() const { return _compoundShapeURL.get(); } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 7fee022011..c2109ba51f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -99,6 +99,9 @@ public: void setAnimationHold(bool hold); bool getAnimationHold() const; + void setRelayParentJoints(bool relayJoints); + bool getRelayParentJoints() const; + void setAnimationFirstFrame(float firstFrame); float getAnimationFirstFrame() const; @@ -121,7 +124,7 @@ public: virtual void setJointTranslations(const QVector& translations); virtual void setJointTranslationsSet(const QVector& translationsSet); - virtual void setAnimationJointsData(const QVector& jointsData); + virtual void setAnimationJointsData(const QVector& jointsData); QVector getJointRotations() const; QVector getJointRotationsSet() const; @@ -147,7 +150,7 @@ protected: bool _jointTranslationsExplicitlySet{ false }; // were the joints set as a property or just side effect of animations struct ModelJointData { - JointData joint; + EntityJointData joint; bool rotationDirty { false }; bool translationDirty { false }; }; @@ -157,6 +160,7 @@ protected: rgbColor _color; QString _modelURL; + bool _relayParentJoints; ThreadSafeValueCache _compoundShapeURL; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index e446861627..ad002b237c 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -14,6 +14,7 @@ #include "FBX.h" +#include #include #include #include @@ -33,7 +34,11 @@ class QIODevice; class FBXNode; +#if defined(Q_OS_ANDROID) +#define FBX_PACK_NORMALS 0 +#else #define FBX_PACK_NORMALS 1 +#endif #if FBX_PACK_NORMALS using NormalType = glm::uint32; diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index bc7daa98f4..b0e2faa600 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -603,7 +603,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (!blendShape.normals.empty() && blendShape.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals blendShape.tangents.reserve(blendShape.normals.size()); - std::fill_n(std::back_inserter(fbxMesh.tangents), blendShape.normals.size(), Vectors::UNIT_X); + std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); } } @@ -611,7 +611,11 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); // If there are normals then there should be tangents - assert(normalsSize == tangentsSize); + + assert(normalsSize <= tangentsSize); + if (tangentsSize > normalsSize) { + qWarning() << "Unexpected tangents in " << url; + } const auto normalsAndTangentsSize = normalsSize + tangentsSize; const int normalsAndTangentsStride = 2 * sizeof(NormalType); const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); diff --git a/libraries/gl/src/gl/Config.cpp b/libraries/gl/src/gl/Config.cpp index 1f29fe21b1..0e3a174e91 100644 --- a/libraries/gl/src/gl/Config.cpp +++ b/libraries/gl/src/gl/Config.cpp @@ -13,23 +13,112 @@ #include -#if defined(Q_OS_ANDROID) -PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT = NULL; -PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64vEXT = NULL; -PFNGLFRAMEBUFFERTEXTUREEXTPROC glFramebufferTextureEXT = NULL; +#if defined(Q_OS_WIN) +#elif defined(Q_OS_ANDROID) +#elif defined(Q_OS_MAC) +#include +#include +#include +#include +#else +#include +#include #endif + + +#if defined(Q_OS_WIN) + +static void* getGlProcessAddress(const char *namez) { + auto result = wglGetProcAddress(namez); + if (!result) { + static HMODULE glModule = nullptr; + if (!glModule) { + glModule = LoadLibraryW(L"opengl32.dll"); + } + result = GetProcAddress(glModule, namez); + } + if (!result) { + OutputDebugStringA(namez); + OutputDebugStringA("\n"); + } + return (void*)result; +} + +typedef BOOL(APIENTRYP PFNWGLSWAPINTERVALEXTPROC)(int interval); +typedef int (APIENTRYP PFNWGLGETSWAPINTERVALEXTPROC) (void); +typedef BOOL(APIENTRYP PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int *attribList); + +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; +PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT; +PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; +PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; + +#elif defined(Q_OS_ANDROID) + +static void* getGlProcessAddress(const char *namez) { + auto result = eglGetProcAddress(namez); + return (void*)result; +} + +#elif defined(Q_OS_MAC) + +static void* getGlProcessAddress(const char *namez) { + static void* GL_LIB = nullptr; + if (nullptr == GL_LIB) { + GL_LIB = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW | RTLD_GLOBAL); + } + return dlsym(GL_LIB, namez); +} + +#else + +static void* getGlProcessAddress(const char *namez) { + return (void*)glXGetProcAddressARB((const GLubyte*)namez); +} + +#endif + + + void gl::initModuleGl() { static std::once_flag once; std::call_once(once, [] { -#if defined(Q_OS_ANDROID) - glQueryCounterEXT = (PFNGLQUERYCOUNTEREXTPROC)eglGetProcAddress("glQueryCounterEXT"); - glGetQueryObjectui64vEXT = (PFNGLGETQUERYOBJECTUI64VEXTPROC)eglGetProcAddress("glGetQueryObjectui64vEXT"); - glFramebufferTextureEXT = (PFNGLFRAMEBUFFERTEXTUREEXTPROC)eglGetProcAddress("glFramebufferTextureEXT"); +#if defined(Q_OS_WIN) + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)getGlProcessAddress("wglSwapIntervalEXT"); + wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC)getGlProcessAddress("wglGetSwapIntervalEXT"); + wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)getGlProcessAddress("wglChoosePixelFormatARB"); + wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)getGlProcessAddress("wglCreateContextAttribsARB"); +#endif + +#if defined(USE_GLES) + gladLoadGLES2Loader(getGlProcessAddress); #else - glewExperimental = true; - glewInit(); + gladLoadGLLoader(getGlProcessAddress); #endif }); } +int gl::getSwapInterval() { +#if defined(Q_OS_WIN) + return wglGetSwapIntervalEXT(); +#elif defined(Q_OS_MAC) + GLint interval; + CGLGetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); + return interval; +#else + // TODO: Fill in for linux + return 1; +#endif +} + +void gl::setSwapInterval(int interval) { +#if defined(Q_OS_WIN) + wglSwapIntervalEXT(interval); +#elif defined(Q_OS_MAC) + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#else + Q_UNUSED(interval); +#endif +} diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index cdf86675c6..aad000a242 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -14,12 +14,7 @@ #include -#if defined(Q_OS_ANDROID) -#define HIFI_GLES -#define HIFI_EGL -#endif - -#if defined(HIFI_GLES) +#if defined(USE_GLES) // Minimum GL ES version required is 3.2 #define GL_MIN_VERSION_MAJOR 0x03 #define GL_MIN_VERSION_MINOR 0x02 @@ -35,51 +30,28 @@ #define MINIMUM_GL_VERSION ((GL_MIN_VERSION_MAJOR << 8) | GL_MIN_VERSION_MINOR) -#if defined(HIFI_GLES) +#include + +#if defined(Q_OS_ANDROID) #include +#else + +#ifndef GL_SLUMINANCE8_EXT +#define GL_SLUMINANCE8_EXT 0x8C47 #endif -#if defined(HIFI_GLES) -#include +// Prevent inclusion of System GL headers +#define __glext_h_ +#define __gl_h_ +#define __gl3_h_ -#define GL_DEPTH_COMPONENT32_OES 0x81A7 -#define GL_TIME_ELAPSED_EXT 0x88BF -#define GL_TIMESTAMP_EXT 0x8E28 -#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 -#define GL_TEXTURE_BORDER_COLOR_EXT 0x1004 -#define GL_CLAMP_TO_BORDER_EXT 0x812D -#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE -#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF - - -// Add some additional extensions missing from GLES 3.1 -extern "C" { - typedef void (GL_APIENTRYP PFNGLQUERYCOUNTEREXTPROC) (GLuint id, GLenum target); - typedef void (GL_APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params); - typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); - extern PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT; - extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64vEXT; - extern PFNGLFRAMEBUFFERTEXTUREEXTPROC glFramebufferTextureEXT; -} - -#else // !defined(HIFI_GLES) - -#define GL_GLEXT_PROTOTYPES 1 -#include - -#if defined(Q_OS_DARWIN) -#include -#include -#include -#elif defined(Q_OS_WIN64) -#include #endif -#endif // !defined(Q_OS_ANDROID) - // Platform specific code to load the GL functions namespace gl { void initModuleGl(); + int getSwapInterval(); + void setSwapInterval(int swapInterval); } #endif // hifi_gpu_GPUConfig_h diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index f7e08e607b..8acbada5aa 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -23,10 +23,11 @@ #include #include #include "GLLogging.h" +#include "Config.h" #ifdef Q_OS_WIN -#ifdef DEBUG +#if defined(DEBUG) || defined(USE_GLES) static bool enableDebugLogger = true; #else static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); @@ -69,15 +70,9 @@ Context::Context(QWindow* window) { setWindow(window); } -#ifdef Q_OS_WIN -void Context::destroyWin32Context(HGLRC hglrc) { - wglDeleteContext(hglrc); -} -#endif - void Context::release() { doneCurrent(); -#ifdef Q_OS_WIN +#ifdef GL_CUSTOM_CONTEXT if (_wrappedContext) { destroyContext(_wrappedContext); _wrappedContext = nullptr; @@ -123,14 +118,68 @@ void Context::setWindow(QWindow* window) { release(); _window = window; -#ifdef Q_OS_WIN +#ifdef GL_CUSTOM_CONTEXT _hwnd = (HWND)window->winId(); #endif updateSwapchainMemoryCounter(); } -#ifdef Q_OS_WIN +void Context::clear() { + glClearColor(0, 0, 0, 1); + QSize windowSize = _window->size() * _window->devicePixelRatio(); + glViewport(0, 0, windowSize.width(), windowSize.height()); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + swapBuffers(); +} + +#if defined(GL_CUSTOM_CONTEXT) + +static void debugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { + if (GL_DEBUG_SEVERITY_NOTIFICATION == severity) { + return; + } + // FIXME For high severity errors, force a sync to the log, since we might crash + // before the log file was flushed otherwise. Performance hit here + qCDebug(glLogging) << "OpenGL: " << message; +} + +static void setupPixelFormatSimple(HDC hdc) { + // FIXME build the PFD based on the + static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be + { + sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor + 1, // Version Number + PFD_DRAW_TO_WINDOW | // Format Must Support Window + PFD_SUPPORT_OPENGL | // Format Must Support OpenGL + PFD_DOUBLEBUFFER, // Must Support Double Buffering + PFD_TYPE_RGBA, // Request An RGBA Format + 24, // Select Our Color Depth + 0, 0, 0, 0, 0, 0, // Color Bits Ignored + 1, // Alpha Buffer + 0, // Shift Bit Ignored + 0, // No Accumulation Buffer + 0, 0, 0, 0, // Accumulation Bits Ignored + 24, // 24 Bit Z-Buffer (Depth Buffer) + 8, // 8 Bit Stencil Buffer + 0, // No Auxiliary Buffer + PFD_MAIN_PLANE, // Main Drawing Layer + 0, // Reserved + 0, 0, 0 // Layer Masks Ignored + }; + auto pixelFormat = ChoosePixelFormat(hdc, &pfd); + if (pixelFormat == 0) { + throw std::runtime_error("Unable to create initial context"); + } + + if (SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) { + throw std::runtime_error("Unable to create initial context"); + } +} + +void Context::destroyWin32Context(HGLRC hglrc) { + wglDeleteContext(hglrc); +} bool Context::makeCurrent() { BOOL result = wglMakeCurrent(_hdc, _hglrc); @@ -148,56 +197,38 @@ void Context::doneCurrent() { wglMakeCurrent(0, 0); } -void GLAPIENTRY debugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { - if (GL_DEBUG_SEVERITY_NOTIFICATION == severity) { - return; - } - qCDebug(glLogging) << "OpenGL: " << message; - - // For high severity errors, force a sync to the log, since we might crash - // before the log file was flushed otherwise. Performance hit here - if (GL_DEBUG_SEVERITY_HIGH == severity) { - AbstractLoggerInterface* logger = AbstractLoggerInterface::get(); - if (logger) { - // FIXME find a way to force the log file to sync that doesn't lead to a deadlock - // logger->sync(); - } - } -} +// Pixel format arguments +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 -// FIXME build the PFD based on the -static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be -{ - sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor - 1, // Version Number - PFD_DRAW_TO_WINDOW | // Format Must Support Window - PFD_SUPPORT_OPENGL | // Format Must Support OpenGL - PFD_DOUBLEBUFFER, // Must Support Double Buffering - PFD_TYPE_RGBA, // Request An RGBA Format - 24, // Select Our Color Depth - 0, 0, 0, 0, 0, 0, // Color Bits Ignored - 1, // Alpha Buffer - 0, // Shift Bit Ignored - 0, // No Accumulation Buffer - 0, 0, 0, 0, // Accumulation Bits Ignored - 24, // 24 Bit Z-Buffer (Depth Buffer) - 8, // 8 Bit Stencil Buffer - 0, // No Auxiliary Buffer - PFD_MAIN_PLANE, // Main Drawing Layer - 0, // Reserved - 0, 0, 0 // Layer Masks Ignored -}; +// Context create arguments +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -void setupPixelFormatSimple(HDC hdc) { - auto pixelFormat = ChoosePixelFormat(hdc, &pfd); - if (pixelFormat == 0) { - throw std::runtime_error("Unable to create initial context"); - } +// Context create flag bits +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 + +#if !defined(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB) +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#endif + +typedef BOOL(APIENTRYP PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int *attribList); + +GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; +GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; - if (SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) { - throw std::runtime_error("Unable to create initial context"); - } -} void Context::create() { if (!PRIMARY) { @@ -218,7 +249,6 @@ void Context::create() { if (qApp->property(hifi::properties::CRASHED).toBool()) { enableDebugLogger = true; } - auto hdc = GetDC(hwnd); setupPixelFormatSimple(hdc); auto glrc = wglCreateContext(hdc); @@ -227,20 +257,25 @@ void Context::create() { if (!makeCurrentResult) { throw std::runtime_error("Unable to create initial context"); } - glewExperimental = true; - glewInit(); - if (glewIsSupported("GL_VERSION_4_5")) { - _version = 0x0405; - } else if (glewIsSupported("GL_VERSION_4_3")) { - _version = 0x0403; - } - glGetError(); + gl::initModuleGl(); wglMakeCurrent(0, 0); wglDeleteContext(glrc); ReleaseDC(hwnd, hdc); }); _hdc = GetDC(_hwnd); +#if defined(USE_GLES) + _version = 0x0200; +#else + if (GLAD_GL_VERSION_4_5) { + _version = 0x0405; + } else if (GLAD_GL_VERSION_4_3) { + _version = 0x0403; + } else { + _version = 0x0401; + } +#endif + static int pixelFormat = 0; static PIXELFORMATDESCRIPTOR pfd; if (!pixelFormat) { @@ -261,6 +296,7 @@ void Context::create() { formatAttribs.push_back(24); formatAttribs.push_back(WGL_STENCIL_BITS_ARB); formatAttribs.push_back(8); + #ifdef NATIVE_SRGB_FRAMEBUFFER // formatAttribs.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB); // formatAttribs.push_back(GL_TRUE); @@ -286,7 +322,11 @@ void Context::create() { contextAttribs.push_back(WGL_CONTEXT_MINOR_VERSION_ARB); contextAttribs.push_back(minorVersion); contextAttribs.push_back(WGL_CONTEXT_PROFILE_MASK_ARB); +#if defined(USE_GLES) + contextAttribs.push_back(WGL_CONTEXT_ES2_PROFILE_BIT_EXT); +#else contextAttribs.push_back(WGL_CONTEXT_CORE_PROFILE_BIT_ARB); +#endif contextAttribs.push_back(WGL_CONTEXT_FLAGS_ARB); if (enableDebugLogger) { contextAttribs.push_back(WGL_CONTEXT_DEBUG_BIT_ARB); @@ -307,22 +347,17 @@ void Context::create() { qApp->setProperty(hifi::properties::gl::PRIMARY_CONTEXT, QVariant::fromValue((void*)PRIMARY)); } + if (!makeCurrent()) { + throw std::runtime_error("Could not make context current"); + } if (enableDebugLogger) { - makeCurrent(); glDebugMessageCallback(debugMessageCallback, NULL); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); - doneCurrent(); } + doneCurrent(); } -#endif -void Context::clear() { - glClearColor(0, 0, 0, 1); - QSize windowSize = _window->size() * _window->devicePixelRatio(); - glViewport(0, 0, windowSize.width(), windowSize.height()); - glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - swapBuffers(); -} +#endif OffscreenContext::~OffscreenContext() { diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h index d1b0d53631..4f0747a86f 100644 --- a/libraries/gl/src/gl/Context.h +++ b/libraries/gl/src/gl/Context.h @@ -22,6 +22,9 @@ class QWindow; class QOpenGLContext; class QThread; +#if defined(Q_OS_WIN) +#define GL_CUSTOM_CONTEXT +#endif namespace gl { class Context { @@ -29,7 +32,7 @@ namespace gl { QWindow* _window { nullptr }; static Context* PRIMARY; static void destroyContext(QOpenGLContext* context); -#if defined(Q_OS_WIN) +#if defined(GL_CUSTOM_CONTEXT) uint32_t _version { 0x0401 }; HWND _hwnd { 0 }; HDC _hdc { 0 }; diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index 4d5d2c4e9f..dd65c3076c 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -8,6 +8,8 @@ #include "Context.h" +#include "Config.h" + #include #include @@ -28,7 +30,7 @@ void Context::makeCurrent(QOpenGLContext* context, QSurface* surface) { } QOpenGLContext* Context::qglContext() { -#ifdef Q_OS_WIN +#ifdef GL_CUSTOM_CONTEXT if (!_wrappedContext) { _wrappedContext = new QOpenGLContext(); _wrappedContext->setNativeHandle(QVariant::fromValue(QWGLNativeContext(_hglrc, _hwnd))); @@ -45,10 +47,12 @@ void Context::moveToThread(QThread* thread) { qglContext()->moveToThread(thread); } -#ifndef Q_OS_WIN +#ifndef GL_CUSTOM_CONTEXT bool Context::makeCurrent() { updateSwapchainMemoryCounter(); - return _context->makeCurrent(_window); + bool result = _context->makeCurrent(_window); + gl::initModuleGl(); + return result; } void Context::swapBuffers() { diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 722253c26f..d5e3bb5a67 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -2,6 +2,8 @@ #include +#include "Config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include - 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). @@ -28,18 +28,19 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; static std::once_flag once; std::call_once(once, [] { -#if defined(HIFI_GLES) +#if defined(USE_GLES) format.setRenderableType(QSurfaceFormat::OpenGLES); format.setRedBufferSize(8); format.setGreenBufferSize(8); format.setBlueBufferSize(8); format.setAlphaBufferSize(8); +#else + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); #endif // 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); setGLFormatVersion(format); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); QSurfaceFormat::setDefaultFormat(format); }); return format; diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 73319e2e6d..581bb09360 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -26,7 +26,7 @@ class QGLFormat; template // https://bugreports.qt.io/browse/QTBUG-64703 prevents us from using "defined(QT_OPENGL_ES_3_1)" -#if defined(Q_OS_ANDROID) +#if defined(USE_GLES) void setGLFormatVersion(F& format, int major = 3, int minor = 2) #else void setGLFormatVersion(F& format, int major = 4, int minor = 5) diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 23bdcff640..c1d049f7f3 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -69,6 +69,7 @@ void GLWidget::createContext() { _context->create(); _context->makeCurrent(); _context->clear(); + _context->doneCurrent(); } bool GLWidget::makeCurrent() { diff --git a/libraries/gl/src/gl/GLWindow.cpp b/libraries/gl/src/gl/GLWindow.cpp index c40301deb0..e1e6279b1c 100644 --- a/libraries/gl/src/gl/GLWindow.cpp +++ b/libraries/gl/src/gl/GLWindow.cpp @@ -8,6 +8,7 @@ #include "GLWindow.h" +#include "Config.h" #include #include @@ -19,26 +20,18 @@ void GLWindow::createContext(QOpenGLContext* shareContext) { } void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) { - setSurfaceType(QSurface::OpenGLSurface); - setFormat(format); - _context = new QOpenGLContext; - _context->setFormat(format); - if (shareContext) { - _context->setShareContext(shareContext); - } + _context = new gl::Context(); + _context->setWindow(this); _context->create(); + _context->makeCurrent(); + _context->clear(); } GLWindow::~GLWindow() { - if (_context) { - _context->doneCurrent(); - _context->deleteLater(); - _context = nullptr; - } } bool GLWindow::makeCurrent() { - bool makeCurrentResult = _context->makeCurrent(this); + bool makeCurrentResult = _context->makeCurrent(); Q_ASSERT(makeCurrentResult); std::call_once(_reportOnce, []{ @@ -48,8 +41,6 @@ bool GLWindow::makeCurrent() { qCDebug(glLogging) << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); }); - Q_ASSERT(_context == QOpenGLContext::currentContext()); - return makeCurrentResult; } @@ -58,11 +49,11 @@ void GLWindow::doneCurrent() { } void GLWindow::swapBuffers() { - _context->swapBuffers(this); + _context->swapBuffers(); } QOpenGLContext* GLWindow::context() const { - return _context; + return _context->qglContext(); } diff --git a/libraries/gl/src/gl/GLWindow.h b/libraries/gl/src/gl/GLWindow.h index bccfcd7320..e3df21cb49 100644 --- a/libraries/gl/src/gl/GLWindow.h +++ b/libraries/gl/src/gl/GLWindow.h @@ -12,9 +12,9 @@ #include #include +#include "Context.h" class QOpenGLContext; -class QOpenGLDebugLogger; class GLWindow : public QWindow { public: @@ -26,8 +26,8 @@ public: void swapBuffers(); QOpenGLContext* context() const; private: + gl::Context* _context{ nullptr }; std::once_flag _reportOnce; - QOpenGLContext* _context{ nullptr }; }; #endif diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 67eb3520c9..8a476c45ad 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -12,6 +12,8 @@ #include "OffscreenGLCanvas.h" +#include "Config.h" + #include #include #include diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.cpp b/libraries/gl/src/gl/OpenGLVersionChecker.cpp deleted file mode 100644 index 771a8b9a75..0000000000 --- a/libraries/gl/src/gl/OpenGLVersionChecker.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// -// OpenGLVersionChecker.cpp -// libraries/gl/src/gl -// -// Created by David Rowe on 28 Jan 2016. -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OpenGLVersionChecker.h" -#include "Config.h" - -#include - -#include -#include -#include -#include - -#include "GLHelpers.h" - -OpenGLVersionChecker::OpenGLVersionChecker(int& argc, char** argv) : - QApplication(argc, argv) -{ -} - -const QGLFormat& getDefaultGLFormat() { - // Specify an OpenGL 3.3 format using the Core profile. - // That is, no old-school fixed pipeline functionality - static QGLFormat glFormat; - static std::once_flag once; - std::call_once(once, [] { - setGLFormatVersion(glFormat); - glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0 - glFormat.setSampleBuffers(false); - glFormat.setDepth(false); - glFormat.setStencil(false); - QGLFormat::setDefaultFormat(glFormat); - }); - return glFormat; -} - - -QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { - valid = true; - override = false; - - QGLWidget* glWidget = new QGLWidget(getDefaultGLFormat()); - - valid = glWidget->isValid(); - // Inform user if no OpenGL support - if (!valid) { - QMessageBox messageBox; - messageBox.setWindowTitle("Missing OpenGL Support"); - messageBox.setIcon(QMessageBox::Warning); - messageBox.setText(QString().sprintf("Your system does not support OpenGL, Interface cannot run.")); - messageBox.setInformativeText("Press OK to exit."); - messageBox.setStandardButtons(QMessageBox::Ok); - messageBox.setDefaultButton(QMessageBox::Ok); - messageBox.exec(); - return QJsonObject(); - } - - // Retrieve OpenGL version - // glWidget->initializeGL(); - glWidget->makeCurrent(); - QJsonObject glData = getGLContextData(); - delete glWidget; - - // Compare against minimum - // The GL_VERSION string begins with a version number in one of these forms: - // - major_number.minor_number - // - major_number.minor_number.release_number - // Reference: https://www.opengl.org/sdk/docs/man/docbook4/xhtml/glGetString.xml - - int minimumMajorNumber = (MINIMUM_GL_VERSION >> 8) & 0xFF; - int minimumMinorNumber = (MINIMUM_GL_VERSION & 0xFF); - int majorNumber = 0; - int minorNumber = 0; - const QString version { "version" }; - if (glData.contains(version)) { - QString glVersion = glData[version].toString(); - QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); - if (versionParts.size() >= 2) { - majorNumber = versionParts[0].toInt(); - minorNumber = versionParts[1].toInt(); - valid = (majorNumber > minimumMajorNumber - || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); - } else { - valid = false; - } - } else { - valid = false; - } - - // Prompt user if below minimum - if (!valid) { - QMessageBox messageBox; - messageBox.setWindowTitle("OpenGL Version Too Low"); - messageBox.setIcon(QMessageBox::Warning); - messageBox.setText(QString().sprintf("Your OpenGL version of %i.%i is lower than the minimum of %i.%i.", - majorNumber, minorNumber, minimumMajorNumber, minimumMinorNumber)); - messageBox.setInformativeText("Press OK to exit; Ignore to continue."); - messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Ignore); - messageBox.setDefaultButton(QMessageBox::Ok); - override = messageBox.exec() == QMessageBox::Ignore; - } - - return glData; -} diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.h b/libraries/gl/src/gl/OpenGLVersionChecker.h deleted file mode 100644 index 913675af24..0000000000 --- a/libraries/gl/src/gl/OpenGLVersionChecker.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// OpenGLVersionChecker.h -// libraries/gl/src/gl -// -// Created by David Rowe on 28 Jan 2016. -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_OpenGLVersionChecker_h -#define hifi_OpenGLVersionChecker_h - -#include - -class OpenGLVersionChecker : public QApplication { - -public: - OpenGLVersionChecker(int& argc, char** argv); - - static QJsonObject checkVersion(bool& valid, bool& override); -}; - -#endif // hifi_OpenGLVersionChecker_h diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 2192f5f45b..eb6de5df13 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -153,30 +153,7 @@ void GLBackend::init() { qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; - - glewExperimental = true; - GLenum err = glewInit(); - glGetError(); // clear the potential error from glewExperimental - if (GLEW_OK != err) { - // glewInit failed, something is seriously wrong. - qCDebug(gpugllogging, "Error: %s\n", glewGetErrorString(err)); - } - qCDebug(gpugllogging, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); - -#if defined(Q_OS_WIN) - if (wglewGetExtension("WGL_EXT_swap_control")) { - int swapInterval = wglGetSwapIntervalEXT(); - qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } -#endif - -#if defined(Q_OS_LINUX) - // TODO: Write the correct code for Linux... - /* if (wglewGetExtension("WGL_EXT_swap_control")) { - int swapInterval = wglGetSwapIntervalEXT(); - qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - }*/ -#endif + qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF")); #if THREADED_TEXTURE_BUFFERING // This has to happen on the main thread in order to give the thread // pool a reasonable parent object diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 7b3db4e4fe..1873034ac5 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -75,7 +75,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { break; case gpu::NUINT8: if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { - result = GL_SLUMINANCE8; + result = GL_SLUMINANCE8_EXT; } else { result = GL_R8; } @@ -491,7 +491,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::NUINT8: { if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { - texel.internalFormat = GL_SLUMINANCE8; + texel.internalFormat = GL_SLUMINANCE8_EXT; } else { texel.internalFormat = GL_R8; } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp index 32bfb777a8..88cf89ad99 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp @@ -102,7 +102,7 @@ void GL41Backend::do_drawInstanced(const Batch& batch, size_t paramOffset) { _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; _stats._DSNumDrawcalls += trueNumInstances; } else { - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); _stats._DSNumTriangles += (numInstances * numVertices) / 3; _stats._DSNumDrawcalls += numInstances; } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index cde4ff5f75..925e83bb77 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -156,7 +156,7 @@ void GL41Texture::syncSampler() const { glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); if (sampler.doComparison()) { - glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 795a630ccd..1d415c7ca3 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -197,7 +197,7 @@ void GL45Texture::syncSampler() const { glTextureParameteri(_id, GL_TEXTURE_MAG_FILTER, fm.magFilter); if (sampler.doComparison()) { - glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); glTextureParameteri(_id, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_NONE); diff --git a/libraries/gpu-gles/CMakeLists.txt b/libraries/gpu-gles/CMakeLists.txt index 55ec53b184..16575a7018 100644 --- a/libraries/gpu-gles/CMakeLists.txt +++ b/libraries/gpu-gles/CMakeLists.txt @@ -1,11 +1,5 @@ set(TARGET_NAME gpu-gles) -setup_hifi_library(OpenGL) +setup_hifi_library(Concurrent) link_hifi_libraries(shared gl gpu) GroupSources("src") - target_opengl() -target_nsight() - -if (NOT ANDROID) - target_glew() -endif () diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp index cb00d00b3e..6fd5df6f81 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp @@ -22,17 +22,18 @@ #include "nvToolsExt.h" #endif +#include #include #include #include #include "GLTexture.h" #include "GLShader.h" + using namespace gpu; using namespace gpu::gl; static GLBackend* INSTANCE{ nullptr }; -static const char* GL_BACKEND_PROPERTY_NAME = "com.highfidelity.gl.backend"; BackendPointer GLBackend::createBackend() { // FIXME provide a mechanism to override the backend for testing @@ -45,18 +46,17 @@ BackendPointer GLBackend::createBackend() { result->initInput(); result->initTransform(); + result->initTextureManagementStage(); INSTANCE = result.get(); void* voidInstance = &(*result); - qApp->setProperty(GL_BACKEND_PROPERTY_NAME, QVariant::fromValue(voidInstance)); - - gl::GLTexture::initTextureTransferHelper(); + qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance)); return result; } GLBackend& getBackend() { if (!INSTANCE) { - INSTANCE = static_cast(qApp->property(GL_BACKEND_PROPERTY_NAME).value()); + INSTANCE = static_cast(qApp->property(hifi::properties::gl::BACKEND).value()); } return *INSTANCE; } @@ -65,9 +65,6 @@ bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindin return GLShader::makeProgram(getBackend(), shader, slotBindings); } -std::array commandNames = { - {QString("draw"),QString("drawIndexed"),QString("drawInstanced"),QString("drawIndexedInstanced"),QString("multiDrawIndirect"),QString("multiDrawIndexedIndirect"),QString("setInputFormat"),QString("setInputBuffer"),QString("setIndexBuffer"),QString("setIndirectBuffer"),QString("setModelTransform"),QString("setViewTransform"),QString("setProjectionTransform"),QString("setViewportTransform"),QString("setDepthRangeTransform"),QString("setPipeline"),QString("setStateBlendFactor"),QString("setStateScissorRect"),QString("setUniformBuffer"),QString("setResourceTexture"),QString("setFramebuffer"),QString("clearFramebuffer"),QString("blit"),QString("generateTextureMips"),QString("beginQuery"),QString("endQuery"),QString("getQuery"),QString("resetStages"),QString("runLambda"),QString("startNamedCall"),QString("stopNamedCall"),QString("glUniform1i"),QString("glUniform1f"),QString("glUniform2f"),QString("glUniform3f"),QString("glUniform4f"),QString("glUniform3fv"),QString("glUniform4fv"),QString("glUniform4iv"),QString("glUniformMatrix3fv"),QString("glUniformMatrix4fv"),QString("glColor4f"),QString("pushProfileRange"),QString("popProfileRange"),QString("NUM_COMMANDS")} -}; GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = { @@ -94,6 +91,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_setStateScissorRect), (&::gpu::gl::GLBackend::do_setUniformBuffer), + (&::gpu::gl::GLBackend::do_setResourceBuffer), (&::gpu::gl::GLBackend::do_setResourceTexture), (&::gpu::gl::GLBackend::do_setFramebuffer), @@ -107,6 +105,11 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_resetStages), + (&::gpu::gl::GLBackend::do_disableContextViewCorrection), + (&::gpu::gl::GLBackend::do_restoreContextViewCorrection), + (&::gpu::gl::GLBackend::do_disableContextStereo), + (&::gpu::gl::GLBackend::do_restoreContextStereo), + (&::gpu::gl::GLBackend::do_runLambda), (&::gpu::gl::GLBackend::do_startNamedCall), @@ -144,16 +147,11 @@ void GLBackend::init() { qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; - - /*glewExperimental = true; - GLenum err = glewInit(); - glGetError(); // clear the potential error from glewExperimental - if (GLEW_OK != err) { - // glewInit failed, something is seriously wrong. - qCDebug(gpugllogging, "Error: %s\n", glewGetErrorString(err)); - } - qCDebug(gpugllogging, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); - */ +#if THREADED_TEXTURE_BUFFERING + // This has to happen on the main thread in order to give the thread + // pool a reasonable parent object + GLVariableAllocationSupport::TransferJob::startBufferingThread(); +#endif }); } @@ -165,8 +163,6 @@ GLBackend::GLBackend() { GLBackend::~GLBackend() { - resetStages(); - killInput(); killTransform(); } @@ -178,7 +174,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { _inRenderTransferPass = true; { // Sync all the buffers - ANDROID_PROFILE(render, "syncGPUBuffer", 0xffaaffaa, 1) + PROFILE_RANGE(render_gpu_gl_detail, "syncGPUBuffer"); for (auto& cached : batch._buffers._items) { if (cached._data) { @@ -187,8 +183,8 @@ void GLBackend::renderPassTransfer(const Batch& batch) { } } - { // Sync all the buffers - ANDROID_PROFILE(render, "syncCPUTransform", 0xffaaaaff, 1) + { // Sync all the transform states + PROFILE_RANGE(render_gpu_gl_detail, "syncCPUTransform"); _transform._cameras.clear(); _transform._cameraOffsets.clear(); @@ -203,10 +199,17 @@ void GLBackend::renderPassTransfer(const Batch& batch) { _transform.preUpdate(_commandIndex, _stereo); break; + case Batch::COMMAND_disableContextStereo: + _stereo._contextDisable = true; + break; + + case Batch::COMMAND_restoreContextStereo: + _stereo._contextDisable = false; + break; + case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: { - ANDROID_PROFILE_COMMAND(render, (int)(*command), 0xffeeaaff, 1) CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); break; @@ -221,8 +224,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { } { // Sync the transform buffers - //PROFILE_RANGE(render_gpu_gl, "transferTransformState"); - ANDROID_PROFILE(render, "transferTransformState", 0xff0000ff, 1) + PROFILE_RANGE(render_gpu_gl_detail, "transferGPUTransform"); transferTransformState(batch); } @@ -257,14 +259,12 @@ void GLBackend::renderPassDraw(const Batch& batch) { updateInput(); updateTransform(batch); updatePipeline(); - {ANDROID_PROFILE_COMMAND(render, (int)(*command), 0xff0000ff, 1) + CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); - } break; } default: { - ANDROID_PROFILE_COMMAND(render, (int)(*command), 0xffff00ff, 1) CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); break; @@ -277,7 +277,6 @@ void GLBackend::renderPassDraw(const Batch& batch) { } void GLBackend::render(const Batch& batch) { - ANDROID_PROFILE(render, "GLBackendRender", 0xffff00ff, 1) _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); // Allow the batch to override the rendering stereo settings // for things like full framebuffer copy operations (deferred lighting passes) @@ -287,16 +286,24 @@ void GLBackend::render(const Batch& batch) { } { - //PROFILE_RANGE(render_gpu_gl, "Transfer"); - ANDROID_PROFILE(render, "Transfer", 0xff0000ff, 1) + PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); renderPassTransfer(batch); } +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + if (_stereo.isStereo()) { + glEnable(GL_CLIP_DISTANCE0_EXT); + } +#endif { - //PROFILE_RANGE(render_gpu_gl, _stereo._enable ? "Render Stereo" : "Render"); - ANDROID_PROFILE(render, "RenderPassDraw", 0xff00ddff, 1) + PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render"); renderPassDraw(batch); } +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + if (_stereo.isStereo()) { + glDisable(GL_CLIP_DISTANCE0_EXT); + } +#endif // Restore the saved stereo state for the next batch _stereo._enable = savedStereo; @@ -304,15 +311,15 @@ void GLBackend::render(const Batch& batch) { void GLBackend::syncCache() { + PROFILE_RANGE(render_gpu_gl_detail, __FUNCTION__); + syncTransformStateCache(); syncPipelineStateCache(); syncInputStateCache(); syncOutputStateCache(); - - //glEnable(GL_LINE_SMOOTH); - qDebug() << "TODO: GLBackend.cpp:syncCache GL_LINE_SMOOTH"; } +#ifdef GPU_STEREO_DRAWCALL_DOUBLED void GLBackend::setupStereoSide(int side) { ivec4 vp = _transform._viewport; vp.z /= 2; @@ -320,19 +327,35 @@ void GLBackend::setupStereoSide(int side) { #ifdef GPU_STEREO_CAMERA_BUFFER #ifdef GPU_STEREO_DRAWCALL_DOUBLED - //glVertexAttribI1i(14, side); - glVertexAttribI4i(14, side, 0, 0, 0); - + glVertexAttribI1i(14, side); #endif #else _transform.bindCurrentCamera(side); #endif } +#else +#endif void GLBackend::do_resetStages(const Batch& batch, size_t paramOffset) { resetStages(); } +void GLBackend::do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) { + _transform._viewCorrectionEnabled = false; +} + +void GLBackend::do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) { + _transform._viewCorrectionEnabled = true; +} + +void GLBackend::do_disableContextStereo(const Batch& batch, size_t paramOffset) { + +} + +void GLBackend::do_restoreContextStereo(const Batch& batch, size_t paramOffset) { + +} + void GLBackend::do_runLambda(const Batch& batch, size_t paramOffset) { std::function f = batch._lambdas.get(batch._params[paramOffset]._uint); f(); @@ -361,18 +384,22 @@ void GLBackend::resetStages() { void GLBackend::do_pushProfileRange(const Batch& batch, size_t paramOffset) { - auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); - profileRanges.push_back(name); + if (trace_render_gpu_gl_detail().isDebugEnabled()) { + auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); + profileRanges.push_back(name); #if defined(NSIGHT_FOUND) - nvtxRangePush(name.c_str()); + nvtxRangePush(name.c_str()); #endif + } } void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) { - profileRanges.pop_back(); + if (trace_render_gpu_gl_detail().isDebugEnabled()) { + profileRanges.pop_back(); #if defined(NSIGHT_FOUND) - nvtxRangePop(); + nvtxRangePop(); #endif + } } // TODO: As long as we have gl calls explicitely issued from interface @@ -380,8 +407,11 @@ void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) { // term strategy is to get rid of any GL calls in favor of the HIFI GPU API // As long as we don;t use several versions of shaders we can avoid this more complex code path -// #define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc, isStereo()); +#ifdef GPU_STEREO_CAMERA_BUFFER +#define GET_UNIFORM_LOCATION(shaderUniformLoc) ((_pipeline._programShader) ? _pipeline._programShader->getUniformLocation(shaderUniformLoc, (GLShader::Version) isStereo()) : -1) +#else #define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc +#endif void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { @@ -391,7 +421,7 @@ void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { } updatePipeline(); - glUniform1f( + glUniform1i( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), batch._params[paramOffset + 0]._int); (void)CHECK_GL_ERROR(); @@ -545,6 +575,10 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { if (_input._colorAttribute != newColor) { _input._colorAttribute = newColor; glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r); + // Color has been changed and is not white. To prevent colors from bleeding + // between different objects, we need to set the _hadColorAttribute flag + // as if a previous render call had potential colors + _input._hadColorAttribute = (newColor != glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); } (void)CHECK_GL_ERROR(); } @@ -590,6 +624,7 @@ void GLBackend::queueLambda(const std::function lambda) const { } void GLBackend::recycle() const { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__) { std::list> lamdbasTrash; { @@ -709,9 +744,9 @@ void GLBackend::recycle() const { } } -#ifndef THREADED_TEXTURE_TRANSFER - gl::GLTexture::_textureTransferHelper->process(); -#endif + GLVariableAllocationSupport::manageMemory(); + GLVariableAllocationSupport::_frameTexturesCreated = 0; + Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackend.h b/libraries/gpu-gles/src/gpu/gl/GLBackend.h index f8f307bc17..ea06b6b672 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gles/src/gpu/gl/GLBackend.h @@ -29,9 +29,16 @@ // Different versions for the stereo drawcall // Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only +#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE //#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER //#define GPU_STEREO_TECHNIQUE_INSTANCED + +// Let these be configured by the one define picked above +#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE +#define GPU_STEREO_DRAWCALL_DOUBLED +#endif + #ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER #define GPU_STEREO_DRAWCALL_DOUBLED #define GPU_STEREO_CAMERA_BUFFER @@ -42,259 +49,265 @@ #define GPU_STEREO_CAMERA_BUFFER #endif -//#define ANDROID_INTENSIVE_INSTRUMENTATION 1 - -#ifdef ANDROID_INTENSIVE_INSTRUMENTATION -#define ANDROID_PROFILE_COMMAND(category, commandIndex, argbColor, payload, ...) PROFILE_RANGE_EX(category, commandNames[commandIndex], argbColor, payload, ##__VA_ARGS__); -#define ANDROID_PROFILE(category, name, argbColor, payload, ...) PROFILE_RANGE_EX(category, name, argbColor, payload, ##__VA_ARGS__); -#else -#define ANDROID_PROFILE_COMMAND(category, commandIndex, argbColor, payload, ...) -#define ANDROID_PROFILE(category, name, argbColor, payload, ...) -#endif namespace gpu { namespace gl { - class GLBackend : public Backend, public std::enable_shared_from_this { - // Context Backend static interface required - friend class gpu::Context; - static void init(); - static BackendPointer createBackend(); +class GLBackend : public Backend, public std::enable_shared_from_this { + // Context Backend static interface required + friend class gpu::Context; + static void init(); + static BackendPointer createBackend(); - protected: - explicit GLBackend(bool syncCache); - GLBackend(); - public: - static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings = Shader::BindingSet()); +protected: + explicit GLBackend(bool syncCache); + GLBackend(); +public: + static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings = Shader::BindingSet()); - ~GLBackend(); + virtual ~GLBackend(); - void setCameraCorrection(const Mat4& correction); - void render(const Batch& batch) final override; + void setCameraCorrection(const Mat4& correction); + void render(const Batch& batch) final override; - // This call synchronize the Full Backend cache with the current GLState - // THis is only intended to be used when mixing raw gl calls with the gpu api usage in order to sync - // the gpu::Backend state with the true gl state which has probably been messed up by these ugly naked gl calls - // Let's try to avoid to do that as much as possible! - void syncCache() final override; + // This call synchronize the Full Backend cache with the current GLState + // THis is only intended to be used when mixing raw gl calls with the gpu api usage in order to sync + // the gpu::Backend state with the true gl state which has probably been messed up by these ugly naked gl calls + // Let's try to avoid to do that as much as possible! + void syncCache() final override; - // This is the ugly "download the pixels to sysmem for taking a snapshot" - // Just avoid using it, it's ugly and will break performances - virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, - const Vec4i& region, QImage& destImage) final override; + // This is the ugly "download the pixels to sysmem for taking a snapshot" + // Just avoid using it, it's ugly and will break performances + virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, + const Vec4i& region, QImage& destImage) final override; - // this is the maximum numeber of available input buffers - size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } + // this is the maximum numeber of available input buffers + size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); } - // this is the maximum per shader stage on the low end apple - // TODO make it platform dependant at init time - static const int MAX_NUM_UNIFORM_BUFFERS = 12; - size_t getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; } + // this is the maximum per shader stage on the low end apple + // TODO make it platform dependant at init time + static const int MAX_NUM_UNIFORM_BUFFERS = 12; + size_t getMaxNumUniformBuffers() const { return MAX_NUM_UNIFORM_BUFFERS; } - // this is the maximum per shader stage on the low end apple - // TODO make it platform dependant at init time - static const int MAX_NUM_RESOURCE_TEXTURES = 16; - size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } + // this is the maximum per shader stage on the low end apple + // TODO make it platform dependant at init time + static const int MAX_NUM_RESOURCE_BUFFERS = 16; + size_t getMaxNumResourceBuffers() const { return MAX_NUM_RESOURCE_BUFFERS; } + static const int MAX_NUM_RESOURCE_TEXTURES = 16; + size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } - // Draw Stage - virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; - virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; - virtual void do_drawInstanced(const Batch& batch, size_t paramOffset) = 0; - virtual void do_drawIndexedInstanced(const Batch& batch, size_t paramOffset) = 0; - virtual void do_multiDrawIndirect(const Batch& batch, size_t paramOffset) = 0; - virtual void do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOffset) = 0; + // Draw Stage + virtual void do_draw(const Batch& batch, size_t paramOffset) = 0; + virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0; + virtual void do_drawInstanced(const Batch& batch, size_t paramOffset) = 0; + virtual void do_drawIndexedInstanced(const Batch& batch, size_t paramOffset) = 0; + virtual void do_multiDrawIndirect(const Batch& batch, size_t paramOffset) = 0; + virtual void do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOffset) = 0; - // Input Stage - virtual void do_setInputFormat(const Batch& batch, size_t paramOffset) final; - virtual void do_setInputBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_setIndexBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_setIndirectBuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_generateTextureMips(const Batch& batch, size_t paramOffset) final; + // Input Stage + virtual void do_setInputFormat(const Batch& batch, size_t paramOffset) final; + virtual void do_setInputBuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_setIndexBuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_setIndirectBuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_generateTextureMips(const Batch& batch, size_t paramOffset) final; - // Transform Stage - virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setViewTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setProjectionTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setViewportTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setDepthRangeTransform(const Batch& batch, size_t paramOffset) final; + // Transform Stage + virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_setViewTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_setViewportTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_setDepthRangeTransform(const Batch& batch, size_t paramOffset) final; - // Uniform Stage - virtual void do_setUniformBuffer(const Batch& batch, size_t paramOffset) final; + // Uniform Stage + virtual void do_setUniformBuffer(const Batch& batch, size_t paramOffset) final; - // Resource Stage - virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final; + // Resource Stage + virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final; - // Pipeline Stage - virtual void do_setPipeline(const Batch& batch, size_t paramOffset) final; + // Pipeline Stage + virtual void do_setPipeline(const Batch& batch, size_t paramOffset) final; - // Output stage - virtual void do_setFramebuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_clearFramebuffer(const Batch& batch, size_t paramOffset) final; - virtual void do_blit(const Batch& batch, size_t paramOffset) = 0; + // Output stage + virtual void do_setFramebuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_clearFramebuffer(const Batch& batch, size_t paramOffset) final; + virtual void do_blit(const Batch& batch, size_t paramOffset) = 0; - // Query section - virtual void do_beginQuery(const Batch& batch, size_t paramOffset) final; - virtual void do_endQuery(const Batch& batch, size_t paramOffset) final; - virtual void do_getQuery(const Batch& batch, size_t paramOffset) final; + // Query section + virtual void do_beginQuery(const Batch& batch, size_t paramOffset) final; + virtual void do_endQuery(const Batch& batch, size_t paramOffset) final; + virtual void do_getQuery(const Batch& batch, size_t paramOffset) final; - // Reset stages - virtual void do_resetStages(const Batch& batch, size_t paramOffset) final; + // Reset stages + virtual void do_resetStages(const Batch& batch, size_t paramOffset) final; - virtual void do_runLambda(const Batch& batch, size_t paramOffset) final; + + virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final; + virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final; - virtual void do_startNamedCall(const Batch& batch, size_t paramOffset) final; - virtual void do_stopNamedCall(const Batch& batch, size_t paramOffset) final; + virtual void do_disableContextStereo(const Batch& batch, size_t paramOffset) final; + virtual void do_restoreContextStereo(const Batch& batch, size_t paramOffset) final; - static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; - // The drawcall Info attribute channel is reserved and is the upper bound for the number of availables Input buffers - static const int MAX_NUM_INPUT_BUFFERS = Stream::DRAW_CALL_INFO; + virtual void do_runLambda(const Batch& batch, size_t paramOffset) final; - virtual void do_pushProfileRange(const Batch& batch, size_t paramOffset) final; - virtual void do_popProfileRange(const Batch& batch, size_t paramOffset) final; + virtual void do_startNamedCall(const Batch& batch, size_t paramOffset) final; + virtual void do_stopNamedCall(const Batch& batch, size_t paramOffset) final; - // TODO: As long as we have gl calls explicitely issued from interface - // code, we need to be able to record and batch these calls. THe long - // term strategy is to get rid of any GL calls in favor of the HIFI GPU API - virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform2f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform3f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform4f(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform3fv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform4fv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniform4iv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniformMatrix3fv(const Batch& batch, size_t paramOffset) final; - virtual void do_glUniformMatrix4fv(const Batch& batch, size_t paramOffset) final; + static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; + // The drawcall Info attribute channel is reserved and is the upper bound for the number of availables Input buffers + static const int MAX_NUM_INPUT_BUFFERS = Stream::DRAW_CALL_INFO; - virtual void do_glColor4f(const Batch& batch, size_t paramOffset) final; + virtual void do_pushProfileRange(const Batch& batch, size_t paramOffset) final; + virtual void do_popProfileRange(const Batch& batch, size_t paramOffset) final; - // The State setters called by the GLState::Commands when a new state is assigned - virtual void do_setStateFillMode(int32 mode) final; - virtual void do_setStateCullMode(int32 mode) final; - virtual void do_setStateFrontFaceClockwise(bool isClockwise) final; - virtual void do_setStateDepthClampEnable(bool enable) final; - virtual void do_setStateScissorEnable(bool enable) final; - virtual void do_setStateMultisampleEnable(bool enable) final; - virtual void do_setStateAntialiasedLineEnable(bool enable) final; - virtual void do_setStateDepthBias(Vec2 bias) final; - virtual void do_setStateDepthTest(State::DepthTest test) final; - virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; - virtual void do_setStateAlphaToCoverageEnable(bool enable) final; - virtual void do_setStateSampleMask(uint32 mask) final; - virtual void do_setStateBlend(State::BlendFunction blendFunction) final; - virtual void do_setStateColorWriteMask(uint32 mask) final; - virtual void do_setStateBlendFactor(const Batch& batch, size_t paramOffset) final; - virtual void do_setStateScissorRect(const Batch& batch, size_t paramOffset) final; + // TODO: As long as we have gl calls explicitely issued from interface + // code, we need to be able to record and batch these calls. THe long + // term strategy is to get rid of any GL calls in favor of the HIFI GPU API + virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform2f(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform3f(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4f(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform3fv(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4fv(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4iv(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniformMatrix3fv(const Batch& batch, size_t paramOffset) final; + virtual void do_glUniformMatrix4fv(const Batch& batch, size_t paramOffset) final; - virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; - virtual GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) = 0; - virtual GLuint getBufferID(const Buffer& buffer) = 0; - virtual GLuint getQueryID(const QueryPointer& query) = 0; - virtual bool isTextureReady(const TexturePointer& texture); + virtual void do_glColor4f(const Batch& batch, size_t paramOffset) final; - virtual void releaseBuffer(GLuint id, Size size) const; - virtual void releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const; - virtual void releaseTexture(GLuint id, Size size) const; - virtual void releaseFramebuffer(GLuint id) const; - virtual void releaseShader(GLuint id) const; - virtual void releaseProgram(GLuint id) const; - virtual void releaseQuery(GLuint id) const; - virtual void queueLambda(const std::function lambda) const; + // The State setters called by the GLState::Commands when a new state is assigned + virtual void do_setStateFillMode(int32 mode) final; + virtual void do_setStateCullMode(int32 mode) final; + virtual void do_setStateFrontFaceClockwise(bool isClockwise) final; + virtual void do_setStateDepthClampEnable(bool enable) final; + virtual void do_setStateScissorEnable(bool enable) final; + virtual void do_setStateMultisampleEnable(bool enable) final; + virtual void do_setStateAntialiasedLineEnable(bool enable) final; + virtual void do_setStateDepthBias(Vec2 bias) final; + virtual void do_setStateDepthTest(State::DepthTest test) final; + virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; + virtual void do_setStateAlphaToCoverageEnable(bool enable) final; + virtual void do_setStateSampleMask(uint32 mask) final; + virtual void do_setStateBlend(State::BlendFunction blendFunction) final; + virtual void do_setStateColorWriteMask(uint32 mask) final; + virtual void do_setStateBlendFactor(const Batch& batch, size_t paramOffset) final; + virtual void do_setStateScissorRect(const Batch& batch, size_t paramOffset) final; - bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } + virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; + virtual GLuint getTextureID(const TexturePointer& texture) final; + virtual GLuint getBufferID(const Buffer& buffer) = 0; + virtual GLuint getQueryID(const QueryPointer& query) = 0; - protected: + virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; + virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; + virtual GLTexture* syncGPUObject(const TexturePointer& texture); + virtual GLQuery* syncGPUObject(const Query& query) = 0; + //virtual bool isTextureReady(const TexturePointer& texture); - void recycle() const override; - virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; - virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; - virtual GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) = 0; - virtual GLQuery* syncGPUObject(const Query& query) = 0; + virtual void releaseBuffer(GLuint id, Size size) const; + virtual void releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const; + virtual void releaseTexture(GLuint id, Size size) const; + virtual void releaseFramebuffer(GLuint id) const; + virtual void releaseShader(GLuint id) const; + virtual void releaseProgram(GLuint id) const; + virtual void releaseQuery(GLuint id) const; + virtual void queueLambda(const std::function lambda) const; - static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass { false }; - int32_t _uboAlignment { 0 }; - int _currentDraw { -1 }; + bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); } - std::list profileRanges; - mutable Mutex _trashMutex; - mutable std::list> _buffersTrash; - mutable std::list> _texturesTrash; - mutable std::list> _externalTexturesTrash; - mutable std::list _framebuffersTrash; - mutable std::list _shadersTrash; - mutable std::list _programsTrash; - mutable std::list _queriesTrash; - mutable std::list> _lambdaQueue; +protected: - void renderPassTransfer(const Batch& batch); - void renderPassDraw(const Batch& batch); - void setupStereoSide(int side); + void recycle() const override; - virtual void initInput() final; - virtual void killInput() final; - virtual void syncInputStateCache() final; - virtual void resetInputStage(); - virtual void updateInput(); + static const size_t INVALID_OFFSET = (size_t)-1; + bool _inRenderTransferPass { false }; + int32_t _uboAlignment { 0 }; + int _currentDraw { -1 }; - struct InputStageState { - bool _invalidFormat { true }; - Stream::FormatPointer _format; - std::string _formatKey; + std::list profileRanges; + mutable Mutex _trashMutex; + mutable std::list> _buffersTrash; + mutable std::list> _texturesTrash; + mutable std::list> _externalTexturesTrash; + mutable std::list _framebuffersTrash; + mutable std::list _shadersTrash; + mutable std::list _programsTrash; + mutable std::list _queriesTrash; + mutable std::list> _lambdaQueue; - typedef std::bitset ActivationCache; - ActivationCache _attributeActivation { 0 }; + void renderPassTransfer(const Batch& batch); + void renderPassDraw(const Batch& batch); - typedef std::bitset BuffersState; +#ifdef GPU_STEREO_DRAWCALL_DOUBLED + void setupStereoSide(int side); +#endif - BuffersState _invalidBuffers{ 0 }; - BuffersState _attribBindingBuffers{ 0 }; + virtual void initInput() final; + virtual void killInput() final; + virtual void syncInputStateCache() final; + virtual void resetInputStage(); + virtual void updateInput() = 0; - Buffers _buffers; - Offsets _bufferOffsets; - Offsets _bufferStrides; - std::vector _bufferVBOs; + struct InputStageState { + bool _invalidFormat { true }; + bool _hadColorAttribute{ true }; + Stream::FormatPointer _format; + std::string _formatKey; - glm::vec4 _colorAttribute{ 0.0f }; + typedef std::bitset ActivationCache; + ActivationCache _attributeActivation { 0 }; - BufferPointer _indexBuffer; - Offset _indexBufferOffset { 0 }; - Type _indexBufferType { UINT32 }; + typedef std::bitset BuffersState; - BufferPointer _indirectBuffer; - Offset _indirectBufferOffset{ 0 }; - Offset _indirectBufferStride{ 0 }; + BuffersState _invalidBuffers{ 0 }; + BuffersState _attribBindingBuffers{ 0 }; - GLuint _defaultVAO { 0 }; + Buffers _buffers; + Offsets _bufferOffsets; + Offsets _bufferStrides; + std::vector _bufferVBOs; - InputStageState() : - _invalidFormat(true), - _format(0), - _formatKey(), - _attributeActivation(0), - _buffers(_invalidBuffers.size(), BufferPointer(0)), - _bufferOffsets(_invalidBuffers.size(), 0), - _bufferStrides(_invalidBuffers.size(), 0), - _bufferVBOs(_invalidBuffers.size(), 0) {} - } _input; + glm::vec4 _colorAttribute{ 0.0f }; - virtual void initTransform() = 0; - void killTransform(); - // Synchronize the state cache of this Backend with the actual real state of the GL Context - void syncTransformStateCache(); - void updateTransform(const Batch& batch); - void resetTransformStage(); + BufferPointer _indexBuffer; + Offset _indexBufferOffset { 0 }; + Type _indexBufferType { UINT32 }; + + BufferPointer _indirectBuffer; + Offset _indirectBufferOffset{ 0 }; + Offset _indirectBufferStride{ 0 }; - // Allows for correction of the camera pose to account for changes - // between the time when a was recorded and the time(s) when it is - // executed - struct CameraCorrection { - Mat4 correction; - Mat4 correctionInverse; - }; + GLuint _defaultVAO { 0 }; - struct TransformStageState { + InputStageState() : + _invalidFormat(true), + _format(0), + _formatKey(), + _attributeActivation(0), + _buffers(_invalidBuffers.size(), BufferPointer(0)), + _bufferOffsets(_invalidBuffers.size(), 0), + _bufferStrides(_invalidBuffers.size(), 0), + _bufferVBOs(_invalidBuffers.size(), 0) {} + } _input; + + virtual void initTransform() = 0; + void killTransform(); + // Synchronize the state cache of this Backend with the actual real state of the GL Context + void syncTransformStateCache(); + virtual void updateTransform(const Batch& batch) = 0; + virtual void resetTransformStage(); + + // Allows for correction of the camera pose to account for changes + // between the time when a was recorded and the time(s) when it is + // executed + struct CameraCorrection { + Mat4 correction; + Mat4 correctionInverse; + }; + + struct TransformStageState { #ifdef GPU_STEREO_CAMERA_BUFFER - struct Cameras { + struct Cameras { TransformCamera _cams[2]; Cameras() {}; @@ -304,124 +317,158 @@ namespace gpu { namespace gl { using CameraBufferElement = Cameras; #else - using CameraBufferElement = TransformCamera; + using CameraBufferElement = TransformCamera; #endif - using TransformCameras = std::vector; + using TransformCameras = std::vector; - TransformCamera _camera; - TransformCameras _cameras; + TransformCamera _camera; + TransformCameras _cameras; - mutable std::map _drawCallInfoOffsets; + mutable std::map _drawCallInfoOffsets; - GLuint _objectBuffer { 0 }; - GLuint _cameraBuffer { 0 }; - GLuint _drawCallInfoBuffer { 0 }; - GLuint _objectBufferTexture { 0 }; - size_t _cameraUboSize { 0 }; - bool _viewIsCamera{ false }; - bool _skybox { false }; - Transform _view; - CameraCorrection _correction; + GLuint _objectBuffer { 0 }; + GLuint _cameraBuffer { 0 }; + GLuint _drawCallInfoBuffer { 0 }; + GLuint _objectBufferTexture { 0 }; + size_t _cameraUboSize { 0 }; + bool _viewIsCamera{ false }; + bool _skybox { false }; + Transform _view; + CameraCorrection _correction; + bool _viewCorrectionEnabled{ true }; - Mat4 _projection; - Vec4i _viewport { 0, 0, 1, 1 }; - Vec2 _depthRange { 0.0f, 1.0f }; - bool _invalidView { false }; - bool _invalidProj { false }; - bool _invalidViewport { false }; - bool _enabledDrawcallInfoBuffer{ false }; + Mat4 _projection; + Vec4i _viewport { 0, 0, 1, 1 }; + Vec2 _depthRange { 0.0f, 1.0f }; + bool _invalidView { false }; + bool _invalidProj { false }; + bool _invalidViewport { false }; - using Pair = std::pair; - using List = std::list; - List _cameraOffsets; - mutable List::const_iterator _camerasItr; - mutable size_t _currentCameraOffset{ INVALID_OFFSET }; + bool _enabledDrawcallInfoBuffer{ false }; - void preUpdate(size_t commandIndex, const StereoState& stereo); - void update(size_t commandIndex, const StereoState& stereo) const; - void bindCurrentCamera(int stereoSide) const; - } _transform; + using Pair = std::pair; + using List = std::list; + List _cameraOffsets; + mutable List::const_iterator _camerasItr; + mutable size_t _currentCameraOffset{ INVALID_OFFSET }; - virtual void transferTransformState(const Batch& batch) const = 0; + void preUpdate(size_t commandIndex, const StereoState& stereo); + void update(size_t commandIndex, const StereoState& stereo) const; + void bindCurrentCamera(int stereoSide) const; + } _transform; - struct UniformStageState { - std::array _buffers; - //Buffers _buffers { }; - } _uniform; + virtual void transferTransformState(const Batch& batch) const = 0; - void releaseUniformBuffer(uint32_t slot); - void resetUniformStage(); + struct UniformStageState { + std::array _buffers; + //Buffers _buffers { }; + } _uniform; - // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s - void releaseResourceTexture(uint32_t slot); + void releaseUniformBuffer(uint32_t slot); + void resetUniformStage(); - void resetResourceStage(); + // update resource cache and do the gl bind/unbind call with the current gpu::Buffer cached at slot s + // This is using different gl object depending on the gl version + virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0; + virtual void releaseResourceBuffer(uint32_t slot) = 0; - struct ResourceStageState { - std::array _textures; - //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; - int findEmptyTextureSlot() const; - } _resource; + // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s + void releaseResourceTexture(uint32_t slot); - size_t _commandIndex{ 0 }; + void resetResourceStage(); - // Standard update pipeline check that the current Program and current State or good to go for a - void updatePipeline(); - // Force to reset all the state fields indicated by the 'toBeReset" signature - void resetPipelineState(State::Signature toBeReset); - // Synchronize the state cache of this Backend with the actual real state of the GL Context - void syncPipelineStateCache(); - void resetPipelineStage(); + struct ResourceStageState { + std::array _buffers; + std::array _textures; + //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; + int findEmptyTextureSlot() const; + } _resource; - struct PipelineStageState { - PipelinePointer _pipeline; + size_t _commandIndex{ 0 }; - GLuint _program { 0 }; - GLint _cameraCorrectionLocation { -1 }; - GLShader* _programShader { nullptr }; - bool _invalidProgram { false }; + // Standard update pipeline check that the current Program and current State or good to go for a + void updatePipeline(); + // Force to reset all the state fields indicated by the 'toBeReset" signature + void resetPipelineState(State::Signature toBeReset); + // Synchronize the state cache of this Backend with the actual real state of the GL Context + void syncPipelineStateCache(); + void resetPipelineStage(); - BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; + struct PipelineStageState { + PipelinePointer _pipeline; - State::Data _stateCache{ State::DEFAULT }; - State::Signature _stateSignatureCache { 0 }; + GLuint _program { 0 }; + GLint _cameraCorrectionLocation { -1 }; + GLShader* _programShader { nullptr }; + bool _invalidProgram { false }; - GLState* _state { nullptr }; - bool _invalidState { false }; + BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; + BufferView _cameraCorrectionBufferIdentity { gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr )) }; - PipelineStageState() { - _cameraCorrectionBuffer.edit() = CameraCorrection(); - } - } _pipeline; + State::Data _stateCache{ State::DEFAULT }; + State::Signature _stateSignatureCache { 0 }; - // Synchronize the state cache of this Backend with the actual real state of the GL Context - void syncOutputStateCache(); - void resetOutputStage(); + GLState* _state { nullptr }; + bool _invalidState { false }; - struct OutputStageState { - FramebufferPointer _framebuffer { nullptr }; - GLuint _drawFBO { 0 }; - } _output; + PipelineStageState() { + _cameraCorrectionBuffer.edit() = CameraCorrection(); + _cameraCorrectionBufferIdentity.edit() = CameraCorrection(); + _cameraCorrectionBufferIdentity._buffer->flush(); + } + } _pipeline; - void resetQueryStage(); - struct QueryStageState { - uint32_t _rangeQueryDepth { 0 }; - } _queryStage; + // Backend dependant compilation of the shader + virtual GLShader* compileBackendProgram(const Shader& program); + virtual GLShader* compileBackendShader(const Shader& shader); + virtual std::string getBackendShaderHeader() const; + virtual void makeProgramBindings(ShaderObject& shaderObject); + class ElementResource { + public: + gpu::Element _element; + uint16 _resource; + ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} + }; + ElementResource getFormatFromGLUniform(GLenum gltype); + static const GLint UNUSED_SLOT {-1}; + static bool isUnusedSlot(GLint binding) { return (binding == UNUSED_SLOT); } + virtual int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers); + virtual int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers); + virtual int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& resourceBuffers) = 0; + virtual int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs); + virtual int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs); - void resetStages(); - struct TextureManagementStageState { - bool _sparseCapable { false }; - } _textureManagement; - virtual void initTextureManagementStage() {} + // Synchronize the state cache of this Backend with the actual real state of the GL Context + void syncOutputStateCache(); + void resetOutputStage(); + + struct OutputStageState { + FramebufferPointer _framebuffer { nullptr }; + GLuint _drawFBO { 0 }; + } _output; - typedef void (GLBackend::*CommandCall)(const Batch&, size_t); - static CommandCall _commandCalls[Batch::NUM_COMMANDS]; - friend class GLState; - friend class GLTexture; - }; + void resetQueryStage(); + struct QueryStageState { + uint32_t _rangeQueryDepth { 0 }; + } _queryStage; - } } + void resetStages(); + + struct TextureManagementStageState { + bool _sparseCapable { false }; + } _textureManagement; + virtual void initTextureManagementStage() {} + + typedef void (GLBackend::*CommandCall)(const Batch&, size_t); + static CommandCall _commandCalls[Batch::NUM_COMMANDS]; + friend class GLState; + friend class GLTexture; + friend class GLShader; +}; + +} } #endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendInput.cpp index 057682584d..4145eb6061 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendInput.cpp @@ -73,13 +73,11 @@ void GLBackend::initInput() { if(!_input._defaultVAO) { glGenVertexArrays(1, &_input._defaultVAO); } - qDebug() << "glBindVertexArray(" << _input._defaultVAO << ")"; glBindVertexArray(_input._defaultVAO); (void) CHECK_GL_ERROR(); } void GLBackend::killInput() { - qDebug() << "glBindVertexArray(0)"; glBindVertexArray(0); if(_input._defaultVAO) { glDeleteVertexArrays(1, &_input._defaultVAO); @@ -94,7 +92,6 @@ void GLBackend::syncInputStateCache() { _input._attributeActivation[i] = active; } //_input._defaultVAO - qDebug() << "glBindVertexArray("<<_input._defaultVAO<< ")"; glBindVertexArray(_input._defaultVAO); } @@ -103,7 +100,6 @@ void GLBackend::resetInputStage() { _input._indexBufferType = UINT32; _input._indexBufferOffset = 0; _input._indexBuffer.reset(); - //qDebug() << "GLBackend::resetInputStage glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);"; glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); (void) CHECK_GL_ERROR(); @@ -159,49 +155,83 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { (void)CHECK_GL_ERROR(); } - -// Core 41 doesn't expose the features to really separate the vertex format from the vertex buffers binding -// Core 43 does :) -// FIXME crashing problem with glVertexBindingDivisor / glVertexAttribFormat -// Once resolved, break this up into the GL 4.1 and 4.5 backends -#if 1 || (GPU_INPUT_PROFILE == GPU_CORE_41) -#define NO_SUPPORT_VERTEX_ATTRIB_FORMAT -#else -#define SUPPORT_VERTEX_ATTRIB_FORMAT -#endif - void GLBackend::updateInput() { -#if defined(SUPPORT_VERTEX_ATTRIB_FORMAT) if (_input._invalidFormat) { - InputStageState::ActivationCache newActivation; // Assign the vertex format required if (_input._format) { - for (auto& it : _input._format->getAttributes()) { - const Stream::Attribute& attrib = (it).second; + bool hasColorAttribute{ false }; - GLuint slot = attrib._slot; - GLuint count = attrib._element.getLocationScalarCount(); - uint8_t locationCount = attrib._element.getLocationCount(); - GLenum type = _elementTypeToGL41Type[attrib._element.getType()]; - GLuint offset = attrib._offset;; - GLboolean isNormalized = attrib._element.isNormalized(); + _input._attribBindingBuffers.reset(); - GLenum perLocationSize = attrib._element.getLocationSize(); + const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); + auto& inputChannels = _input._format->getChannels(); + for (auto& channelIt : inputChannels) { + auto bufferChannelNum = (channelIt).first; + const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; + _input._attribBindingBuffers.set(bufferChannelNum); - for (size_t locNum = 0; locNum < locationCount; ++locNum) { - newActivation.set(slot + locNum); - glVertexAttribFormat(slot + locNum, count, type, isNormalized, offset + locNum * perLocationSize); - glVertexAttribBinding(slot + locNum, attrib._channel); + GLuint frequency = 0; + for (unsigned int i = 0; i < channel._slots.size(); i++) { + const Stream::Attribute& attrib = attributes.at(channel._slots[i]); + + GLuint slot = attrib._slot; + GLuint count = attrib._element.getLocationScalarCount(); + uint8_t locationCount = attrib._element.getLocationCount(); + GLenum type = gl::ELEMENT_TYPE_TO_GL[attrib._element.getType()]; + + GLuint offset = (GLuint)attrib._offset;; + GLboolean isNormalized = attrib._element.isNormalized(); + + GLenum perLocationSize = attrib._element.getLocationSize(); + + hasColorAttribute = hasColorAttribute || (slot == Stream::COLOR); + + for (GLuint locNum = 0; locNum < locationCount; ++locNum) { + GLuint attriNum = (GLuint)(slot + locNum); + newActivation.set(attriNum); + if (!_input._attributeActivation[attriNum]) { + _input._attributeActivation.set(attriNum); + glEnableVertexAttribArray(attriNum); + } + if (attrib._element.isInteger()) { + glVertexAttribIFormat(attriNum, count, type, offset + locNum * perLocationSize); + } else { + glVertexAttribFormat(attriNum, count, type, isNormalized, offset + locNum * perLocationSize); + } + glVertexAttribBinding(attriNum, attrib._channel); + } + + if (i == 0) { + frequency = attrib._frequency; + } else { + assert(frequency == attrib._frequency); + } + + + (void)CHECK_GL_ERROR(); } - glVertexBindingDivisor(attrib._channel, attrib._frequency); +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glVertexBindingDivisor(bufferChannelNum, frequency * (isStereo() ? 2 : 1)); +#else + glVertexBindingDivisor(bufferChannelNum, frequency); +#endif } - (void)CHECK_GL_ERROR(); + + if (_input._hadColorAttribute && !hasColorAttribute) { + // The previous input stage had a color attribute but this one doesn't so reset + // color to pure white. + const auto white = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + glVertexAttrib4fv(Stream::COLOR, &white.r); + _input._colorAttribute = white; + } + _input._hadColorAttribute = hasColorAttribute; } // Manage Activation what was and what is expected now - for (size_t i = 0; i < newActivation.size(); i++) { + // This should only disable VertexAttribs since the one needed by the vertex format (if it exists) have been enabled above + for (GLuint i = 0; i < (GLuint)newActivation.size(); i++) { bool newState = newActivation[i]; if (newState != _input._attributeActivation[i]) { if (newState) { @@ -219,120 +249,18 @@ void GLBackend::updateInput() { } if (_input._invalidBuffers.any()) { - int numBuffers = _input._buffers.size(); - auto buffer = _input._buffers.data(); auto vbo = _input._bufferVBOs.data(); auto offset = _input._bufferOffsets.data(); auto stride = _input._bufferStrides.data(); - for (int bufferNum = 0; bufferNum < numBuffers; bufferNum++) { - if (_input._invalidBuffers.test(bufferNum)) { - glBindVertexBuffer(bufferNum, (*vbo), (*offset), (*stride)); + for (GLuint buffer = 0; buffer < _input._buffers.size(); buffer++, vbo++, offset++, stride++) { + if (_input._invalidBuffers.test(buffer)) { + glBindVertexBuffer(buffer, (*vbo), (*offset), (GLsizei)(*stride)); } - buffer++; - vbo++; - offset++; - stride++; } + _input._invalidBuffers.reset(); (void)CHECK_GL_ERROR(); } -#else - if (_input._invalidFormat || _input._invalidBuffers.any()) { - - if (_input._invalidFormat) { - InputStageState::ActivationCache newActivation; - - _stats._ISNumFormatChanges++; - - // Check expected activation - if (_input._format) { - for (auto& it : _input._format->getAttributes()) { - const Stream::Attribute& attrib = (it).second; - uint8_t locationCount = attrib._element.getLocationCount(); - for (int i = 0; i < locationCount; ++i) { - newActivation.set(attrib._slot + i); - } - } - } - - // Manage Activation what was and what is expected now - for (unsigned int i = 0; i < newActivation.size(); i++) { - bool newState = newActivation[i]; - if (newState != _input._attributeActivation[i]) { - - if (newState) { - glEnableVertexAttribArray(i); - } else { - glDisableVertexAttribArray(i); - } - (void)CHECK_GL_ERROR(); - - _input._attributeActivation.flip(i); - } - } - } - - // now we need to bind the buffers and assign the attrib pointers - if (_input._format) { - const Buffers& buffers = _input._buffers; - const Offsets& offsets = _input._bufferOffsets; - const Offsets& strides = _input._bufferStrides; - - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); - _stats._ISNumInputBufferChanges++; - - GLuint boundVBO = 0; - for (auto& channelIt : inputChannels) { - const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; - if ((channelIt).first < buffers.size()) { - int bufferNum = (channelIt).first; - - if (_input._invalidBuffers.test(bufferNum) || _input._invalidFormat) { - // GLuint vbo = gpu::GL41Backend::getBufferID((*buffers[bufferNum])); - GLuint vbo = _input._bufferVBOs[bufferNum]; - if (boundVBO != vbo) { - //qDebug() << "GLBackend::updateInput glBindBuffer(GL_ARRAY_BUFFER, " << vbo <<")"; - glBindBuffer(GL_ARRAY_BUFFER, vbo); - (void)CHECK_GL_ERROR(); - boundVBO = vbo; - } - _input._invalidBuffers[bufferNum] = false; - - for (unsigned int i = 0; i < channel._slots.size(); i++) { - const Stream::Attribute& attrib = attributes.at(channel._slots[i]); - GLuint slot = attrib._slot; - GLuint count = attrib._element.getLocationScalarCount(); - uint8_t locationCount = attrib._element.getLocationCount(); - GLenum type = gl::ELEMENT_TYPE_TO_GL[attrib._element.getType()]; - // GLenum perLocationStride = strides[bufferNum]; - GLenum perLocationStride = attrib._element.getLocationSize(); - GLuint stride = (GLuint)strides[bufferNum]; - GLuint pointer = (GLuint)(attrib._offset + offsets[bufferNum]); - GLboolean isNormalized = attrib._element.isNormalized(); - - for (size_t locNum = 0; locNum < locationCount; ++locNum) { - glVertexAttribPointer(slot + (GLuint)locNum, count, type, isNormalized, stride, - reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); -#ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency * (isStereo() ? 2 : 1)); -#else - glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); -#endif - } - - // TODO: Support properly the IAttrib version - - (void)CHECK_GL_ERROR(); - } - } - } - } - } - // everything format related should be in sync now - _input._invalidFormat = false; - } -#endif } diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp index 6fddb810ee..45c0de8ed7 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendOutput.cpp @@ -48,8 +48,8 @@ void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { - if (_stereo._enable && !_pipeline._stateCache.scissorEnable) { - //qWarning("Clear without scissor in stereo mode"); + if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) { + qWarning("Clear without scissor in stereo mode"); } uint32 masks = batch._params[paramOffset + 7]._uint; @@ -63,17 +63,21 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { int useScissor = batch._params[paramOffset + 0]._int; GLuint glmask = 0; + bool restoreStencilMask = false; + uint8_t cacheStencilMask = 0xFF; if (masks & Framebuffer::BUFFER_STENCIL) { glClearStencil(stencil); glmask |= GL_STENCIL_BUFFER_BIT; - // TODO: we will probably need to also check the write mask of stencil like we do - // for depth buffer, but as would say a famous Fez owner "We'll cross that bridge when we come to it" + cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront(); + if (cacheStencilMask != 0xFF) { + restoreStencilMask = true; + glStencilMask(0xFF); + } } bool restoreDepthMask = false; if (masks & Framebuffer::BUFFER_DEPTH) { glClearDepthf(depth); - glmask |= GL_DEPTH_BUFFER_BIT; bool cacheDepthMask = _pipeline._stateCache.depthTest.getWriteMask(); @@ -122,6 +126,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { glDisable(GL_SCISSOR_TEST); } + // Restore Stencil write mask + if (restoreStencilMask) { + glStencilMask(cacheStencilMask); + } + // Restore write mask meaning turn back off if (restoreDepthMask) { glDepthMask(GL_FALSE); @@ -142,22 +151,19 @@ void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, co auto readFBO = getFramebufferID(srcFramebuffer); if (srcFramebuffer && readFBO) { if ((srcFramebuffer->getWidth() < (region.x + region.z)) || (srcFramebuffer->getHeight() < (region.y + region.w))) { - qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; + qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; return; } } if ((destImage.width() < region.z) || (destImage.height() < region.w)) { - qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; + qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; return; } GLenum format = GL_RGBA; - //GLenum format = GL_BGRA; - qDebug() << "TODO: GLBackendOutput.cpp:do_clearFramebuffer GL_BGRA"; - if (destImage.format() != QImage::Format_ARGB32) { - qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; + qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; return; } diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp index c35966d440..b80be8492f 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendPipeline.cpp @@ -31,7 +31,6 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // null pipeline == reset if (!pipeline) { - qDebug() << " null pipeline"; _pipeline._pipeline.reset(); _pipeline._program = 0; @@ -78,7 +77,12 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { if (_pipeline._invalidProgram) { glUseProgram(_pipeline._program); if (_pipeline._cameraCorrectionLocation != -1) { - auto cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBuffer._buffer); + gl::GLBuffer* cameraCorrectionBuffer = nullptr; + if (_transform._viewCorrectionEnabled) { + cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBuffer._buffer); + } else { + cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBufferIdentity._buffer); + } glBindBufferRange(GL_UNIFORM_BUFFER, _pipeline._cameraCorrectionLocation, cameraCorrectionBuffer->_id, 0, sizeof(CameraCorrection)); } (void) CHECK_GL_ERROR(); @@ -150,6 +154,10 @@ void GLBackend::resetUniformStage() { void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 3]._uint; + if (slot >(GLuint)MAX_NUM_UNIFORM_BUFFERS) { + qCDebug(gpugllogging) << "GLBackend::do_setUniformBuffer: Trying to set a uniform Buffer at slot #" << slot << " which doesn't exist. MaxNumUniformBuffers = " << getMaxNumUniformBuffers(); + return; + } BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; @@ -192,15 +200,48 @@ void GLBackend::releaseResourceTexture(uint32_t slot) { } void GLBackend::resetResourceStage() { + for (uint32_t i = 0; i < _resource._buffers.size(); i++) { + releaseResourceBuffer(i); + } for (uint32_t i = 0; i < _resource._textures.size(); i++) { releaseResourceTexture(i); } } +void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { + GLuint slot = batch._params[paramOffset + 1]._uint; + if (slot >= (GLuint)MAX_NUM_RESOURCE_BUFFERS) { + qCDebug(gpugllogging) << "GLBackend::do_setResourceBuffer: Trying to set a resource Buffer at slot #" << slot << " which doesn't exist. MaxNumResourceBuffers = " << getMaxNumResourceBuffers(); + return; + } + + auto resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint); + + if (!resourceBuffer) { + releaseResourceBuffer(slot); + return; + } + // check cache before thinking + if (_resource._buffers[slot] == resourceBuffer) { + return; + } + + // One more True Buffer bound + _stats._RSNumResourceBufferBounded++; + + // If successful bind then cache it + if (bindResourceBuffer(slot, resourceBuffer)) { + _resource._buffers[slot] = resourceBuffer; + } else { // else clear slot and cache + releaseResourceBuffer(slot); + return; + } +} + void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 1]._uint; if (slot >= (GLuint) MAX_NUM_RESOURCE_TEXTURES) { - // "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" + slot + " which doesn't exist. MaxNumResourceTextures = " + getMaxNumResourceTextures()); + qCDebug(gpugllogging) << "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); return; } @@ -230,7 +271,7 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { _resource._textures[slot] = resourceTexture; - _stats._RSAmountTextureMemoryBounded += object->size(); + _stats._RSAmountTextureMemoryBounded += (int) object->size(); } else { releaseResourceTexture(slot); diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp index e5126bb3df..43c8f8f465 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp @@ -25,6 +25,7 @@ static bool timeElapsed = false; #endif void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { +#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -40,9 +41,11 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth; (void)CHECK_GL_ERROR(); } +#endif } void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { +#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { @@ -65,9 +68,11 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { (void)CHECK_GL_ERROR(); } +#endif } void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { +#if !defined(USE_GLES) auto query = batch._queries.get(batch._params[paramOffset]._uint); if (glGetQueryObjectui64vEXT == NULL) return; @@ -87,6 +92,7 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { } (void)CHECK_GL_ERROR(); } +#endif } void GLBackend::resetQueryStage() { diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendShader.cpp new file mode 100644 index 0000000000..fd44ad462f --- /dev/null +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendShader.cpp @@ -0,0 +1,560 @@ +// +// Created by Gabriel Calero & Cristian Duarte on 2017/12/28 +// Copyright 2013-2017 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 "GLBackend.h" +#include "GLShader.h" +#include + +using namespace gpu; +using namespace gpu::gl; + +// GLSL version +std::string GLBackend::getBackendShaderHeader() const { + return std::string("#version 310 es"); +} + +// Shader domain +static const size_t NUM_SHADER_DOMAINS = 2; + +// GL Shader type enums +// Must match the order of type specified in gpu::Shader::Type +static const std::array SHADER_DOMAINS{ { + GL_VERTEX_SHADER, + GL_FRAGMENT_SHADER, + // GL_GEOMETRY_SHADER, +} }; + +// Domain specific defines +// Must match the order of type specified in gpu::Shader::Type +static const std::array DOMAIN_DEFINES{ { + "#define GPU_VERTEX_SHADER", + "#define GPU_PIXEL_SHADER", + // "#define GPU_GEOMETRY_SHADER", +} }; + +// Stereo specific defines +static const std::string stereoVersion{ +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN" +#endif +#ifdef GPU_STEREO_DRAWCALL_DOUBLED +#ifdef GPU_STEREO_CAMERA_BUFFER + "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED" +#else + "#define GPU_TRANSFORM_IS_STEREO" +#endif +#endif +}; + +// Versions specific of the shader +static const std::array VERSION_DEFINES { { + "", + stereoVersion +} }; + +GLShader* GLBackend::compileBackendShader(const Shader& shader) { + // Any GLSLprogram ? normally yes... + const std::string& shaderSource = shader.getSource().getCode(); + GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; + GLShader::ShaderObjects shaderObjects; + + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& shaderObject = shaderObjects[version]; + + std::string shaderDefines = getBackendShaderHeader() + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version] + + "\n#extension GL_EXT_texture_buffer : enable" + + "\nprecision lowp float; // check precision 2" + + "\nprecision lowp samplerBuffer;" + + "\nprecision lowp sampler2DShadow;"; + std::string error; + +#ifdef SEPARATE_PROGRAM + bool result = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram, error); +#else + bool result = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, error); +#endif + if (!result) { + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << error.c_str(); + return nullptr; + } + } + + // So far so good, the shader is created successfully + GLShader* object = new GLShader(this->shared_from_this()); + object->_shaderObjects = shaderObjects; + + return object; +} + +GLShader* GLBackend::compileBackendProgram(const Shader& program) { + if (!program.isProgram()) { + return nullptr; + } + + GLShader::ShaderObjects programObjects; + + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& programObject = programObjects[version]; + + // Let's go through every shaders and make sure they are ready to go + std::vector shaderGLObjects; + for (auto subShader : program.getShaders()) { + auto object = GLShader::sync((*this), *subShader); + if (object) { + shaderGLObjects.push_back(object->_shaderObjects[version].glshader); + } else { + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - One of the shaders of the program is not compiled?"; + return nullptr; + } + } + + std::string error; + GLuint glprogram = ::gl::compileProgram(shaderGLObjects, error); + if (glprogram == 0) { + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << error.c_str(); + return nullptr; + } + + programObject.glprogram = glprogram; + + makeProgramBindings(programObject); + } + + // So far so good, the program versions have all been created successfully + GLShader* object = new GLShader(this->shared_from_this()); + object->_shaderObjects = programObjects; + + return object; +} + +GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) { + switch (gltype) { + case GL_FLOAT: + return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC2: + return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC3: + return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC4: + return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + + case GL_INT: + return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC2: + return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC3: + return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC4: + return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); + + case GL_UNSIGNED_INT: + return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC2: + return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC3: + return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC4: + return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); + + case GL_BOOL: + return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC2: + return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC3: + return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC4: + return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); + + case GL_FLOAT_MAT2: + return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT3: + return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT4: + return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + + //{GL_FLOAT_MAT2x3 mat2x3}, + //{GL_FLOAT_MAT2x4 mat2x4}, + //{GL_FLOAT_MAT3x2 mat3x2}, + //{GL_FLOAT_MAT3x4 mat3x4}, + //{GL_FLOAT_MAT4x2 mat4x2}, + //{GL_FLOAT_MAT4x3 mat4x3}, + //{GL_DOUBLE_MAT2 dmat2}, + //{GL_DOUBLE_MAT3 dmat3}, + //{GL_DOUBLE_MAT4 dmat4}, + //{GL_DOUBLE_MAT2x3 dmat2x3}, + //{GL_DOUBLE_MAT2x4 dmat2x4}, + //{GL_DOUBLE_MAT3x2 dmat3x2}, + //{GL_DOUBLE_MAT3x4 dmat3x4}, + //{GL_DOUBLE_MAT4x2 dmat4x2}, + //{GL_DOUBLE_MAT4x3 dmat4x3}, + + case GL_SAMPLER_2D: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); + + case GL_SAMPLER_3D: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); + case GL_SAMPLER_CUBE: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_SAMPLER_2D_MULTISAMPLE: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_SAMPLER_2D_ARRAY: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + + case GL_SAMPLER_2D_SHADOW: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); + case GL_SAMPLER_CUBE_SHADOW: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); + + case GL_SAMPLER_2D_ARRAY_SHADOW: + return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); + + // {GL_SAMPLER_1D_SHADOW sampler1DShadow}, + // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, + + case GL_SAMPLER_BUFFER: + return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER); + + // {GL_SAMPLER_2D_RECT sampler2DRect}, + // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, + + case GL_INT_SAMPLER_2D: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_2D_MULTISAMPLE: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_3D: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); + case GL_INT_SAMPLER_CUBE: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_INT_SAMPLER_2D_ARRAY: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + + // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, + // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, + + case GL_UNSIGNED_INT_SAMPLER_2D: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_3D: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); + case GL_UNSIGNED_INT_SAMPLER_CUBE: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + //{GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, + //{GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, + + //{GL_IMAGE_1D image1D}, + //{GL_IMAGE_2D image2D}, + //{GL_IMAGE_3D image3D}, + //{GL_IMAGE_2D_RECT image2DRect}, + //{GL_IMAGE_CUBE imageCube}, + //{GL_IMAGE_BUFFER imageBuffer}, + //{GL_IMAGE_1D_ARRAY image1DArray}, + //{GL_IMAGE_2D_ARRAY image2DArray}, + //{GL_IMAGE_2D_MULTISAMPLE image2DMS}, + //{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, + //{GL_INT_IMAGE_1D iimage1D}, + //{GL_INT_IMAGE_2D iimage2D}, + //{GL_INT_IMAGE_3D iimage3D}, + //{GL_INT_IMAGE_2D_RECT iimage2DRect}, + //{GL_INT_IMAGE_CUBE iimageCube}, + //{GL_INT_IMAGE_BUFFER iimageBuffer}, + //{GL_INT_IMAGE_1D_ARRAY iimage1DArray}, + //{GL_INT_IMAGE_2D_ARRAY iimage2DArray}, + //{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, + //{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, + //{GL_UNSIGNED_INT_IMAGE_1D uimage1D}, + //{GL_UNSIGNED_INT_IMAGE_2D uimage2D}, + //{GL_UNSIGNED_INT_IMAGE_3D uimage3D}, + //{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, + //{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot + + //{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, + //{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, + //{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, + //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, + //{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, + //{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} +#if 0 + case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); + case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); + case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); +#endif + + default: + return ElementResource(Element(), Resource::BUFFER); + } +}; + +int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { + GLint uniformsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); + + for (int i = 0; i < uniformsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + GLint location = glGetUniformLocation(glprogram, name); + const GLint INVALID_UNIFORM_LOCATION = -1; + + // Try to make sense of the gltype + auto elementResource = getFormatFromGLUniform(type); + + // The uniform as a standard var type + if (location != INVALID_UNIFORM_LOCATION) { + // Let's make sure the name doesn't contains an array element + std::string sname(name); + auto foundBracket = sname.find_first_of('['); + if (foundBracket != std::string::npos) { + // std::string arrayname = sname.substr(0, foundBracket); + + if (sname[foundBracket + 1] == '0') { + sname = sname.substr(0, foundBracket); + } else { + // skip this uniform since it's not the first element of an array + continue; + } + } + + if (elementResource._resource == Resource::BUFFER) { + uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); + } else { + // For texture/Sampler, the location is the actual binding value + GLint binding = -1; + glGetUniformiv(glprogram, location, &binding); + + auto requestedBinding = slotBindings.find(std::string(sname)); + if (requestedBinding != slotBindings.end()) { + if (binding != (*requestedBinding)._location) { + binding = (*requestedBinding)._location; + for (auto i = 0; i < size; i++) { + // If we are working with an array of textures, reserve for each elemet + glProgramUniform1i(glprogram, location + i, binding + i); + } + } + } + + textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + } + } + } + + return uniformsCount; +} + +int GLBackend::makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { + GLint buffersCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); + + // fast exit + if (buffersCount == 0) { + return 0; + } + + GLint maxNumUniformBufferSlots = 0; + glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); + std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); + + struct UniformBlockInfo { + using Vector = std::vector; + const GLuint index{ 0 }; + const std::string name; + GLint binding{ -1 }; + GLint size{ 0 }; + + static std::string getName(GLuint glprogram, GLuint i) { + static const GLint NAME_LENGTH = 256; + GLint length = 0; + GLchar nameBuffer[NAME_LENGTH]; + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); + glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, nameBuffer); + return std::string(nameBuffer); + } + + UniformBlockInfo(GLuint glprogram, GLuint i) : index(i), name(getName(glprogram, i)) { + glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_BINDING, &binding); + glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_DATA_SIZE, &size); + } + }; + + UniformBlockInfo::Vector uniformBlocks; + uniformBlocks.reserve(buffersCount); + for (int i = 0; i < buffersCount; i++) { + uniformBlocks.push_back(UniformBlockInfo(glprogram, i)); + } + + for (auto& info : uniformBlocks) { + auto requestedBinding = slotBindings.find(info.name); + if (requestedBinding != slotBindings.end()) { + info.binding = (*requestedBinding)._location; + glUniformBlockBinding(glprogram, info.index, info.binding); + uniformBufferSlotMap[info.binding] = info.index; + } + } + + for (auto& info : uniformBlocks) { + if (slotBindings.count(info.name)) { + continue; + } + + // If the binding is 0, or the binding maps to an already used binding + if (info.binding == 0 || !isUnusedSlot(uniformBufferSlotMap[info.binding])) { + // If no binding was assigned then just do it finding a free slot + auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), GLBackend::isUnusedSlot); + if (slotIt != uniformBufferSlotMap.end()) { + info.binding = slotIt - uniformBufferSlotMap.begin(); + glUniformBlockBinding(glprogram, info.index, info.binding); + } else { + // This should neve happen, an active ubo cannot find an available slot among the max available?! + info.binding = -1; + } + } + + uniformBufferSlotMap[info.binding] = info.index; + } + + for (auto& info : uniformBlocks) { + static const Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); + buffers.insert(Shader::Slot(info.name, info.binding, element, Resource::BUFFER, info.size)); + } + return buffersCount; +} + +int GLBackend::makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { + GLint inputsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); + + for (int i = 0; i < inputsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + + GLint binding = glGetAttribLocation(glprogram, name); + + auto elementResource = getFormatFromGLUniform(type); + inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); + } + + return inputsCount; +} + +int GLBackend::makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { + /* GLint outputsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); + + for (int i = 0; i < inputsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + + auto element = getFormatFromGLUniform(type); + outputs.insert(Shader::Slot(name, i, element)); + } + */ + return 0; //inputsCount; +} + +void GLBackend::makeProgramBindings(ShaderObject& shaderObject) { + if (!shaderObject.glprogram) { + return; + } + GLuint glprogram = shaderObject.glprogram; + GLint loc = -1; + + //Check for gpu specific attribute slotBindings + loc = glGetAttribLocation(glprogram, "inPosition"); + if (loc >= 0 && loc != gpu::Stream::POSITION) { + glBindAttribLocation(glprogram, gpu::Stream::POSITION, "inPosition"); + } + + loc = glGetAttribLocation(glprogram, "inNormal"); + if (loc >= 0 && loc != gpu::Stream::NORMAL) { + glBindAttribLocation(glprogram, gpu::Stream::NORMAL, "inNormal"); + } + + loc = glGetAttribLocation(glprogram, "inColor"); + if (loc >= 0 && loc != gpu::Stream::COLOR) { + glBindAttribLocation(glprogram, gpu::Stream::COLOR, "inColor"); + } + + loc = glGetAttribLocation(glprogram, "inTexCoord0"); + if (loc >= 0 && loc != gpu::Stream::TEXCOORD) { + glBindAttribLocation(glprogram, gpu::Stream::TEXCOORD, "inTexCoord0"); + } + + loc = glGetAttribLocation(glprogram, "inTangent"); + if (loc >= 0 && loc != gpu::Stream::TANGENT) { + glBindAttribLocation(glprogram, gpu::Stream::TANGENT, "inTangent"); + } + + char attribName[] = "inTexCoordn"; + for (auto i = 0; i < 4; i++) { + auto streamId = gpu::Stream::TEXCOORD1 + i; + + attribName[strlen(attribName) - 1] = '1' + i; + loc = glGetAttribLocation(glprogram, attribName); + if (loc >= 0 && loc != streamId) { + glBindAttribLocation(glprogram, streamId, attribName); + } + } + + loc = glGetAttribLocation(glprogram, "inSkinClusterIndex"); + if (loc >= 0 && loc != gpu::Stream::SKIN_CLUSTER_INDEX) { + glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_INDEX, "inSkinClusterIndex"); + } + + loc = glGetAttribLocation(glprogram, "inSkinClusterWeight"); + if (loc >= 0 && loc != gpu::Stream::SKIN_CLUSTER_WEIGHT) { + glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_WEIGHT, "inSkinClusterWeight"); + } + + loc = glGetAttribLocation(glprogram, "_drawCallInfo"); + if (loc >= 0 && loc != gpu::Stream::DRAW_CALL_INFO) { + glBindAttribLocation(glprogram, gpu::Stream::DRAW_CALL_INFO, "_drawCallInfo"); + } + + // Link again to take into account the assigned attrib location + glLinkProgram(glprogram); + + GLint linked = 0; + glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); + if (!linked) { + qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; + } +} diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp index 0a7db78b11..4a5c772b8b 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp @@ -11,6 +11,8 @@ #include "GLBackend.h" #include "GLState.h" +#include + using namespace gpu; using namespace gpu::gl; @@ -96,13 +98,11 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { void GLBackend::do_setStateDepthClampEnable(bool enable) { if (_pipeline._stateCache.depthClampEnable != enable) { - if (enable) { - qDebug() << "TODO: GLBackendState.cpp:do_setStateDepthClampEnable GL_DEPTH_CLAMP"; - //glEnable(GL_DEPTH_CLAMP); - } else { - //glDisable(GL_DEPTH_CLAMP); - qDebug() << "TODO: GLBackendState.cpp:do_setStateDepthClampEnable GL_DEPTH_CLAMP"; - } + //if (enable) { + // glEnable(GL_DEPTH_CLAMP); + //} else { + // glDisable(GL_DEPTH_CLAMP); + //} (void)CHECK_GL_ERROR(); _pipeline._stateCache.depthClampEnable = enable; @@ -124,14 +124,14 @@ void GLBackend::do_setStateScissorEnable(bool enable) { void GLBackend::do_setStateMultisampleEnable(bool enable) { if (_pipeline._stateCache.multisampleEnable != enable) { +#if !defined(USE_GLES) if (enable) { - //glEnable(GL_MULTISAMPLE); - qDebug() << "TODO: GLBackendState.cpp:do_setStateMultisampleEnable GL_MULTISAMPLE"; + glEnable(GL_MULTISAMPLE); } else { - //glDisable(GL_MULTISAMPLE); - qDebug() << "TODO: GLBackendState.cpp:do_setStateMultisampleEnable GL_MULTISAMPLE"; + glDisable(GL_MULTISAMPLE); } (void)CHECK_GL_ERROR(); +#endif _pipeline._stateCache.multisampleEnable = enable; } @@ -139,14 +139,14 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { if (_pipeline._stateCache.antialisedLineEnable != enable) { +#if !defined(USE_GLES) if (enable) { - //glEnable(GL_LINE_SMOOTH); - qDebug() << "TODO: GLBackendState.cpp:do_setStateAntialiasedLineEnable GL_LINE_SMOOTH"; + glEnable(GL_LINE_SMOOTH); } else { - //glDisable(GL_LINE_SMOOTH); - qDebug() << "TODO: GLBackendState.cpp:do_setStateAntialiasedLineEnable GL_LINE_SMOOTH"; + glDisable(GL_LINE_SMOOTH); } (void)CHECK_GL_ERROR(); +#endif _pipeline._stateCache.antialisedLineEnable = enable; } @@ -156,17 +156,17 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) { if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { if ((bias.x != 0.0f) || (bias.y != 0.0f)) { glEnable(GL_POLYGON_OFFSET_FILL); - //glEnable(GL_POLYGON_OFFSET_LINE); - qDebug() << "TODO: GLBackendState.cpp:do_setStateDepthBias GL_POLYGON_OFFSET_LINE"; - //glEnable(GL_POLYGON_OFFSET_POINT); - qDebug() << "TODO: GLBackendState.cpp:do_setStateDepthBias GL_POLYGON_OFFSET_POINT"; +#if !defined(USE_GLES) + glEnable(GL_POLYGON_OFFSET_LINE); + glEnable(GL_POLYGON_OFFSET_POINT); +#endif glPolygonOffset(bias.x, bias.y); } else { glDisable(GL_POLYGON_OFFSET_FILL); - //glDisable(GL_POLYGON_OFFSET_LINE); - qDebug() << "TODO: GLBackendState.cpp:do_setStateDepthBias GL_POLYGON_OFFSET_LINE"; - //glDisable(GL_POLYGON_OFFSET_POINT); - qDebug() << "TODO: GLBackendState.cpp:do_setStateDepthBias GL_POLYGON_OFFSET_POINT"; +#if !defined(USE_GLES) + glDisable(GL_POLYGON_OFFSET_LINE); + glDisable(GL_POLYGON_OFFSET_POINT); +#endif } (void)CHECK_GL_ERROR(); @@ -190,7 +190,7 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { glDepthFunc(COMPARISON_TO_GL[test.getFunction()]); } if (CHECK_GL_ERROR()) { - qDebug() << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") + qCDebug(gpulogging) << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") << "Func=" << test.getFunction() << "Raw=" << test.getRaw(); diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp index 4be7682a4f..ace33766ce 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendTexture.cpp @@ -15,13 +15,56 @@ using namespace gpu; using namespace gpu::gl; -bool GLBackend::isTextureReady(const TexturePointer& texture) { - // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = syncGPUObject(texture, true); - qDebug() << "GLBackendTexture isTextureReady syncGPUObject"; - return object && object->isReady(); + +GLuint GLBackend::getTextureID(const TexturePointer& texture) { + GLTexture* object = syncGPUObject(texture); + + if (!object) { + return 0; + } + + return object->_id; } +GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) { + const Texture& texture = *texturePointer; + // Special case external textures + if (TextureUsageType::EXTERNAL == texture.getUsageType()) { + Texture::ExternalUpdates updates = texture.getUpdates(); + if (!updates.empty()) { + Texture::ExternalRecycler recycler = texture.getExternalRecycler(); + Q_ASSERT(recycler); + // Discard any superfluous updates + while (updates.size() > 1) { + const auto& update = updates.front(); + // Superfluous updates will never have been read, but we want to ensure the previous + // writes to them are complete before they're written again, so return them with the + // same fences they arrived with. This can happen on any thread because no GL context + // work is involved + recycler(update.first, update.second); + updates.pop_front(); + } + + // The last texture remaining is the one we'll use to create the GLTexture + const auto& update = updates.front(); + // Check for a fence, and if it exists, inject a wait into the command stream, then destroy the fence + if (update.second) { + GLsync fence = static_cast(update.second); + glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(fence); + } + + // Create the new texture object (replaces any previous texture object) + new GLExternalTexture(shared_from_this(), texture, update.first); + } + + // Return the texture object (if any) associated with the texture, without extensive logic + // (external textures are + return Backend::getGPUObject(texture); + } + + return nullptr; +} void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); @@ -30,8 +73,7 @@ void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) { } // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = syncGPUObject(resourceTexture, false); - qDebug() << "GLBackendTexture do_generateTextureMips syncGPUObject"; + GLTexture* object = syncGPUObject(resourceTexture); if (!object) { return; } diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp index 3068e24dac..f286a5cca9 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendTransform.cpp @@ -15,7 +15,6 @@ using namespace gpu::gl; // Transform Stage void GLBackend::do_setModelTransform(const Batch& batch, size_t paramOffset) { - qDebug() << "do_setModelTransform"; } void GLBackend::do_setViewTransform(const Batch& batch, size_t paramOffset) { @@ -38,7 +37,7 @@ void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset) glViewport(vp.x, vp.y, vp.z, vp.w); // Where we assign the GL viewport - if (_stereo._enable) { + if (_stereo.isStereo()) { vp.z /= 2; if (_stereo._pass) { vp.x += vp.z; @@ -103,7 +102,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo if (_invalidView) { // Apply the correction - if (_viewIsCamera && _correction.correction != glm::mat4()) { + if (_viewIsCamera && (_viewCorrectionEnabled && _correction.correction != glm::mat4())) { // FIXME should I switch to using the camera correction buffer in Transform.slf and leave this out? Transform result; _view.mult(result, _view, _correction.correction); @@ -120,7 +119,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo size_t offset = _cameraUboSize * _cameras.size(); _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); - if (stereo._enable) { + if (stereo.isStereo()) { #ifdef GPU_STEREO_CAMERA_BUFFER _cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view), _camera.getEyeCamera(1, stereo, _view))); #else @@ -152,7 +151,7 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta #ifdef GPU_STEREO_CAMERA_BUFFER bindCurrentCamera(0); #else - if (!stereo._enable) { + if (!stereo.isStereo()) { bindCurrentCamera(0); } #endif @@ -162,51 +161,11 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta void GLBackend::TransformStageState::bindCurrentCamera(int eye) const { if (_currentCameraOffset != INVALID_OFFSET) { - //qDebug() << "GLBackend::TransformStageState::bindCurrentCamera"; glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, _cameraBuffer, _currentCameraOffset + eye * _cameraUboSize, sizeof(CameraBufferElement)); } } -void GLBackend::updateTransform(const Batch& batch) { - _transform.update(_commandIndex, _stereo); - - auto& drawCallInfoBuffer = batch.getDrawCallInfoBuffer(); - if (batch._currentNamedCall.empty()) { - (void)CHECK_GL_ERROR(); - auto& drawCallInfo = drawCallInfoBuffer[_currentDraw]; - glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is disabled - (void)CHECK_GL_ERROR(); - GLint current_vao, current_vbo, maxVertexAtribs; - glGetIntegerv(GL_VERTEX_ARRAY_BINDING, ¤t_vao); - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, ¤t_vbo); - glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAtribs); - glVertexAttribI4i(gpu::Stream::DRAW_CALL_INFO, drawCallInfo.index, drawCallInfo.unused, 0, 0); - - //int values[] = {drawCallInfo.index, drawCallInfo.unused}; - //glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_INT, 0, (const GLvoid *) values); - - /* - //glDisableVertexAttribArray currentvao 1 current vbo 0 - GL_INVALID_OPERATION is generated - a non-zero vertex array object is bound, - zero is bound to the GL_ARRAY_BUFFER buffer object binding point and - the pointer argument is not NULL. TRUE - */ - //qDebug() << "GLBackend::updateTransform glVertexAttribIPointer done"; - (void)CHECK_GL_ERROR(); - - } else { - //qDebug() << "GLBackend::updateTransform else"; - glEnableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is enabled - glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); - glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_UNSIGNED_SHORT, 0, - _transform._drawCallInfoOffsets[batch._currentNamedCall]); - glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, 1); - } - - (void)CHECK_GL_ERROR(); -} - void GLBackend::resetTransformStage() { - + glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); + _transform._enabledDrawcallInfoBuffer = false; } diff --git a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp index 150bb2be70..0bca9e86d9 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.cpp @@ -21,28 +21,27 @@ GLFramebuffer::~GLFramebuffer() { } } -bool GLFramebuffer::checkStatus(GLenum target) const { - bool result = false; +bool GLFramebuffer::checkStatus() const { switch (_status) { case GL_FRAMEBUFFER_COMPLETE: // Success ! - result = true; - break; + return true; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; break; -/* TODO: case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; - break; */ + //case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + // qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; + // break; + //case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + // qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; + // break; case GL_FRAMEBUFFER_UNSUPPORTED: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; break; } - return result; + return false; } diff --git a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h b/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h index a2fd0999f3..5a388e1965 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h +++ b/libraries/gpu-gles/src/gpu/gl/GLFramebuffer.h @@ -64,7 +64,7 @@ public: protected: GLenum _status { GL_FRAMEBUFFER_COMPLETE }; virtual void update() = 0; - bool checkStatus(GLenum target) const; + bool checkStatus() const; GLFramebuffer(const std::weak_ptr& backend, const Framebuffer& framebuffer, GLuint id) : GLObject(backend, framebuffer, id) {} ~GLFramebuffer(); diff --git a/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp index 09c09de353..ebf1a55232 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLPipeline.cpp @@ -51,10 +51,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { // Special case for view correction matrices, any pipeline that declares the correction buffer // uniform will automatically have it provided without any client code necessary. // Required for stable lighting in the HMD. - //CLIMAX_MERGE_START - //getbuffers() doesnt exist anymore.. use get uniformbuffers()? object->_cameraCorrection = shader->getUniformBuffers().findLocation("cameraCorrectionBuffer"); - //CLIMAX_MERGE_END object->_program = programObject; object->_state = stateObject; diff --git a/libraries/gpu-gles/src/gpu/gl/GLShader.cpp b/libraries/gpu-gles/src/gpu/gl/GLShader.cpp index b728010470..7ed9121978 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLShader.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLShader.cpp @@ -30,126 +30,6 @@ GLShader::~GLShader() { } } -// GLSL version -static const std::string glslVersion { - "#version 310 es" -}; - -// Shader domain -static const size_t NUM_SHADER_DOMAINS = 3; - -// GL Shader type enums -// Must match the order of type specified in gpu::Shader::Type -static const std::array SHADER_DOMAINS { { - GL_VERTEX_SHADER, - GL_FRAGMENT_SHADER, - //GL_GEOMETRY_SHADER, -} }; - -// Domain specific defines -// Must match the order of type specified in gpu::Shader::Type -static const std::array DOMAIN_DEFINES { { - "#define GPU_VERTEX_SHADER", - "#define GPU_PIXEL_SHADER", - "#define GPU_GEOMETRY_SHADER", -} }; - -// Stereo specific defines -static const std::string stereoVersion { -#ifdef GPU_STEREO_DRAWCALL_INSTANCED - "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN" -#endif -#ifdef GPU_STEREO_DRAWCALL_DOUBLED -#ifdef GPU_STEREO_CAMERA_BUFFER - "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED" -#else - "#define GPU_TRANSFORM_IS_STEREO" -#endif -#endif -}; - -// Versions specific of the shader -static const std::array VERSION_DEFINES { { - "", - stereoVersion -} }; - -GLShader* compileBackendShader(GLBackend& backend, const Shader& shader) { - // Any GLSLprogram ? normally yes... - const std::string& shaderSource = shader.getSource().getCode(); - GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; - GLShader::ShaderObjects shaderObjects; - - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& shaderObject = shaderObjects[version]; - std::string shaderDefines = glslVersion + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version] - + "\n" + "#extension GL_EXT_texture_buffer : enable" - + "\nprecision lowp float; // check precision 2" - + "\nprecision lowp samplerBuffer;" - + "\nprecision lowp sampler2DShadow;"; - // TODO Delete bool result = compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram); - std::string error; - - -#ifdef SEPARATE_PROGRAM - bool result = ::gl::compileShader(shaderDomain, shaderSource.c_str(), shaderDefines.c_str(), shaderObject.glshader, shaderObject.glprogram, error); -#else - bool result = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, error); -#endif - if (!result) { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << error.c_str(); - return nullptr; - } - } - - // So far so good, the shader is created successfully - GLShader* object = new GLShader(backend.shared_from_this()); - object->_shaderObjects = shaderObjects; - - return object; -} - -GLShader* compileBackendProgram(GLBackend& backend, const Shader& program) { - if (!program.isProgram()) { - return nullptr; - } - - GLShader::ShaderObjects programObjects; - - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& programObject = programObjects[version]; - - // Let's go through every shaders and make sure they are ready to go - std::vector< GLuint > shaderGLObjects; - for (auto subShader : program.getShaders()) { - auto object = GLShader::sync(backend, *subShader); - if (object) { - shaderGLObjects.push_back(object->_shaderObjects[version].glshader); - } else { - qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; - return nullptr; - } - } - - std::string error; - GLuint glprogram = ::gl::compileProgram(shaderGLObjects, error); - if (glprogram == 0) { - qCWarning(gpugllogging) << error.c_str(); - return nullptr; - } - - programObject.glprogram = glprogram; - - makeProgramBindings(programObject); - } - - // So far so good, the program versions have all been created successfully - GLShader* object = new GLShader(backend.shared_from_this()); - object->_shaderObjects = programObjects; - - return object; -} - GLShader* GLShader::sync(GLBackend& backend, const Shader& shader) { GLShader* object = Backend::getGPUObject(shader); @@ -159,13 +39,13 @@ GLShader* GLShader::sync(GLBackend& backend, const Shader& shader) { } // need to have a gpu object? if (shader.isProgram()) { - GLShader* tempObject = compileBackendProgram(backend, shader); + GLShader* tempObject = backend.compileBackendProgram(shader); if (tempObject) { object = tempObject; Backend::setGPUObject(shader, object); } } else if (shader.isDomain()) { - GLShader* tempObject = compileBackendShader(backend, shader); + GLShader* tempObject = backend.compileBackendShader(shader); if (tempObject) { object = tempObject; Backend::setGPUObject(shader, object); @@ -189,21 +69,21 @@ bool GLShader::makeProgram(GLBackend& backend, Shader& shader, const Shader::Bin auto& shaderObject = object->_shaderObjects[version]; if (shaderObject.glprogram) { Shader::SlotSet buffers; - makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); + backend.makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); Shader::SlotSet uniforms; Shader::SlotSet textures; Shader::SlotSet samplers; - makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); + backend.makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); Shader::SlotSet resourceBuffers; - makeResourceBufferSlots(shaderObject.glprogram, slotBindings, resourceBuffers); + backend.makeResourceBufferSlots(shaderObject.glprogram, slotBindings, resourceBuffers); Shader::SlotSet inputs; - makeInputSlots(shaderObject.glprogram, slotBindings, inputs); + backend.makeInputSlots(shaderObject.glprogram, slotBindings, inputs); Shader::SlotSet outputs; - makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); + backend.makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); // Define the public slots only from the default version if (version == 0) { @@ -222,3 +102,5 @@ bool GLShader::makeProgram(GLBackend& backend, Shader& shader, const Shader::Bin return true; } + + diff --git a/libraries/gpu-gles/src/gpu/gl/GLShader.h b/libraries/gpu-gles/src/gpu/gl/GLShader.h index e03b487a60..dcf2dc330d 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLShader.h +++ b/libraries/gpu-gles/src/gpu/gl/GLShader.h @@ -12,6 +12,13 @@ namespace gpu { namespace gl { +struct ShaderObject { + GLuint glshader { 0 }; + GLuint glprogram { 0 }; + GLint transformCameraSlot { -1 }; + GLint transformObjectSlot { -1 }; +}; + class GLShader : public GPUObject { public: static GLShader* sync(GLBackend& backend, const Shader& shader); diff --git a/libraries/gpu-gles/src/gpu/gl/GLShared.cpp b/libraries/gpu-gles/src/gpu/gl/GLShared.cpp index 5d340889a6..f818a221b2 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLShared.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLShared.cpp @@ -17,6 +17,7 @@ Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") Q_LOGGING_CATEGORY(trace_render_gpu_gl, "trace.render.gpu.gl") +Q_LOGGING_CATEGORY(trace_render_gpu_gl_detail, "trace.render.gpu.gl.detail") namespace gpu { namespace gl { @@ -27,22 +28,25 @@ bool checkGLError(const char* name) { } else { switch (error) { case GL_INVALID_ENUM: - qCDebug(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."; + 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: - qCDebug(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"; + 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: - qCDebug(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.."; + 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: - qCDebug(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."; + 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: - qCDebug(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."; + 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; - default: - qCDebug(gpugllogging) << "GLBackend" << name << ": Unknown error: " << error; + 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; @@ -50,12 +54,8 @@ bool checkGLError(const char* name) { } bool checkGLErrorDebug(const char* name) { -#ifdef DEBUG + // FIXME, disable in debug mode when near release return checkGLError(name); -#else - Q_UNUSED(name); - return false; -#endif } gpu::Size getFreeDedicatedMemory() { @@ -86,43 +86,6 @@ gpu::Size getFreeDedicatedMemory() { return result; } -gpu::Size getDedicatedMemory() { - static Size dedicatedMemory { 0 }; - static std::once_flag once; - std::call_once(once, [&] { - if (!dedicatedMemory) { - GLint atiGpuMemory[4]; - // not really total memory, but close enough if called early enough in the application lifecycle - //glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); - qDebug() << "TODO: GLShared.cpp.cpp:initInput GL_TEXTURE_FREE_MEMORY_ATI"; - if (GL_NO_ERROR == glGetError()) { - dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]); - } - } - - if (!dedicatedMemory) { - GLint nvGpuMemory { 0 }; - qDebug() << "TODO: GLShared.cpp.cpp:initInput GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX"; - //glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory); - if (GL_NO_ERROR == glGetError()) { - dedicatedMemory = KB_TO_BYTES(nvGpuMemory); - } - } - - if (!dedicatedMemory) { - auto gpuIdent = GPUIdent::getInstance(); - if (gpuIdent && gpuIdent->isValid()) { - dedicatedMemory = MB_TO_BYTES(gpuIdent->getMemory()); - } - } - }); - - return dedicatedMemory; -} - - - - ComparisonFunction comparisonFuncFromGL(GLenum func) { if (func == GL_NEVER) { return NEVER; @@ -219,21 +182,17 @@ State::BlendArg blendArgFromGL(GLenum blendArg) { void getCurrentGLState(State::Data& state) { { - GLint modes[2]; + //GLint modes[2]; //glGetIntegerv(GL_POLYGON_MODE, modes); - qDebug() << "TODO: GLShared.cpp:getCurrentGLState GL_POLYGON_MODE"; - qDebug() << "TODO: GLShared.cpp:getCurrentGLState GL_FILL"; - qDebug() << "TODO: GLShared.cpp:getCurrentGLState GL_LINE"; - - if (modes[0] == 0 /*GL_FILL*/) { - state.fillMode = State::FILL_FACE; - } else { - if (modes[0] == 0 /*GL_LINE*/) { - state.fillMode = State::FILL_LINE; - } else { - state.fillMode = State::FILL_POINT; - } - } + //if (modes[0] == GL_FILL) { + // state.fillMode = State::FILL_FACE; + //} else { + // if (modes[0] == GL_LINE) { + // state.fillMode = State::FILL_LINE; + // } else { + // state.fillMode = State::FILL_POINT; + // } + //} } { if (glIsEnabled(GL_CULL_FACE)) { @@ -248,15 +207,10 @@ void getCurrentGLState(State::Data& state) { GLint winding; glGetIntegerv(GL_FRONT_FACE, &winding); state.frontFaceClockwise = (winding == GL_CW); - //state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); - qDebug() << "TODO: GLShared.cpp.cpp:getCurrentGLState GL_DEPTH_CLAMP"; + state.depthClampEnable = false; //glIsEnabled(GL_DEPTH_CLAMP_EXT); state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); - //state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); - qDebug() << "TODO: GLShared.cpp.cpp:getCurrentGLState GL_MULTISAMPLE"; - - //state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); - qDebug() << "TODO: GLShared.cpp.cpp:getCurrentGLState GL_LINE_SMOOTH"; - + state.multisampleEnable = false; //glIsEnabled(GL_MULTISAMPLE_EXT); + state.antialisedLineEnable = false; //glIsEnabled(GL_LINE_SMOOTH); } { if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { @@ -354,504 +308,6 @@ void getCurrentGLState(State::Data& state) { } -class ElementResource { -public: - gpu::Element _element; - uint16 _resource; - - ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} -}; - -ElementResource getFormatFromGLUniform(GLenum gltype) { - switch (gltype) { - case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - /* - case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - */ - case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); - - case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); -#if defined(Q_OS_WIN) - case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); -#endif - - case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); - - - case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - - /* {GL_FLOAT_MAT2x3 mat2x3}, - {GL_FLOAT_MAT2x4 mat2x4}, - {GL_FLOAT_MAT3x2 mat3x2}, - {GL_FLOAT_MAT3x4 mat3x4}, - {GL_FLOAT_MAT4x2 mat4x2}, - {GL_FLOAT_MAT4x3 mat4x3}, - {GL_DOUBLE_MAT2 dmat2}, - {GL_DOUBLE_MAT3 dmat3}, - {GL_DOUBLE_MAT4 dmat4}, - {GL_DOUBLE_MAT2x3 dmat2x3}, - {GL_DOUBLE_MAT2x4 dmat2x4}, - {GL_DOUBLE_MAT3x2 dmat3x2}, - {GL_DOUBLE_MAT3x4 dmat3x4}, - {GL_DOUBLE_MAT4x2 dmat4x2}, - {GL_DOUBLE_MAT4x3 dmat4x3}, - */ - - //qDebug() << "TODO: GLShared.cpp.cpp:ElementResource GL_SAMPLER_1D"; - //case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); - case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); - - case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); - case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); - -#if defined(Q_OS_WIN) - case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif - - case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); -#if defined(Q_OS_WIN) - case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); - - case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); -#endif - - // {GL_SAMPLER_1D_SHADOW sampler1DShadow}, - // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, - - // {GL_SAMPLER_BUFFER samplerBuffer}, - // {GL_SAMPLER_2D_RECT sampler2DRect}, - // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, - -#if defined(Q_OS_WIN) - case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); - case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); - case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - - // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, - // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, - - case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); - case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); - case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif - // {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, - // {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, - /* - {GL_IMAGE_1D image1D}, - {GL_IMAGE_2D image2D}, - {GL_IMAGE_3D image3D}, - {GL_IMAGE_2D_RECT image2DRect}, - {GL_IMAGE_CUBE imageCube}, - {GL_IMAGE_BUFFER imageBuffer}, - {GL_IMAGE_1D_ARRAY image1DArray}, - {GL_IMAGE_2D_ARRAY image2DArray}, - {GL_IMAGE_2D_MULTISAMPLE image2DMS}, - {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, - {GL_INT_IMAGE_1D iimage1D}, - {GL_INT_IMAGE_2D iimage2D}, - {GL_INT_IMAGE_3D iimage3D}, - {GL_INT_IMAGE_2D_RECT iimage2DRect}, - {GL_INT_IMAGE_CUBE iimageCube}, - {GL_INT_IMAGE_BUFFER iimageBuffer}, - {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, - {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, - {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, - {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, - {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, - {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, - {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, - {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, - {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot - - {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, - {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, - {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} - */ - default: - return ElementResource(Element(), Resource::BUFFER); - } - -}; - -int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { - GLint uniformsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); - - for (int i = 0; i < uniformsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - GLint location = glGetUniformLocation(glprogram, name); - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - // Let's make sure the name doesn't contains an array element - std::string sname(name); - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - if (elementResource._resource == Resource::BUFFER) { - uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); - } else { - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - glProgramUniform1i(glprogram, location, binding); - } - } - - textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } - } - } - - return uniformsCount; -} - -const GLint UNUSED_SLOT = -1; -bool isUnusedSlot(GLint binding) { - return (binding == UNUSED_SLOT); -} - -int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { - GLint buffersCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumUniformBufferSlots = 0; - glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); - std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); - - struct UniformBlockInfo { - using Vector = std::vector; - const GLuint index{ 0 }; - const std::string name; - GLint binding{ -1 }; - GLint size{ 0 }; - - static std::string getName(GLuint glprogram, GLuint i) { - static const GLint NAME_LENGTH = 256; - GLint length = 0; - GLchar nameBuffer[NAME_LENGTH]; - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); - glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, nameBuffer); - return std::string(nameBuffer); - } - - UniformBlockInfo(GLuint glprogram, GLuint i) : index(i), name(getName(glprogram, i)) { - glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_BINDING, &binding); - glGetActiveUniformBlockiv(glprogram, index, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - } - }; - - UniformBlockInfo::Vector uniformBlocks; - uniformBlocks.reserve(buffersCount); - for (int i = 0; i < buffersCount; i++) { - uniformBlocks.push_back(UniformBlockInfo(glprogram, i)); - } - - for (auto& info : uniformBlocks) { - auto requestedBinding = slotBindings.find(info.name); - if (requestedBinding != slotBindings.end()) { - info.binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, info.index, info.binding); - uniformBufferSlotMap[info.binding] = info.index; - } - } - - for (auto& info : uniformBlocks) { - if (slotBindings.count(info.name)) { - continue; - } - - // If the binding is 0, or the binding maps to an already used binding - if (info.binding == 0 || uniformBufferSlotMap[info.binding] != UNUSED_SLOT) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), isUnusedSlot); - if (slotIt != uniformBufferSlotMap.end()) { - info.binding = slotIt - uniformBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, info.index, info.binding); - } else { - // This should neve happen, an active ubo cannot find an available slot among the max available?! - info.binding = -1; - } - } - - uniformBufferSlotMap[info.binding] = info.index; - } - - for (auto& info : uniformBlocks) { - static const Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); - buffers.insert(Shader::Slot(info.name, info.binding, element, Resource::BUFFER, info.size)); - } - return buffersCount; -} -//CLIMAX_MERGE_START -//This has been copied over from gl45backendshader.cpp -int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) { - GLint buffersCount = 0; - glGetProgramInterfaceiv(glprogram, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumResourceBufferSlots = 0; - glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxNumResourceBufferSlots); - std::vector resourceBufferSlotMap(maxNumResourceBufferSlots, -1); - - struct ResourceBlockInfo { - using Vector = std::vector; - const GLuint index{ 0 }; - const std::string name; - GLint binding{ -1 }; - GLint size{ 0 }; - - static std::string getName(GLuint glprogram, GLuint i) { - static const GLint NAME_LENGTH = 256; - GLint length = 0; - GLchar nameBuffer[NAME_LENGTH]; - glGetProgramResourceName(glprogram, GL_SHADER_STORAGE_BLOCK, i, NAME_LENGTH, &length, nameBuffer); - return std::string(nameBuffer); - } - - ResourceBlockInfo(GLuint glprogram, GLuint i) : index(i), name(getName(glprogram, i)) { - GLenum props[2] = { GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE}; - glGetProgramResourceiv(glprogram, GL_SHADER_STORAGE_BLOCK, i, 2, props, 2, nullptr, &binding); - } - }; - - ResourceBlockInfo::Vector resourceBlocks; - resourceBlocks.reserve(buffersCount); - for (int i = 0; i < buffersCount; i++) { - resourceBlocks.push_back(ResourceBlockInfo(glprogram, i)); - } - - for (auto& info : resourceBlocks) { - auto requestedBinding = slotBindings.find(info.name); - if (requestedBinding != slotBindings.end()) { - info.binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, info.index, info.binding); - resourceBufferSlotMap[info.binding] = info.index; - } - } - - for (auto& info : resourceBlocks) { - if (slotBindings.count(info.name)) { - continue; - } - - // If the binding is -1, or the binding maps to an already used binding - if (info.binding == -1 || !isUnusedSlot(resourceBufferSlotMap[info.binding])) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(resourceBufferSlotMap.begin(), resourceBufferSlotMap.end(), isUnusedSlot); - if (slotIt != resourceBufferSlotMap.end()) { - info.binding = slotIt - resourceBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, info.index, info.binding); - } else { - // This should never happen, an active ssbo cannot find an available slot among the max available?! - info.binding = -1; - } - } - - resourceBufferSlotMap[info.binding] = info.index; - } - - for (auto& info : resourceBlocks) { - static const Element element(SCALAR, gpu::UINT32, gpu::RESOURCE_BUFFER); - resourceBuffers.insert(Shader::Slot(info.name, info.binding, element, Resource::BUFFER, info.size)); - } - return buffersCount; -} -//CLIMAX_MERGE_END - -int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { - GLint inputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - GLint binding = glGetAttribLocation(glprogram, name); - - auto elementResource = getFormatFromGLUniform(type); - inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); - } - - return inputsCount; -} - -int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { - /* GLint outputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - auto element = getFormatFromGLUniform(type); - outputs.insert(Shader::Slot(name, i, element)); - } - */ - return 0; //inputsCount; -} - -void makeProgramBindings(ShaderObject& shaderObject) { - if (!shaderObject.glprogram) { - return; - } - GLuint glprogram = shaderObject.glprogram; - GLint loc = -1; - - //Check for gpu specific attribute slotBindings - loc = glGetAttribLocation(glprogram, "inPosition"); - if (loc >= 0 && loc != gpu::Stream::POSITION) { - glBindAttribLocation(glprogram, gpu::Stream::POSITION, "inPosition"); - } - - loc = glGetAttribLocation(glprogram, "inNormal"); - if (loc >= 0 && loc != gpu::Stream::NORMAL) { - glBindAttribLocation(glprogram, gpu::Stream::NORMAL, "inNormal"); - } - - loc = glGetAttribLocation(glprogram, "inColor"); - if (loc >= 0 && loc != gpu::Stream::COLOR) { - glBindAttribLocation(glprogram, gpu::Stream::COLOR, "inColor"); - } - - loc = glGetAttribLocation(glprogram, "inTexCoord0"); - if (loc >= 0 && loc != gpu::Stream::TEXCOORD) { - glBindAttribLocation(glprogram, gpu::Stream::TEXCOORD, "inTexCoord0"); - } - - loc = glGetAttribLocation(glprogram, "inTangent"); - if (loc >= 0 && loc != gpu::Stream::TANGENT) { - glBindAttribLocation(glprogram, gpu::Stream::TANGENT, "inTangent"); - } - - loc = glGetAttribLocation(glprogram, "inTexCoord1"); - if (loc >= 0 && loc != gpu::Stream::TEXCOORD1) { - glBindAttribLocation(glprogram, gpu::Stream::TEXCOORD1, "inTexCoord1"); - } - - loc = glGetAttribLocation(glprogram, "inSkinClusterIndex"); - if (loc >= 0 && loc != gpu::Stream::SKIN_CLUSTER_INDEX) { - glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_INDEX, "inSkinClusterIndex"); - } - - loc = glGetAttribLocation(glprogram, "inSkinClusterWeight"); - if (loc >= 0 && loc != gpu::Stream::SKIN_CLUSTER_WEIGHT) { - glBindAttribLocation(glprogram, gpu::Stream::SKIN_CLUSTER_WEIGHT, "inSkinClusterWeight"); - } - - loc = glGetAttribLocation(glprogram, "_drawCallInfo"); - if (loc >= 0 && loc != gpu::Stream::DRAW_CALL_INFO) { - glBindAttribLocation(glprogram, gpu::Stream::DRAW_CALL_INFO, "_drawCallInfo"); - } - - // Link again to take into account the assigned attrib location - glLinkProgram(glprogram); - - GLint linked = 0; - glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); - if (!linked) { - qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; - } - - // now assign the ubo binding, then DON't relink! - - //Check for gpu specific uniform slotBindings - loc = glGetProgramResourceIndex(glprogram, GL_SHADER_STORAGE_BLOCK, "transformObjectBuffer"); - if (loc >= 0) { - // FIXME GLES - // glShaderStorageBlockBinding(glprogram, loc, TRANSFORM_OBJECT_SLOT); - shaderObject.transformObjectSlot = TRANSFORM_OBJECT_SLOT; - } - - loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer"); - if (loc >= 0) { - glUniformBlockBinding(glprogram, loc, TRANSFORM_CAMERA_SLOT); - shaderObject.transformCameraSlot = TRANSFORM_CAMERA_SLOT; - } - - (void)CHECK_GL_ERROR(); -} - void serverWait() { auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); assert(fence); diff --git a/libraries/gpu-gles/src/gpu/gl/GLShared.h b/libraries/gpu-gles/src/gpu/gl/GLShared.h index 54209b106d..dcce896a37 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gles/src/gpu/gl/GLShared.h @@ -16,18 +16,18 @@ Q_DECLARE_LOGGING_CATEGORY(gpugllogging) Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl) +Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl_detail) + +#define BUFFER_OFFSET(bytes) ((GLubyte*) nullptr + (bytes)) namespace gpu { namespace gl { - static const GLint TRANSFORM_OBJECT_SLOT { 14 }; // SSBO binding slot - // Create a fence and inject a GPU wait on the fence void serverWait(); // Create a fence and synchronously wait on the fence void clientWait(); -gpu::Size getDedicatedMemory(); gpu::Size getFreeDedicatedMemory(); ComparisonFunction comparisonFuncFromGL(GLenum func); State::StencilOp stencilOpFromGL(GLenum stencilOp); @@ -35,25 +35,6 @@ State::BlendOp blendOpFromGL(GLenum blendOp); State::BlendArg blendArgFromGL(GLenum blendArg); void getCurrentGLState(State::Data& state); -struct ShaderObject { - GLuint glshader { 0 }; - GLuint glprogram { 0 }; - GLint transformCameraSlot { -1 }; - GLint transformObjectSlot { -1 }; -}; - -int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers); -int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers); -int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs); -int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs); -//CLIMAX_MERGE_START -//makeResourceBufferSlots has been added to glbacked as a virtual function and is being used in gl42 and gl45 overrides. -//Since these files dont exist in the andoid version create a stub here. -int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& resourceBuffers); -//CLIMAX_MERGE_END -void makeProgramBindings(ShaderObject& shaderObject); - enum GLSyncState { // The object is currently undergoing no processing, although it's content // may be out of date, or it's storage may be invalid relative to the @@ -128,7 +109,9 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { GL_SHORT, GL_UNSIGNED_SHORT, GL_BYTE, - GL_UNSIGNED_BYTE + GL_UNSIGNED_BYTE, + GL_UNSIGNED_BYTE, + GL_INT_2_10_10_10_REV, }; bool checkGLError(const char* name = nullptr); @@ -156,6 +139,7 @@ class GLQuery; class GLState; class GLShader; class GLTexture; +struct ShaderObject; } } // namespace gpu::gl diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp index 9808d389f1..2a39901ee7 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp @@ -11,13 +11,58 @@ using namespace gpu; using namespace gpu::gl; +bool GLTexelFormat::isCompressed() const { + switch (internalFormat) { + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_RGBA_ASTC_4x4: + case GL_COMPRESSED_RGBA_ASTC_5x4: + case GL_COMPRESSED_RGBA_ASTC_5x5: + case GL_COMPRESSED_RGBA_ASTC_6x5: + case GL_COMPRESSED_RGBA_ASTC_6x6: + case GL_COMPRESSED_RGBA_ASTC_8x5: + case GL_COMPRESSED_RGBA_ASTC_8x6: + case GL_COMPRESSED_RGBA_ASTC_8x8: + case GL_COMPRESSED_RGBA_ASTC_10x5: + case GL_COMPRESSED_RGBA_ASTC_10x6: + case GL_COMPRESSED_RGBA_ASTC_10x8: + case GL_COMPRESSED_RGBA_ASTC_10x10: + case GL_COMPRESSED_RGBA_ASTC_12x10: + case GL_COMPRESSED_RGBA_ASTC_12x12: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12: + return true; + default: + return false; + } +} GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { -// qDebug() << "GLTexelFormat::evalGLTexelFormatInternal " << dstFormat.getDimension() << ", " << dstFormat.getSemantic() << ", " << dstFormat.getType(); GLenum result = GL_RGBA8; switch (dstFormat.getDimension()) { case gpu::SCALAR: { switch (dstFormat.getSemantic()) { + case gpu::RED: case gpu::RGB: case gpu::RGBA: case gpu::SRGB: @@ -44,6 +89,12 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::INT16: result = GL_R16I; break; + case gpu::NUINT16: + //result = GL_R16_EXT; + break; + case gpu::NINT16: + //result = GL_R16_SNORM_EXT; + break; case gpu::HALF: result = GL_R16F; break; @@ -55,8 +106,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { break; case gpu::NUINT8: if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { - //result = GL_SLUMINANCE8; - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormatInternal GL_SLUMINANCE8"; + result = GL_SLUMINANCE8_NV; } else { result = GL_R8; } @@ -65,7 +115,6 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { result = GL_R8_SNORM; break; default: - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormatInternal " << dstFormat.getType(); Q_UNREACHABLE(); break; } @@ -74,10 +123,19 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { // the type should be float result = GL_R11F_G11F_B10F; break; - + case gpu::RGB9E5: + // the type should be float + result = GL_RGB9_E5; + break; case gpu::DEPTH: result = GL_DEPTH_COMPONENT16; switch (dstFormat.getType()) { + case gpu::UINT32: + case gpu::INT32: + case gpu::NUINT32: + case gpu::NINT32: + result = GL_DEPTH_COMPONENT32_OES; + break; case gpu::FLOAT: result = GL_DEPTH_COMPONENT32F; break; @@ -105,7 +163,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -114,10 +172,11 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: + case gpu::XY: result = GL_RG8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -131,11 +190,10 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { break; case gpu::SRGB: case gpu::SRGBA: - //result = GL_SRGB8; // standard 2.2 gamma correction color - result = GL_RGB8; // standard 2.2 gamma correction color + result = GL_SRGB8; // standard 2.2 gamma correction color break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -163,6 +221,12 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::INT16: result = GL_RGBA16I; break; + case gpu::NUINT16: + //result = GL_RGBA16_EXT; + break; + case gpu::NINT16: + //result = GL_RGBA16_SNORM_EXT; + break; case gpu::HALF: result = GL_RGBA16F; break; @@ -175,45 +239,40 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::NUINT8: result = GL_RGBA8; break; + case gpu::NUINT2: + //result = GL_RGBA2; + break; case gpu::NINT8: result = GL_RGBA8_SNORM; break; - case gpu::COMPRESSED: - case gpu::NUINT2: - case gpu::NINT16: - case gpu::NUINT16: + case gpu::NINT2_10_10_10: case gpu::NUINT32: case gpu::NINT32: + case gpu::COMPRESSED: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); } break; case gpu::SRGB: - //result = GL_SRGB8; - result = GL_RGB8; - qDebug() << "SRGBA Here 2"; + result = GL_SRGB8; break; case gpu::SRGBA: result = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } - default: qCDebug(gpugllogging) << "Unknown combination of texel format"; } //qDebug() << "GLTexelFormat::evalGLTexelFormatInternal result " << result; - return result; } GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat) { -// qDebug() << "GLTexelFormat::evalGLTexelFormat dst.getDimension=" << dstFormat.getDimension() << " dst.getType=" << dstFormat.getType() << " dst.getSemantic=" << dstFormat.getSemantic(); -// qDebug() << "GLTexelFormat::evalGLTexelFormat src.getDimension=" << srcFormat.getDimension() << " src.getType=" << srcFormat.getType() << " src.getSemantic=" << srcFormat.getSemantic(); if (dstFormat != srcFormat) { GLTexelFormat texel = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE }; @@ -224,20 +283,15 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { + case gpu::RED: case gpu::RGB: case gpu::RGBA: texel.internalFormat = GL_R8; break; - //CLIMAX_MERGE_START - // case gpu::COMPRESSED_R: - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_RED_RGTC1"; - // //texel.internalFormat = GL_COMPRESSED_RED_RGTC1; - // break; - //CLIMAX_MERGE_END - case gpu::DEPTH: - texel.internalFormat = GL_DEPTH_COMPONENT32_OES; + texel.format = GL_DEPTH_COMPONENT; + texel.internalFormat = GL_DEPTH_COMPONENT32F; break; case gpu::DEPTH_STENCIL: texel.type = GL_UNSIGNED_INT_24_8; @@ -245,7 +299,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -257,10 +311,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: + case gpu::XY: texel.internalFormat = GL_RG8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -276,19 +331,8 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::RGBA: texel.internalFormat = GL_RGB8; break; - //CLIMAX_MERGE_START - //not needed? - // case gpu::COMPRESSED_RGB: - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_RGB"; - // //texel.internalFormat = GL_COMPRESSED_RGB; - // break; - // case gpu::COMPRESSED_SRGB: - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_SRGB"; - // //texel.internalFormat = GL_COMPRESSED_SRGB; - // break; - //CLIMAX_MERGE_END default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -301,8 +345,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (srcFormat.getSemantic()) { case gpu::BGRA: case gpu::SBGRA: - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_BGRA"; - //texel.format = GL_BGRA; + texel.format = GL_RGBA; // GL_BGRA_EXT; break; case gpu::RGB: case gpu::RGBA: @@ -320,49 +363,19 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_RGBA8; break; case gpu::SRGB: - //texel.internalFormat = GL_SRGB8; - texel.internalFormat = GL_RGB8; - qDebug() << "SRGBA Here 3"; + texel.internalFormat = GL_SRGB8; break; case gpu::SRGBA: texel.internalFormat = GL_SRGB8_ALPHA8; break; - - //CLIMAX_MERGE_START - // case gpu::COMPRESSED_RGBA: - // //texel.internalFormat = GL_COMPRESSED_RGBA; - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_RGBA"; - // break; - // case gpu::COMPRESSED_SRGBA: - // //texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA; - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_SRGB_ALPHA"; - // break; - //CLIMAX_MERGE_END - // FIXME: WE will want to support this later - /* - case gpu::COMPRESSED_BC3_RGBA: - texel.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - break; - case gpu::COMPRESSED_BC3_SRGBA: - texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - break; - - case gpu::COMPRESSED_BC7_RGBA: - texel.internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; - break; - case gpu::COMPRESSED_BC7_SRGBA: - texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; - break; - */ - default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } return texel; } else { @@ -374,12 +387,8 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { - //CLIMAX_MERGE_START - // case gpu::COMPRESSED_R: { - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_RED_RGTC1"; - // //texel.internalFormat = GL_COMPRESSED_RED_RGTC1; - // break; - // } + + case gpu::RED: case gpu::RGB: case gpu::RGBA: case gpu::SRGB: @@ -415,13 +424,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; } case gpu::NUINT16: { - //texel.internalFormat = GL_R16; - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_R16"; + texel.internalFormat = GL_R16_EXT; break; } case gpu::NINT16: { - //texel.internalFormat = GL_R16_SNORM; - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_R16_SNORM"; + texel.internalFormat = GL_R16_SNORM_EXT; break; } case gpu::HALF: { @@ -438,9 +445,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::NUINT8: { if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { -// texel.internalFormat = GL_SLUMINANCE8; - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_SLUMINANCE8"; - + texel.internalFormat = GL_SLUMINANCE8_NV; } else { texel.internalFormat = GL_R8; } @@ -461,10 +466,16 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::R11G11B10: texel.format = GL_RGB; - // the type should be float + texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV; texel.internalFormat = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + texel.format = GL_RGB; + texel.type = GL_UNSIGNED_INT_5_9_9_9_REV; + texel.internalFormat = GL_RGB9_E5; + break; + case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT32_OES; @@ -497,6 +508,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::COMPRESSED: case gpu::NUINT2: + case gpu::NINT2_10_10_10: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -508,7 +520,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -521,10 +533,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getSemantic()) { case gpu::RGB: case gpu::RGBA: + case gpu::XY: texel.internalFormat = GL_RG8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -542,20 +555,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::SRGB: case gpu::SRGBA: - //texel.internalFormat = GL_SRGB8; // standard 2.2 gamma correction color - texel.internalFormat = GL_RGB8; // standard 2.2 gamma correction color + texel.internalFormat = GL_SRGB8; // standard 2.2 gamma correction color break; - //CLIMAX_MERGE_START - // case gpu::COMPRESSED_RGB: - // //texel.internalFormat = GL_COMPRESSED_RGB; - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_RGB"; - // break; - // case gpu::COMPRESSED_SRGB: - // //texel.internalFormat = GL_COMPRESSED_SRGB; - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_SRGB"; - // break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -592,13 +595,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::NUINT16: texel.format = GL_RGBA; - //texel.internalFormat = GL_RGBA16; - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_RGBA16"; + //texel.internalFormat = GL_RGBA16_EXT; break; case gpu::NINT16: texel.format = GL_RGBA; - qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_RGBA16_SNORM"; - //texel.internalFormat = GL_RGBA16_SNORM; + //texel.internalFormat = GL_RGBA16_SNORM_EXT; break; case gpu::HALF: texel.format = GL_RGBA; @@ -620,39 +621,32 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.format = GL_RGBA; texel.internalFormat = GL_RGBA8_SNORM; break; + case gpu::NUINT2: + texel.format = GL_RGBA; + texel.internalFormat = GL_RGBA8; + break; case gpu::NUINT32: case gpu::NINT32: + case gpu::NINT2_10_10_10: case gpu::COMPRESSED: - case gpu::NUINT2: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); } break; case gpu::SRGB: - //texel.internalFormat = GL_SRGB8; - texel.internalFormat = GL_RGB8; // standard 2.2 gamma correction color + texel.internalFormat = GL_SRGB8; break; case gpu::SRGBA: texel.internalFormat = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color break; - //CLIMAX_MERGE_START - // case gpu::COMPRESSED_RGBA: - // //texel.internalFormat = GL_COMPRESSED_RGBA; - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_RGBA"; - // break; - // case gpu::COMPRESSED_SRGBA: - // qDebug() << "TODO: GLTexelFormat.cpp:evalGLTexelFormat GL_COMPRESSED_SRGB_ALPHA"; - // //texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA; - // break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } default: qCDebug(gpugllogging) << "Unknown combination of texel format"; } - //qDebug() << "GLTexelFormat::evalGLTexelFormat Texel.type " << texel.type << " - texel.format=" << texel.format << " texel.internalFormat=" << texel.internalFormat; return texel; } } diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h index 94ded3dc23..8f37f6b604 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h +++ b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.h @@ -18,6 +18,11 @@ public: GLenum format; GLenum type; + GLTexelFormat(GLenum glinternalFormat, GLenum glformat, GLenum gltype) : internalFormat(glinternalFormat), format(glformat), type(gltype) {} + GLTexelFormat(GLenum glinternalFormat) : internalFormat(glinternalFormat) {} + + bool isCompressed() const; + static GLTexelFormat evalGLTexelFormat(const Element& dstFormat) { return evalGLTexelFormat(dstFormat, dstFormat); } diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp index 5f5e3a9be1..08b3f87094 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLTexture.cpp @@ -8,35 +8,28 @@ #include "GLTexture.h" +#include #include -#include "GLTextureTransfer.h" #include "GLBackend.h" using namespace gpu; using namespace gpu::gl; -std::shared_ptr GLTexture::_textureTransferHelper; -// FIXME placeholder for texture memory over-use -#define DEFAULT_MAX_MEMORY_MB 256 -#define MIN_FREE_GPU_MEMORY_PERCENTAGE 0.25f -#define OVER_MEMORY_PRESSURE 2.0f - -const GLenum GLTexture::CUBE_FACE_LAYOUT[6] = { +const GLenum GLTexture::CUBE_FACE_LAYOUT[GLTexture::TEXTURE_CUBE_NUM_FACES] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; + const GLenum GLTexture::WRAP_MODES[Sampler::NUM_WRAP_MODES] = { GL_REPEAT, // WRAP_REPEAT, GL_MIRRORED_REPEAT, // WRAP_MIRROR, GL_CLAMP_TO_EDGE, // WRAP_CLAMP, - GL_CLAMP_TO_BORDER_EXT, // WRAP_BORDER, - - //GL_MIRROR_CLAMP_TO_EDGE_EXT // WRAP_MIRROR_ONCE, -// qDebug() << "TODO: GLTexture.cpp:WRAP_MODES GL_MIRROR_CLAMP_TO_EDGE_EXT"; + GL_CLAMP_TO_BORDER, // WRAP_BORDER, + GL_MIRRORED_REPEAT //GL_MIRROR_CLAMP_TO_EDGE_EXT // WRAP_MIRROR_ONCE, }; const GLFilterMode GLTexture::FILTER_MODES[Sampler::NUM_FILTERS] = { @@ -73,6 +66,17 @@ GLenum GLTexture::getGLTextureType(const Texture& texture) { return GL_TEXTURE_2D; } +uint8_t GLTexture::getFaceCount(GLenum target) { + switch (target) { + case GL_TEXTURE_2D: + return TEXTURE_2D_NUM_FACES; + case GL_TEXTURE_CUBE_MAP: + return TEXTURE_CUBE_NUM_FACES; + default: + Q_UNREACHABLE(); + break; + } +} const std::vector& GLTexture::getFaceTargets(GLenum target) { static std::vector cubeFaceTargets { @@ -96,228 +100,602 @@ const std::vector& GLTexture::getFaceTargets(GLenum target) { return faceTargets; } -// Default texture memory = GPU total memory - 2GB -#define GPU_MEMORY_RESERVE_BYTES MB_TO_BYTES(2048) -// Minimum texture memory = 1GB -#define TEXTURE_MEMORY_MIN_BYTES MB_TO_BYTES(1024) - - -float GLTexture::getMemoryPressure() { - // Check for an explicit memory limit - auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); - - - // If no memory limit has been set, use a percentage of the total dedicated memory - if (!availableTextureMemory) { -#if 0 - auto totalMemory = getDedicatedMemory(); - if ((GPU_MEMORY_RESERVE_BYTES + TEXTURE_MEMORY_MIN_BYTES) > totalMemory) { - availableTextureMemory = TEXTURE_MEMORY_MIN_BYTES; - } else { - availableTextureMemory = totalMemory - GPU_MEMORY_RESERVE_BYTES; - } -#else - // Hardcode texture limit for sparse textures at 1 GB for now - availableTextureMemory = TEXTURE_MEMORY_MIN_BYTES; -#endif - } - - // Return the consumed texture memory divided by the available texture memory. - //CLIMAX_MERGE_START - //auto consumedGpuMemory = Context::getTextureGPUMemoryUsage() - Context::getTextureGPUFramebufferMemoryUsage(); - //float memoryPressure = (float)consumedGpuMemory / (float)availableTextureMemory; - //static Context::Size lastConsumedGpuMemory = 0; - //if (memoryPressure > 1.0f && lastConsumedGpuMemory != consumedGpuMemory) { - // lastConsumedGpuMemory = consumedGpuMemory; - // qCDebug(gpugllogging) << "Exceeded max allowed texture memory: " << consumedGpuMemory << " / " << availableTextureMemory; - //} - //return memoryPressure; - return 0; - -} - - -// Create the texture and allocate storage -GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable) : - GLObject(backend, texture, id), - _external(false), - _source(texture.source()), - _storageStamp(texture.getStamp()), - _target(getGLTextureType(texture)), - _internalFormat(gl::GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat())), - _maxMip(texture.getMaxMip()), - _minMip(texture.getMinMip()), - _virtualSize(texture.evalTotalSize()), - _transferrable(transferrable) -{ - //qDebug() << "GLTexture::GLTexture building GLTexture with _internalFormat" << _internalFormat; - auto strongBackend = _backend.lock(); - strongBackend->recycle(); - //CLIMAX_MERGE_START - //Backend::incrementTextureGPUCount(); - //Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); - //CLIMAX_MERGE_END - Backend::setGPUObject(texture, this); -} - GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) : - GLObject(backend, texture, id), - _external(true), - _source(texture.source()), - _storageStamp(0), - _target(getGLTextureType(texture)), - _internalFormat(GL_RGBA8), - // FIXME force mips to 0? - _maxMip(texture.getMaxMip()), - _minMip(texture.getMinMip()), - _virtualSize(0), - _transferrable(false) + GLObject(backend, texture, id), + _source(texture.source()), + _target(getGLTextureType(texture)), + _texelFormat(GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat())) { Backend::setGPUObject(texture, this); - - // FIXME Is this necessary? - //withPreservedTexture([this] { - // syncSampler(); - // if (_gpuObject.isAutogenerateMips()) { - // generateMips(); - // } - //}); } GLTexture::~GLTexture() { auto backend = _backend.lock(); - if (backend) { - if (_external) { - auto recycler = _gpuObject.getExternalRecycler(); - if (recycler) { - backend->releaseExternalTexture(_id, recycler); - } else { - qWarning() << "No recycler available for texture " << _id << " possible leak"; - } - } else if (_id) { - // WARNING! Sparse textures do not use this code path. See GL45BackendTexture for - // the GL45Texture destructor for doing any required work tracking GPU stats - backend->releaseTexture(_id, _size); - } - - ////CLIMAX_MERGE_START - //if (!_external && !_transferrable) { - // Backend::updateTextureGPUFramebufferMemoryUsage(_size, 0); - //} + if (backend && _id) { + backend->releaseTexture(_id, 0); } - //Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); - //CLIMAX_MERGE_END } -void GLTexture::createTexture() { - withPreservedTexture([&] { - allocateStorage(); - (void)CHECK_GL_ERROR(); - syncSampler(); - (void)CHECK_GL_ERROR(); +Size GLTexture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const { + if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { + return 0; + } + auto dim = _gpuObject.evalMipDimensions(sourceMip); + auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); + auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); + if (mipData) { + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat()); + return copyMipFaceLinesFromTexture(targetMip, face, dim, 0, texelFormat.internalFormat, texelFormat.format, texelFormat.type, mipSize, mipData->readData()); + } else { + qCDebug(gpugllogging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str(); + } + return 0; +} + + +GLExternalTexture::GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id) + : Parent(backend, texture, id) { + Backend::textureExternalCount.increment(); +} + +GLExternalTexture::~GLExternalTexture() { + auto backend = _backend.lock(); + if (backend) { + auto recycler = _gpuObject.getExternalRecycler(); + if (recycler) { + backend->releaseExternalTexture(_id, recycler); + } else { + qCWarning(gpugllogging) << "No recycler available for texture " << _id << " possible leak"; + } + const_cast(_id) = 0; + } + Backend::textureExternalCount.decrement(); +} + + +// Variable sized textures +using MemoryPressureState = GLVariableAllocationSupport::MemoryPressureState; +using WorkQueue = GLVariableAllocationSupport::WorkQueue; +using TransferJobPointer = GLVariableAllocationSupport::TransferJobPointer; + +std::list GLVariableAllocationSupport::_memoryManagedTextures; +MemoryPressureState GLVariableAllocationSupport::_memoryPressureState { MemoryPressureState::Idle }; +std::atomic GLVariableAllocationSupport::_memoryPressureStateStale { false }; +const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS { 64, 64, 1 }; +WorkQueue GLVariableAllocationSupport::_transferQueue; +WorkQueue GLVariableAllocationSupport::_promoteQueue; +WorkQueue GLVariableAllocationSupport::_demoteQueue; +size_t GLVariableAllocationSupport::_frameTexturesCreated { 0 }; + +#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f +#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f +#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024) + +static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB); + +using TransferJob = GLVariableAllocationSupport::TransferJob; + +const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS { 1024, 1024, 1 }; +const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.x * GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS.y * 4; + +#if THREADED_TEXTURE_BUFFERING + +TexturePointer GLVariableAllocationSupport::_currentTransferTexture; +TransferJobPointer GLVariableAllocationSupport::_currentTransferJob; +QThreadPool* TransferJob::_bufferThreadPool { nullptr }; + +void TransferJob::startBufferingThread() { + static std::once_flag once; + std::call_once(once, [&] { + _bufferThreadPool = new QThreadPool(qApp); + _bufferThreadPool->setMaxThreadCount(1); }); } -void GLTexture::withPreservedTexture(std::function f) const { - GLint boundTex = -1; - switch (_target) { - case GL_TEXTURE_2D: - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - break; +#endif - case GL_TEXTURE_CUBE_MAP: - glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); - break; +TransferJob::TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) + : _parent(parent) { - default: - qFatal("Unsupported texture type"); + auto transferDimensions = _parent._gpuObject.evalMipDimensions(sourceMip); + GLenum format; + GLenum internalFormat; + GLenum type; + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_parent._gpuObject.getTexelFormat(), _parent._gpuObject.getStoredMipFormat()); + format = texelFormat.format; + internalFormat = texelFormat.internalFormat; + type = texelFormat.type; + _transferSize = _parent._gpuObject.getStoredMipFaceSize(sourceMip, face); + + // If we're copying a subsection of the mip, do additional calculations to find the size and offset of the segment + if (0 != lines) { + transferDimensions.y = lines; + auto dimensions = _parent._gpuObject.evalMipDimensions(sourceMip); + auto bytesPerLine = (uint32_t)_transferSize / dimensions.y; + _transferOffset = bytesPerLine * lineOffset; + _transferSize = bytesPerLine * lines; } - (void)CHECK_GL_ERROR(); - glBindTexture(_target, _texture); - f(); - glBindTexture(_target, boundTex); - (void)CHECK_GL_ERROR(); + Backend::texturePendingGPUTransferMemSize.update(0, _transferSize); + + if (_transferSize > GLVariableAllocationSupport::MAX_TRANSFER_SIZE) { + qCWarning(gpugllogging) << "Transfer size of " << _transferSize << " exceeds theoretical maximum transfer size"; + } + + // Buffering can invoke disk IO, so it should be off of the main and render threads + _bufferingLambda = [=] { + auto mipStorage = _parent._gpuObject.accessStoredMipFace(sourceMip, face); + if (mipStorage) { + _mipData = mipStorage->createView(_transferSize, _transferOffset); + } else { + qCWarning(gpugllogging) << "Buffering failed because mip could not be retrieved from texture " << _parent._source.c_str() ; + } + }; + + _transferLambda = [=] { + if (_mipData) { + _parent.copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, internalFormat, format, type, _mipData->size(), _mipData->readData()); + _mipData.reset(); + } else { + qCWarning(gpugllogging) << "Transfer failed because mip could not be retrieved from texture " << _parent._source.c_str(); + } + }; } -void GLTexture::setSize(GLuint size) const { - ////CLIMAX_MERGE_START - //if (!_external && !_transferrable) { - // Backend::updateTextureGPUFramebufferMemoryUsage(_size, 0); - //} - //Backend::updateTextureGPUMemoryUsage(_size, size); - const_cast(_size) = size; +TransferJob::TransferJob(const GLTexture& parent, std::function transferLambda) + : _parent(parent), _bufferingRequired(false), _transferLambda(transferLambda) { } -bool GLTexture::isInvalid() const { - return _storageStamp < _gpuObject.getStamp(); +TransferJob::~TransferJob() { + Backend::texturePendingGPUTransferMemSize.update(_transferSize, 0); } -bool GLTexture::isOutdated() const { - return GLSyncState::Idle == _syncState && _contentStamp < _gpuObject.getDataStamp(); -} - -bool GLTexture::isReady() const { - // If we have an invalid texture, we're never ready - if (isInvalid()) { +bool TransferJob::tryTransfer() { +#if THREADED_TEXTURE_BUFFERING + // Are we ready to transfer + if (!bufferingCompleted()) { + startBuffering(); return false; } - - auto syncState = _syncState.load(); - if (isOutdated() || Idle != syncState) { - return false; +#else + if (_bufferingRequired) { + _bufferingLambda(); } - +#endif + _transferLambda(); return true; } +#if THREADED_TEXTURE_BUFFERING +bool TransferJob::bufferingRequired() const { + if (!_bufferingRequired) { + return false; + } -// Do any post-transfer operations that might be required on the main context / rendering thread -void GLTexture::postTransfer() { - //CLIMAX_MERGE_START - - // setSyncState(GLSyncState::Idle); - // ++_transferCount; + // The default state of a QFuture is with status Canceled | Started | Finished, + // so we have to check isCancelled before we check the actual state + if (_bufferingStatus.isCanceled()) { + return true; + } - // // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory - // switch (_gpuObject.getType()) { - // case Texture::TEX_2D: - // for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - // if (_gpuObject.isStoredMipFaceAvailable(i)) { - // _gpuObject.notifyMipFaceGPULoaded(i); - // } - // } - // break; - - // case Texture::TEX_CUBE: - // // transfer pixels from each faces - // for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - // for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - // if (_gpuObject.isStoredMipFaceAvailable(i, f)) { - // _gpuObject.notifyMipFaceGPULoaded(i, f); - // } - // } - // } - // break; - - // default: - // qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; - // break; - // } - //CLIMAX_MERGE_END + return !_bufferingStatus.isStarted(); } -void GLTexture::initTextureTransferHelper() { - _textureTransferHelper = std::make_shared(); +bool TransferJob::bufferingCompleted() const { + if (!_bufferingRequired) { + return true; + } + + // The default state of a QFuture is with status Canceled | Started | Finished, + // so we have to check isCancelled before we check the actual state + if (_bufferingStatus.isCanceled()) { + return false; + } + + return _bufferingStatus.isFinished(); } -void GLTexture::startTransfer() { - createTexture(); +void TransferJob::startBuffering() { + if (bufferingRequired()) { + assert(_bufferingStatus.isCanceled()); + _bufferingStatus = QtConcurrent::run(_bufferThreadPool, [=] { + _bufferingLambda(); + }); + assert(!_bufferingStatus.isCanceled()); + assert(_bufferingStatus.isStarted()); + } +} +#endif + +GLVariableAllocationSupport::GLVariableAllocationSupport() { + _memoryPressureStateStale = true; } -void GLTexture::finishTransfer() { - if (_gpuObject.isAutogenerateMips()) { - generateMips(); +GLVariableAllocationSupport::~GLVariableAllocationSupport() { + _memoryPressureStateStale = true; +} + +void GLVariableAllocationSupport::addMemoryManagedTexture(const TexturePointer& texturePointer) { + _memoryManagedTextures.push_back(texturePointer); + if (MemoryPressureState::Idle != _memoryPressureState) { + addToWorkQueue(texturePointer); } } +void GLVariableAllocationSupport::addToWorkQueue(const TexturePointer& texturePointer) { + GLTexture* gltexture = Backend::getGPUObject(*texturePointer); + GLVariableAllocationSupport* vargltexture = dynamic_cast(gltexture); + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + if (vargltexture->canDemote()) { + // Demote largest first + _demoteQueue.push({ texturePointer, (float)gltexture->size() }); + } + break; + + case MemoryPressureState::Undersubscribed: + if (vargltexture->canPromote()) { + // Promote smallest first + _promoteQueue.push({ texturePointer, 1.0f / (float)gltexture->size() }); + } + break; + + case MemoryPressureState::Transfer: + if (vargltexture->hasPendingTransfers()) { + // Transfer priority given to smaller mips first + _transferQueue.push({ texturePointer, 1.0f / (float)gltexture->_gpuObject.evalMipSize(vargltexture->_populatedMip) }); + } + break; + + case MemoryPressureState::Idle: + Q_UNREACHABLE(); + break; + } +} + +WorkQueue& GLVariableAllocationSupport::getActiveWorkQueue() { + static WorkQueue empty; + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + return _demoteQueue; + + case MemoryPressureState::Undersubscribed: + return _promoteQueue; + + case MemoryPressureState::Transfer: + return _transferQueue; + + case MemoryPressureState::Idle: + Q_UNREACHABLE(); + break; + } + return empty; +} + +// FIXME hack for stats display +QString getTextureMemoryPressureModeString() { + switch (GLVariableAllocationSupport::_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + return "Oversubscribed"; + + case MemoryPressureState::Undersubscribed: + return "Undersubscribed"; + + case MemoryPressureState::Transfer: + return "Transfer"; + + case MemoryPressureState::Idle: + return "Idle"; + } + Q_UNREACHABLE(); + return "Unknown"; +} + +void GLVariableAllocationSupport::updateMemoryPressure() { + static size_t lastAllowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + + size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); + if (0 == allowedMemoryAllocation) { + allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY; + } + + // If the user explicitly changed the allowed memory usage, we need to mark ourselves stale + // so that we react + if (allowedMemoryAllocation != lastAllowedMemoryAllocation) { + _memoryPressureStateStale = true; + lastAllowedMemoryAllocation = allowedMemoryAllocation; + } + + if (!_memoryPressureStateStale.exchange(false)) { + return; + } + + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + + // Clear any defunct textures (weak pointers that no longer have a valid texture) + _memoryManagedTextures.remove_if([&](const TextureWeakPointer& weakPointer) { + return weakPointer.expired(); + }); + + // Convert weak pointers to strong. This new list may still contain nulls if a texture was + // deleted on another thread between the previous line and this one + std::vector strongTextures; { + strongTextures.reserve(_memoryManagedTextures.size()); + std::transform( + _memoryManagedTextures.begin(), _memoryManagedTextures.end(), + std::back_inserter(strongTextures), + [](const TextureWeakPointer& p) { return p.lock(); }); + } + + size_t totalVariableMemoryAllocation = 0; + size_t idealMemoryAllocation = 0; + bool canDemote = false; + bool canPromote = false; + bool hasTransfers = false; + for (const auto& texture : strongTextures) { + // Race conditions can still leave nulls in the list, so we need to check + if (!texture) { + continue; + } + GLTexture* gltexture = Backend::getGPUObject(*texture); + GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); + // Track how much the texture thinks it should be using + idealMemoryAllocation += texture->evalTotalSize(); + // Track how much we're actually using + totalVariableMemoryAllocation += gltexture->size(); + canDemote |= vartexture->canDemote(); + canPromote |= vartexture->canPromote(); + hasTransfers |= vartexture->hasPendingTransfers(); + } + + size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; + float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation; + + auto newState = MemoryPressureState::Idle; + if (pressure < UNDERSUBSCRIBED_PRESSURE_VALUE && (unallocated != 0 && canPromote)) { + newState = MemoryPressureState::Undersubscribed; + } else if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) { + newState = MemoryPressureState::Oversubscribed; + } else if (hasTransfers) { + newState = MemoryPressureState::Transfer; + } + + if (newState != _memoryPressureState) { + _memoryPressureState = newState; + // Clear the existing queue + _transferQueue = WorkQueue(); + _promoteQueue = WorkQueue(); + _demoteQueue = WorkQueue(); + + // Populate the existing textures into the queue + if (_memoryPressureState != MemoryPressureState::Idle) { + for (const auto& texture : strongTextures) { + // Race conditions can still leave nulls in the list, so we need to check + if (!texture) { + continue; + } + addToWorkQueue(texture); + } + } + } +} + +TexturePointer GLVariableAllocationSupport::getNextWorkQueueItem(WorkQueue& workQueue) { + while (!workQueue.empty()) { + auto workTarget = workQueue.top(); + + auto texture = workTarget.first.lock(); + if (!texture) { + workQueue.pop(); + continue; + } + + // Check whether the resulting texture can actually have work performed + GLTexture* gltexture = Backend::getGPUObject(*texture); + GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + if (vartexture->canDemote()) { + return texture; + } + break; + + case MemoryPressureState::Undersubscribed: + if (vartexture->canPromote()) { + return texture; + } + break; + + case MemoryPressureState::Transfer: + if (vartexture->hasPendingTransfers()) { + return texture; + } + break; + + case MemoryPressureState::Idle: + Q_UNREACHABLE(); + break; + } + + // If we got here, then the texture has no work to do in the current state, + // so pop it off the queue and continue + workQueue.pop(); + } + + return TexturePointer(); +} + +void GLVariableAllocationSupport::processWorkQueue(WorkQueue& workQueue) { + if (workQueue.empty()) { + return; + } + + // Get the front of the work queue to perform work + auto texture = getNextWorkQueueItem(workQueue); + if (!texture) { + return; + } + + // Grab the first item off the demote queue + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + + GLTexture* gltexture = Backend::getGPUObject(*texture); + GLVariableAllocationSupport* vartexture = dynamic_cast(gltexture); + switch (_memoryPressureState) { + case MemoryPressureState::Oversubscribed: + vartexture->demote(); + workQueue.pop(); + addToWorkQueue(texture); + _memoryPressureStateStale = true; + break; + + case MemoryPressureState::Undersubscribed: + vartexture->promote(); + workQueue.pop(); + addToWorkQueue(texture); + _memoryPressureStateStale = true; + break; + + case MemoryPressureState::Transfer: + if (vartexture->executeNextTransfer(texture)) { + workQueue.pop(); + addToWorkQueue(texture); + +#if THREADED_TEXTURE_BUFFERING + // Eagerly start the next buffering job if possible + texture = getNextWorkQueueItem(workQueue); + if (texture) { + gltexture = Backend::getGPUObject(*texture); + vartexture = dynamic_cast(gltexture); + vartexture->executeNextBuffer(texture); + } +#endif + } + break; + + case MemoryPressureState::Idle: + Q_UNREACHABLE(); + break; + } +} + +void GLVariableAllocationSupport::processWorkQueues() { + if (MemoryPressureState::Idle == _memoryPressureState) { + return; + } + + auto& workQueue = getActiveWorkQueue(); + // Do work on the front of the queue + processWorkQueue(workQueue); + + if (workQueue.empty()) { + _memoryPressureState = MemoryPressureState::Idle; + _memoryPressureStateStale = true; + } +} + +void GLVariableAllocationSupport::manageMemory() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + updateMemoryPressure(); + processWorkQueues(); +} + +bool GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& currentTexture) { +#if THREADED_TEXTURE_BUFFERING + // If a transfer job is active on the buffering thread, but has not completed it's buffering lambda, + // then we need to exit early, since we don't want to have the transfer job leave scope while it's + // being used in another thread -- See https://highfidelity.fogbugz.com/f/cases/4626 + if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) { + return false; + } +#endif + + if (_populatedMip <= _allocatedMip) { +#if THREADED_TEXTURE_BUFFERING + _currentTransferJob.reset(); + _currentTransferTexture.reset(); +#endif + return true; + } + + // If the transfer queue is empty, rebuild it + if (_pendingTransfers.empty()) { + populateTransferQueue(); + } + + bool result = false; + if (!_pendingTransfers.empty()) { +#if THREADED_TEXTURE_BUFFERING + // If there is a current transfer, but it's not the top of the pending transfer queue, then it's an orphan, so we want to abandon it. + if (_currentTransferJob && _currentTransferJob != _pendingTransfers.front()) { + _currentTransferJob.reset(); + } + + if (!_currentTransferJob) { + // Keeping hold of a strong pointer to the transfer job ensures that if the pending transfer queue is rebuilt, the transfer job + // doesn't leave scope, causing a crash in the buffering thread + _currentTransferJob = _pendingTransfers.front(); + + // Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture + _currentTransferTexture = currentTexture; + } + + // transfer jobs use asynchronous buffering of the texture data because it may involve disk IO, so we execute a try here to determine if the buffering + // is complete + if (_currentTransferJob->tryTransfer()) { + _pendingTransfers.pop(); + // Once a given job is finished, release the shared pointers keeping them alive + _currentTransferTexture.reset(); + _currentTransferJob.reset(); + result = true; + } +#else + if (_pendingTransfers.front()->tryTransfer()) { + _pendingTransfers.pop(); + result = true; + } +#endif + } + return result; +} + +#if THREADED_TEXTURE_BUFFERING +void GLVariableAllocationSupport::executeNextBuffer(const TexturePointer& currentTexture) { + if (_currentTransferJob && !_currentTransferJob->bufferingCompleted()) { + return; + } + + // If the transfer queue is empty, rebuild it + if (_pendingTransfers.empty()) { + populateTransferQueue(); + } + + if (!_pendingTransfers.empty()) { + if (!_currentTransferJob) { + _currentTransferJob = _pendingTransfers.front(); + _currentTransferTexture = currentTexture; + } + + _currentTransferJob->startBuffering(); + } +} +#endif + +void GLVariableAllocationSupport::incrementPopulatedSize(Size delta) const { + _populatedSize += delta; + // Keep the 2 code paths to be able to debug + if (_size < _populatedSize) { + Backend::textureResourcePopulatedGPUMemSize.update(0, delta); + } else { + Backend::textureResourcePopulatedGPUMemSize.update(0, delta); + } +} +void GLVariableAllocationSupport::decrementPopulatedSize(Size delta) const { + _populatedSize -= delta; + // Keep the 2 code paths to be able to debug + if (_size < _populatedSize) { + Backend::textureResourcePopulatedGPUMemSize.update(delta, 0); + } else { + Backend::textureResourcePopulatedGPUMemSize.update(delta, 0); + } +} \ No newline at end of file diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexture.h b/libraries/gpu-gles/src/gpu/gl/GLTexture.h index 03353ae67d..ce27d02033 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLTexture.h +++ b/libraries/gpu-gles/src/gpu/gl/GLTexture.h @@ -8,10 +8,15 @@ #ifndef hifi_gpu_gl_GLTexture_h #define hifi_gpu_gl_GLTexture_h +#include +#include + #include "GLShared.h" -#include "GLTextureTransfer.h" #include "GLBackend.h" #include "GLTexelFormat.h" +#include + +#define THREADED_TEXTURE_BUFFERING 1 namespace gpu { namespace gl { @@ -20,214 +25,187 @@ struct GLFilterMode { GLint magFilter; }; +class GLVariableAllocationSupport { + friend class GLBackend; + +public: + GLVariableAllocationSupport(); + virtual ~GLVariableAllocationSupport(); + + enum class MemoryPressureState { + Idle, + Transfer, + Oversubscribed, + Undersubscribed, + }; + + using QueuePair = std::pair; + struct QueuePairLess { + bool operator()(const QueuePair& a, const QueuePair& b) { + return a.second < b.second; + } + }; + using WorkQueue = std::priority_queue, QueuePairLess>; + + class TransferJob { + using VoidLambda = std::function; + using VoidLambdaQueue = std::queue; + const GLTexture& _parent; + Texture::PixelsPointer _mipData; + size_t _transferOffset { 0 }; + size_t _transferSize { 0 }; + + bool _bufferingRequired { true }; + VoidLambda _transferLambda; + VoidLambda _bufferingLambda; + +#if THREADED_TEXTURE_BUFFERING + // Indicates if a transfer from backing storage to interal storage has started + QFuture _bufferingStatus; + static QThreadPool* _bufferThreadPool; +#endif + + public: + TransferJob(const TransferJob& other) = delete; + TransferJob(const GLTexture& parent, std::function transferLambda); + TransferJob(const GLTexture& parent, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines = 0, uint32_t lineOffset = 0); + ~TransferJob(); + bool tryTransfer(); + +#if THREADED_TEXTURE_BUFFERING + void startBuffering(); + bool bufferingRequired() const; + bool bufferingCompleted() const; + static void startBufferingThread(); +#endif + + private: + void transfer(); + }; + + using TransferJobPointer = std::shared_ptr; + using TransferQueue = std::queue; + static MemoryPressureState _memoryPressureState; + +public: + static void addMemoryManagedTexture(const TexturePointer& texturePointer); + +protected: + static size_t _frameTexturesCreated; + static std::atomic _memoryPressureStateStale; + static std::list _memoryManagedTextures; + static WorkQueue _transferQueue; + static WorkQueue _promoteQueue; + static WorkQueue _demoteQueue; +#if THREADED_TEXTURE_BUFFERING + static TexturePointer _currentTransferTexture; + static TransferJobPointer _currentTransferJob; +#endif + static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS; + static const uvec3 MAX_TRANSFER_DIMENSIONS; + static const size_t MAX_TRANSFER_SIZE; + + + static void updateMemoryPressure(); + static void processWorkQueues(); + static void processWorkQueue(WorkQueue& workQueue); + static TexturePointer getNextWorkQueueItem(WorkQueue& workQueue); + static void addToWorkQueue(const TexturePointer& texture); + static WorkQueue& getActiveWorkQueue(); + + static void manageMemory(); + + //bool canPromoteNoAllocate() const { return _allocatedMip < _populatedMip; } + bool canPromote() const { return _allocatedMip > _minAllocatedMip; } + bool canDemote() const { return _allocatedMip < _maxAllocatedMip; } + bool hasPendingTransfers() const { return _populatedMip > _allocatedMip; } +#if THREADED_TEXTURE_BUFFERING + void executeNextBuffer(const TexturePointer& currentTexture); +#endif + bool executeNextTransfer(const TexturePointer& currentTexture); + virtual void populateTransferQueue() = 0; + virtual void promote() = 0; + virtual void demote() = 0; + + // THe amount of memory currently allocated + Size _size { 0 }; + + // The amount of memory currnently populated + void incrementPopulatedSize(Size delta) const; + void decrementPopulatedSize(Size delta) const; + mutable Size _populatedSize { 0 }; + + // The allocated mip level, relative to the number of mips in the gpu::Texture object + // The relationship between a given glMip to the original gpu::Texture mip is always + // glMip + _allocatedMip + uint16 _allocatedMip { 0 }; + // The populated mip level, relative to the number of mips in the gpu::Texture object + // This must always be >= the allocated mip + uint16 _populatedMip { 0 }; + // The highest (lowest resolution) mip that we will support, relative to the number + // of mips in the gpu::Texture object + uint16 _maxAllocatedMip { 0 }; + // The lowest (highest resolution) mip that we will support, relative to the number + // of mips in the gpu::Texture object + uint16 _minAllocatedMip { 0 }; + // Contains a series of lambdas that when executed will transfer data to the GPU, modify + // the _populatedMip and update the sampler in order to fully populate the allocated texture + // until _populatedMip == _allocatedMip + TransferQueue _pendingTransfers; +}; class GLTexture : public GLObject { + using Parent = GLObject; + friend class GLBackend; + friend class GLVariableAllocationSupport; public: static const uint16_t INVALID_MIP { (uint16_t)-1 }; static const uint8_t INVALID_FACE { (uint8_t)-1 }; - static void initTextureTransferHelper(); - static std::shared_ptr _textureTransferHelper; - - template - static GLTexture* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) { - const Texture& texture = *texturePointer; - - // Special case external textures - //CLIMAX_MERGE_START - //Z:/HiFi_Android/HiFi_GIT/libraries/gpu-gl-android/src/gpu/gl/../gles/../gl/GLTexture.h:37:32: error: no member named 'isExternal' in 'gpu::Texture::Usage' - // The only instance of this being used again. replace. - // if (texture.getUsage().isExternal()) { - // Texture::ExternalUpdates updates = texture.getUpdates(); - // if (!updates.empty()) { - // Texture::ExternalRecycler recycler = texture.getExternalRecycler(); - // Q_ASSERT(recycler); - // // Discard any superfluous updates - // while (updates.size() > 1) { - // const auto& update = updates.front(); - // // Superfluous updates will never have been read, but we want to ensure the previous - // // writes to them are complete before they're written again, so return them with the - // // same fences they arrived with. This can happen on any thread because no GL context - // // work is involved - // recycler(update.first, update.second); - // updates.pop_front(); - // } - - // // The last texture remaining is the one we'll use to create the GLTexture - // const auto& update = updates.front(); - // // Check for a fence, and if it exists, inject a wait into the command stream, then destroy the fence - // if (update.second) { - // GLsync fence = static_cast(update.second); - // glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); - // glDeleteSync(fence); - // } - - // // Create the new texture object (replaces any previous texture object) - // new GLTextureType(backend.shared_from_this(), texture, update.first); - // } - - - // Return the texture object (if any) associated with the texture, without extensive logic - // (external textures are - //return Backend::getGPUObject(texture); - //} - //CLIMAX_MERGE_END - if (!texture.isDefined()) { - // NO texture definition yet so let's avoid thinking - return nullptr; - } - - // If the object hasn't been created, or the object definition is out of date, drop and re-create - GLTexture* object = Backend::getGPUObject(texture); - - // Create the texture if need be (force re-creation if the storage stamp changes - // for easier use of immutable storage) - if (!object || object->isInvalid()) { - // This automatically any previous texture - object = new GLTextureType(backend.shared_from_this(), texture, needTransfer); - if (!object->_transferrable) { - object->createTexture(); - object->_contentStamp = texture.getDataStamp(); - object->updateSize(); - object->postTransfer(); - } - } - - // Object maybe doens't neet to be tranasferred after creation - if (!object->_transferrable) { - return object; - } - - // If we just did a transfer, return the object after doing post-transfer work - if (GLSyncState::Transferred == object->getSyncState()) { - object->postTransfer(); - } - - if (object->isOutdated()) { - // Object might be outdated, if so, start the transfer - // (outdated objects that are already in transfer will have reported 'true' for ready() - _textureTransferHelper->transferTexture(texturePointer); - return nullptr; - } - - if (!object->isReady()) { - return nullptr; - } - - ((GLTexture*)object)->updateMips(); - - return object; - } - - template - static GLuint getId(GLBackend& backend, const TexturePointer& texture, bool shouldSync) { - if (!texture) { - return 0; - } - GLTexture* object { nullptr }; - if (shouldSync) { - object = sync(backend, texture, shouldSync); - } else { - object = Backend::getGPUObject(*texture); - } - - if (!object) { - return 0; - } - - if (!shouldSync) { - return object->_id; - } - - // Don't return textures that are in transfer state - if ((object->getSyncState() != GLSyncState::Idle) || - // Don't return transferrable textures that have never completed transfer - (!object->_transferrable || 0 != object->_transferCount)) { - return 0; - } - - return object->_id; - } - ~GLTexture(); - // Is this texture generated outside the GPU library? - const bool _external; const GLuint& _texture { _id }; const std::string _source; - const Stamp _storageStamp; const GLenum _target; - const GLenum _internalFormat; - const uint16 _maxMip; - uint16 _minMip; - const GLuint _virtualSize; // theoretical size as expected - Stamp _contentStamp { 0 }; - const bool _transferrable; - Size _transferCount { 0 }; - GLuint size() const { return _size; } - GLSyncState getSyncState() const { return _syncState; } + GLTexelFormat _texelFormat; - // Is the storage out of date relative to the gpu texture? - bool isInvalid() const; + static const std::vector& getFaceTargets(GLenum textureType); + static uint8_t getFaceCount(GLenum textureType); + static GLenum getGLTextureType(const Texture& texture); - // Is the content out of date relative to the gpu texture? - bool isOutdated() const; - - // Is the texture in a state where it can be rendered with no work? - bool isReady() const; - - // Execute any post-move operations that must occur only on the main thread - virtual void postTransfer(); - - uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } - - static const size_t CUBE_NUM_FACES = 6; - static const GLenum CUBE_FACE_LAYOUT[6]; + static const uint8_t TEXTURE_2D_NUM_FACES = 1; + static const uint8_t TEXTURE_CUBE_NUM_FACES = 6; + static const GLenum CUBE_FACE_LAYOUT[TEXTURE_CUBE_NUM_FACES]; static const GLFilterMode FILTER_MODES[Sampler::NUM_FILTERS]; static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES]; - // Return a floating point value indicating how much of the allowed - // texture memory we are currently consuming. A value of 0 indicates - // no texture memory usage, while a value of 1 indicates all available / allowed memory - // is consumed. A value above 1 indicates that there is a problem. - static float getMemoryPressure(); protected: - - static const std::vector& getFaceTargets(GLenum textureType); - - static GLenum getGLTextureType(const Texture& texture); - - - const GLuint _size { 0 }; // true size as reported by the gl api - std::atomic _syncState { GLSyncState::Idle }; - - GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable); - GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); - - void setSyncState(GLSyncState syncState) { _syncState = syncState; } - - void createTexture(); - - virtual void updateMips() {} - virtual void allocateStorage() const = 0; - virtual void updateSize() const = 0; - virtual void syncSampler() const = 0; + virtual Size size() const = 0; virtual void generateMips() const = 0; - virtual void withPreservedTexture(std::function f) const; + virtual void syncSampler() const = 0; -protected: - void setSize(GLuint size) const; + virtual Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const = 0; + virtual Size copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face) const final; + virtual void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {} // Only relevant for Variable Allocation textures - virtual void startTransfer(); - // Returns true if this is the last block required to complete transfer - virtual bool continueTransfer() { return false; } - virtual void finishTransfer(); - -private: - friend class GLTextureTransferHelper; - friend class GLBackend; + GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); }; +class GLExternalTexture : public GLTexture { + using Parent = GLTexture; + friend class GLBackend; +public: + ~GLExternalTexture(); +protected: + GLExternalTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id); + void generateMips() const override {} + void syncSampler() const override {} + Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override { return 0;} + + Size size() const override { return 0; } +}; + + } } #endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gles/src/gpu/gl/GLTextureTransfer.cpp deleted file mode 100644 index cec46cb90d..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLTextureTransfer.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/04/03 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLTextureTransfer.h" - -#include -#include - -#include "GLShared.h" -#include "GLTexture.h" - -#ifdef HAVE_NSIGHT -#include "nvToolsExt.h" -std::unordered_map _map; -#endif - - -#ifdef TEXTURE_TRANSFER_PBOS -#define TEXTURE_TRANSFER_BLOCK_SIZE (64 * 1024) -#define TEXTURE_TRANSFER_PBO_COUNT 128 -#endif - -using namespace gpu; -using namespace gpu::gl; - -GLTextureTransferHelper::GLTextureTransferHelper() { -#ifdef THREADED_TEXTURE_TRANSFER - setObjectName("TextureTransferThread"); - _context.create(); - initialize(true, QThread::LowPriority); - // Clean shutdown on UNIX, otherwise _canvas is freed early - connect(qApp, &QCoreApplication::aboutToQuit, [&] { terminate(); }); -#else - initialize(false, QThread::LowPriority); -#endif -} - -GLTextureTransferHelper::~GLTextureTransferHelper() { -#ifdef THREADED_TEXTURE_TRANSFER - if (isStillRunning()) { - terminate(); - } -#else - terminate(); -#endif -} - -void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { - GLTexture* object = Backend::getGPUObject(*texturePointer); - - //CLIMAX_MERGE_START - //Backend::incrementTextureGPUTransferCount(); - object->setSyncState(GLSyncState::Pending); - Lock lock(_mutex); - _pendingTextures.push_back(texturePointer); -} - -void GLTextureTransferHelper::setup() { -#ifdef THREADED_TEXTURE_TRANSFER - _context.makeCurrent(); - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - // FIXME don't use opengl 4.5 DSA functionality without verifying it's present - glCreateRenderbuffers(1, &_drawRenderbuffer); - glNamedRenderbufferStorage(_drawRenderbuffer, GL_RGBA8, 128, 128); - glCreateFramebuffers(1, &_drawFramebuffer); - glNamedFramebufferRenderbuffer(_drawFramebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _drawRenderbuffer); - glCreateFramebuffers(1, &_readFramebuffer); -#endif - -#ifdef TEXTURE_TRANSFER_PBOS - std::array pbos; - glCreateBuffers(TEXTURE_TRANSFER_PBO_COUNT, &pbos[0]); - for (uint32_t i = 0; i < TEXTURE_TRANSFER_PBO_COUNT; ++i) { - TextureTransferBlock newBlock; - newBlock._pbo = pbos[i]; - glNamedBufferStorage(newBlock._pbo, TEXTURE_TRANSFER_BLOCK_SIZE, 0, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - newBlock._mapped = glMapNamedBufferRange(newBlock._pbo, 0, TEXTURE_TRANSFER_BLOCK_SIZE, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); - _readyQueue.push(newBlock); - } -#endif -#endif -} - -void GLTextureTransferHelper::shutdown() { -#ifdef THREADED_TEXTURE_TRANSFER - _context.makeCurrent(); -#endif - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - glNamedFramebufferRenderbuffer(_drawFramebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0); - glDeleteFramebuffers(1, &_drawFramebuffer); - _drawFramebuffer = 0; - glDeleteFramebuffers(1, &_readFramebuffer); - _readFramebuffer = 0; - - glNamedFramebufferTexture(_readFramebuffer, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0); - glDeleteRenderbuffers(1, &_drawRenderbuffer); - _drawRenderbuffer = 0; -#endif -} - -void GLTextureTransferHelper::queueExecution(VoidLambda lambda) { - Lock lock(_mutex); - _pendingCommands.push_back(lambda); -} - -#define MAX_TRANSFERS_PER_PASS 2 - -bool GLTextureTransferHelper::process() { - // Take any new textures or commands off the queue - VoidLambdaList pendingCommands; - TextureList newTransferTextures; - { - Lock lock(_mutex); - newTransferTextures.swap(_pendingTextures); - pendingCommands.swap(_pendingCommands); - } - - if (!pendingCommands.empty()) { - for (auto command : pendingCommands) { - command(); - } - glFlush(); - } - - if (!newTransferTextures.empty()) { - for (auto& texturePointer : newTransferTextures) { -#ifdef HAVE_NSIGHT - _map[texturePointer] = nvtxRangeStart("TextureTansfer"); -#endif - GLTexture* object = Backend::getGPUObject(*texturePointer); - object->startTransfer(); - _transferringTextures.push_back(texturePointer); - _textureIterator = _transferringTextures.begin(); - } - _transferringTextures.sort([](const gpu::TexturePointer& a, const gpu::TexturePointer& b)->bool { - return a->getSize() < b->getSize(); - }); - } - - // No transfers in progress, sleep - if (_transferringTextures.empty()) { -#ifdef THREADED_TEXTURE_TRANSFER - QThread::usleep(1); -#endif - return true; - } - - static auto lastReport = usecTimestampNow(); - auto now = usecTimestampNow(); - auto lastReportInterval = now - lastReport; - if (lastReportInterval > USECS_PER_SECOND * 4) { - lastReport = now; - qDebug() << "Texture list " << _transferringTextures.size(); - } - - size_t transferCount = 0; - for (_textureIterator = _transferringTextures.begin(); _textureIterator != _transferringTextures.end();) { - if (++transferCount > MAX_TRANSFERS_PER_PASS) { - break; - } - auto texture = *_textureIterator; - GLTexture* gltexture = Backend::getGPUObject(*texture); - if (gltexture->continueTransfer()) { - ++_textureIterator; - continue; - } - - gltexture->finishTransfer(); - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - // FIXME force a draw on the texture transfer thread before passing the texture to the main thread for use -#endif - -#ifdef THREADED_TEXTURE_TRANSFER - clientWait(); -#endif - gltexture->_contentStamp = gltexture->_gpuObject.getDataStamp(); - gltexture->updateSize(); - gltexture->setSyncState(gpu::gl::GLSyncState::Transferred); - //CLIMAX_MERGE_START - //Backend::decrementTextureGPUTransferCount(); -#ifdef HAVE_NSIGHT - // Mark the texture as transferred - nvtxRangeEnd(_map[texture]); - _map.erase(texture); -#endif - _textureIterator = _transferringTextures.erase(_textureIterator); - } - -#ifdef THREADED_TEXTURE_TRANSFER - if (!_transferringTextures.empty()) { - // Don't saturate the GPU - clientWait(); - } else { - // Don't saturate the CPU - QThread::msleep(1); - } -#endif - - return true; -} diff --git a/libraries/gpu-gles/src/gpu/gl/GLTextureTransfer.h b/libraries/gpu-gles/src/gpu/gl/GLTextureTransfer.h deleted file mode 100644 index a23c282fd4..0000000000 --- a/libraries/gpu-gles/src/gpu/gl/GLTextureTransfer.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/04/03 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_gl_GLTextureTransfer_h -#define hifi_gpu_gl_GLTextureTransfer_h - -#include -#include - -#include - -#include - -#include "GLShared.h" - -#ifdef Q_OS_WIN -#define THREADED_TEXTURE_TRANSFER -#endif - -#ifdef THREADED_TEXTURE_TRANSFER -// FIXME when sparse textures are enabled, it's harder to force a draw on the transfer thread -// also, the current draw code is implicitly using OpenGL 4.5 functionality -//#define TEXTURE_TRANSFER_FORCE_DRAW -// FIXME PBO's increase the complexity and don't seem to work reliably -//#define TEXTURE_TRANSFER_PBOS -#endif - -namespace gpu { namespace gl { - -using TextureList = std::list; -using TextureListIterator = TextureList::iterator; - -class GLTextureTransferHelper : public GenericThread { -public: - using VoidLambda = std::function; - using VoidLambdaList = std::list; - using Pointer = std::shared_ptr; - GLTextureTransferHelper(); - ~GLTextureTransferHelper(); - void transferTexture(const gpu::TexturePointer& texturePointer); - void queueExecution(VoidLambda lambda); - - void setup() override; - void shutdown() override; - bool process() override; - -private: -#ifdef THREADED_TEXTURE_TRANSFER - ::gl::OffscreenContext _context; -#endif - -#ifdef TEXTURE_TRANSFER_FORCE_DRAW - // Framebuffers / renderbuffers for forcing access to the texture on the transfer thread - GLuint _drawRenderbuffer { 0 }; - GLuint _drawFramebuffer { 0 }; - GLuint _readFramebuffer { 0 }; -#endif - - // A mutex for protecting items access on the render and transfer threads - Mutex _mutex; - // Commands that have been submitted for execution on the texture transfer thread - VoidLambdaList _pendingCommands; - // Textures that have been submitted for transfer - TextureList _pendingTextures; - // Textures currently in the transfer process - // Only used on the transfer thread - TextureList _transferringTextures; - TextureListIterator _textureIterator; - -}; - -} } - -#endif \ No newline at end of file diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 69a417d952..aeda054e72 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -27,6 +27,12 @@ class GLESBackend : public GLBackend { friend class Context; public: + static const GLint TRANSFORM_OBJECT_SLOT { 31 }; + static const GLint RESOURCE_TRANSFER_TEX_UNIT { 32 }; + static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 }; + static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 }; + static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 }; + static bool supportedTextureFormat(const gpu::Element& format); explicit GLESBackend(bool syncCache) : Parent(syncCache) {} GLESBackend() : Parent() {} virtual ~GLESBackend() { @@ -38,33 +44,95 @@ public: static const std::string GLES_VERSION; const std::string& getVersion() const override { return GLES_VERSION; } - class GLESTexture : public GLTexture { using Parent = GLTexture; - GLuint allocate(); - public: - GLESTexture(const std::weak_ptr& backend, const Texture& buffer, GLuint externalId); - GLESTexture(const std::weak_ptr& backend, const Texture& buffer, bool transferrable); - + friend class GLESBackend; + GLuint allocate(const Texture& texture); protected: - void transferMip(uint16_t mipLevel, uint8_t face) const; - void startTransfer() override; - void allocateStorage() const override; - void updateSize() const override; - void syncSampler() const override; + GLESTexture(const std::weak_ptr& backend, const Texture& buffer); void generateMips() const override; + Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override; + void syncSampler() const override; + + void withPreservedTexture(std::function f) const; }; + // + // Textures that have fixed allocation sizes and cannot be managed at runtime + // + + class GLESFixedAllocationTexture : public GLESTexture { + using Parent = GLESTexture; + friend class GLESBackend; + + public: + GLESFixedAllocationTexture(const std::weak_ptr& backend, const Texture& texture); + ~GLESFixedAllocationTexture(); + + protected: + Size size() const override { return _size; } + void allocateStorage() const; + void syncSampler() const override; + const Size _size { 0 }; + }; + + class GLESAttachmentTexture : public GLESFixedAllocationTexture { + using Parent = GLESFixedAllocationTexture; + friend class GLESBackend; + protected: + GLESAttachmentTexture(const std::weak_ptr& backend, const Texture& texture); + ~GLESAttachmentTexture(); + }; + + class GLESStrictResourceTexture : public GLESFixedAllocationTexture { + using Parent = GLESFixedAllocationTexture; + friend class GLESBackend; + protected: + GLESStrictResourceTexture(const std::weak_ptr& backend, const Texture& texture); + ~GLESStrictResourceTexture(); + }; + + class GLESVariableAllocationTexture : public GLESTexture, public GLVariableAllocationSupport { + using Parent = GLESTexture; + friend class GLESBackend; + using PromoteLambda = std::function; + + + protected: + GLESVariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture); + ~GLESVariableAllocationTexture(); + + void allocateStorage(uint16 allocatedMip); + void syncSampler() const override; + void promote() override; + void demote() override; + void populateTransferQueue() override; + + Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override; + Size copyMipsFromTexture(); + + void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) override; + + Size size() const override { return _size; } + }; + + class GLESResourceTexture : public GLESVariableAllocationTexture { + using Parent = GLESVariableAllocationTexture; + friend class GLESBackend; + protected: + GLESResourceTexture(const std::weak_ptr& backend, const Texture& texture); + ~GLESResourceTexture(); + }; protected: GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; + GLuint getResourceBufferID(const Buffer& buffer); GLBuffer* syncGPUObject(const Buffer& buffer) override; - GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; - GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + GLTexture* syncGPUObject(const TexturePointer& texture) override; GLuint getQueryID(const QueryPointer& query) override; GLQuery* syncGPUObject(const Query& query) override; @@ -78,17 +146,25 @@ protected: void do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOffset) override; // Input Stage - void updateInput() override; void resetInputStage() override; + void updateInput() override; // Synchronize the state cache of this Backend with the actual real state of the GL Context void transferTransformState(const Batch& batch) const override; void initTransform() override; - void updateTransform(const Batch& batch); - void resetTransformStage(); + void updateTransform(const Batch& batch) override; + + // Resource Stage + bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override; + void releaseResourceBuffer(uint32_t slot) override; // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; + + std::string getBackendShaderHeader() const override; + void makeProgramBindings(ShaderObject& shaderObject) override; + int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override; + }; } } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp index f6bdea45af..05bda34d7f 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendBuffer.cpp @@ -67,3 +67,27 @@ GLuint GLESBackend::getBufferID(const Buffer& buffer) { GLBuffer* GLESBackend::syncGPUObject(const Buffer& buffer) { return GLESBuffer::sync(*this, buffer); } + +bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) { + GLBuffer* object = syncGPUObject((*buffer)); + if (object) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id); + + (void)CHECK_GL_ERROR(); + + _resource._buffers[slot] = buffer; + + return true; + } + + return false; +} + +void GLESBackend::releaseResourceBuffer(uint32_t slot) { + auto& buf = _resource._buffers[slot]; + if (buf) { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0); + buf.reset(); + } +} + diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp index 0b7c525fbb..dc4025247e 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp @@ -53,10 +53,12 @@ public: GL_COLOR_ATTACHMENT15 }; int unit = 0; + auto backend = _backend.lock(); for (auto& b : _gpuObject.getRenderBuffers()) { surface = b._texture; if (surface) { - gltexture = gl::GLTexture::sync(*_backend.lock().get(), surface, false); // Grab the gltexture and don't transfer + Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); + gltexture = backend->syncGPUObject(surface); } else { gltexture = nullptr; } @@ -81,9 +83,11 @@ public: } if (_gpuObject.getDepthStamp() != _depthStamp) { + auto backend = _backend.lock(); auto surface = _gpuObject.getDepthStencilBuffer(); if (_gpuObject.hasDepthStencil() && surface) { - gltexture = gl::GLTexture::sync(*_backend.lock().get(), surface, false); // Grab the gltexture and don't transfer + Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType()); + gltexture = backend->syncGPUObject(surface); } if (gltexture) { @@ -99,8 +103,8 @@ public: if (!_colorBuffers.empty()) { glDrawBuffers((GLsizei)_colorBuffers.size(), _colorBuffers.data()); } else { - static const std::vector NO_BUFFERS{ GL_NONE }; - glDrawBuffers((GLsizei)NO_BUFFERS.size(), NO_BUFFERS.data()); + GLenum DrawBuffers[1] = {GL_NONE}; + glDrawBuffers(1, DrawBuffers); } // Now check for completness @@ -111,7 +115,7 @@ public: glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); } - checkStatus(GL_DRAW_FRAMEBUFFER); + checkStatus(); } @@ -120,7 +124,7 @@ public: : Parent(backend, framebuffer, allocate()) { } }; -gl::GLFramebuffer* gpu::gles::GLESBackend::syncGPUObject(const Framebuffer& framebuffer) { +gl::GLFramebuffer* GLESBackend::syncGPUObject(const Framebuffer& framebuffer) { return GLESFramebuffer::sync(*this, framebuffer); } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp new file mode 100644 index 0000000000..01a87978c2 --- /dev/null +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp @@ -0,0 +1,113 @@ +// +// Created by Sam Gateau on 2017/04/13 +// Copyright 2013-2017 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 "GLESBackend.h" +#include "../gl/GLShader.h" + +using namespace gpu; +using namespace gpu::gl; +using namespace gpu::gles; + +// GLSL version +std::string GLESBackend::getBackendShaderHeader() const { + return Parent::getBackendShaderHeader(); +} + +int GLESBackend::makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) { + GLint ssboCount = 0; + GLint uniformsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); + + for (int i = 0; i < uniformsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + GLint location = glGetUniformLocation(glprogram, name); + const GLint INVALID_UNIFORM_LOCATION = -1; + + // Try to make sense of the gltype + auto elementResource = getFormatFromGLUniform(type); + + // The uniform as a standard var type + if (location != INVALID_UNIFORM_LOCATION) { + + if (elementResource._resource == Resource::BUFFER) { + if (elementResource._element.getSemantic() == gpu::RESOURCE_BUFFER) { + // Let's make sure the name doesn't contains an array element + std::string sname(name); + auto foundBracket = sname.find_first_of('['); + if (foundBracket != std::string::npos) { + // std::string arrayname = sname.substr(0, foundBracket); + + if (sname[foundBracket + 1] == '0') { + sname = sname.substr(0, foundBracket); + } else { + // skip this uniform since it's not the first element of an array + continue; + } + } + + // For texture/Sampler, the location is the actual binding value + GLint binding = -1; + glGetUniformiv(glprogram, location, &binding); + + if (binding == GLESBackend::TRANSFORM_OBJECT_SLOT) { + continue; + } + + auto requestedBinding = slotBindings.find(std::string(sname)); + if (requestedBinding != slotBindings.end()) { + GLint requestedLoc = (*requestedBinding)._location + GLESBackend::RESOURCE_BUFFER_SLOT0_TEX_UNIT; + if (binding != requestedLoc) { + binding = requestedLoc; + } + } else { + binding += GLESBackend::RESOURCE_BUFFER_SLOT0_TEX_UNIT; + } + glProgramUniform1i(glprogram, location, binding); + + ssboCount++; + resourceBuffers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + } + } + } + } + + return ssboCount; +} + +void GLESBackend::makeProgramBindings(ShaderObject& shaderObject) { + if (!shaderObject.glprogram) { + return; + } + GLuint glprogram = shaderObject.glprogram; + GLint loc = -1; + + GLBackend::makeProgramBindings(shaderObject); + + // now assign the ubo binding, then DON't relink! + + //Check for gpu specific uniform slotBindings + loc = glGetUniformLocation(glprogram, "transformObjectBuffer"); + if (loc >= 0) { + glProgramUniform1i(glprogram, loc, GLESBackend::TRANSFORM_OBJECT_SLOT); + shaderObject.transformObjectSlot = GLESBackend::TRANSFORM_OBJECT_SLOT; + } + + loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer"); + if (loc >= 0) { + glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_CAMERA_SLOT); + shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT; + } + + (void)CHECK_GL_ERROR(); +} + diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index 31a98edd12..d2fa2aabab 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -12,41 +12,105 @@ #include #include -#include -// #include "../gl/GLTexelFormat.h" +#include "../gl/GLTexelFormat.h" using namespace gpu; using namespace gpu::gl; using namespace gpu::gles; -//using GL41TexelFormat = GLTexelFormat; +bool GLESBackend::supportedTextureFormat(const gpu::Element& format) { + // FIXME distinguish between GLES and GL compressed formats after support + // for the former is added to gpu::Element + return !format.isCompressed(); +} + +GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) { + if (!texturePointer) { + return nullptr; + } + + const Texture& texture = *texturePointer; + if (TextureUsageType::EXTERNAL == texture.getUsageType()) { + return Parent::syncGPUObject(texturePointer); + } + + if (!texture.isDefined()) { + // NO texture definition yet so let's avoid thinking + return nullptr; + } + + // Check whether the texture is in a format we can deal with + auto format = texture.getTexelFormat(); + if (!supportedTextureFormat(format)) { + return nullptr; + } + + GLESTexture* object = Backend::getGPUObject(texture); + if (!object) { + switch (texture.getUsageType()) { + case TextureUsageType::RENDERBUFFER: + object = new GLESAttachmentTexture(shared_from_this(), texture); + break; + + case TextureUsageType::RESOURCE: +// FIXME disabling variable allocation textures for now, while debugging android rendering +// and crashes +#if 0 + qCDebug(gpugllogging) << "variable / Strict texture " << texture.source().c_str(); + object = new GLESResourceTexture(shared_from_this(), texture); + GLVariableAllocationSupport::addMemoryManagedTexture(texturePointer); + break; +#endif + case TextureUsageType::STRICT_RESOURCE: + qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str(); + object = new GLESStrictResourceTexture(shared_from_this(), texture); + break; + + default: + Q_UNREACHABLE(); + } + } else { + if (texture.getUsageType() == TextureUsageType::RESOURCE) { + auto varTex = static_cast (object); + + if (varTex->_minAllocatedMip > 0) { + auto minAvailableMip = texture.minAvailableMipLevel(); + if (minAvailableMip < varTex->_minAllocatedMip) { + varTex->_minAllocatedMip = minAvailableMip; + GLESVariableAllocationTexture::_memoryPressureStateStale = true; + } + } + } + } + + return object; +} + using GLESTexture = GLESBackend::GLESTexture; -GLuint GLESTexture::allocate() { - //CLIMAX_MERGE_START - //Backend::incrementTextureGPUCount(); - //CLIMAX_MERGE_END +GLESTexture::GLESTexture(const std::weak_ptr& backend, const Texture& texture) + : GLTexture(backend, texture, allocate(texture)) { +} + +void GLESTexture::withPreservedTexture(std::function f) const { + glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_TEX_UNIT); + glBindTexture(_target, _texture); + (void)CHECK_GL_ERROR(); + + f(); + glBindTexture(_target, 0); + (void)CHECK_GL_ERROR(); +} + + +GLuint GLESTexture::allocate(const Texture& texture) { GLuint result; glGenTextures(1, &result); return result; } -GLuint GLESBackend::getTextureID(const TexturePointer& texture, bool transfer) { - return GLESTexture::getId(*this, texture, transfer); -} -GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texture, bool transfer) { - return GLESTexture::sync(*this, texture, transfer); -} - -GLESTexture::GLESTexture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId) - : GLTexture(backend, texture, externalId) { -} - -GLESTexture::GLESTexture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) - : GLTexture(backend, texture, allocate(), transferrable) { -} void GLESTexture::generateMips() const { withPreservedTexture([&] { @@ -55,102 +119,88 @@ void GLESTexture::generateMips() const { (void)CHECK_GL_ERROR(); } -void GLESTexture::allocateStorage() const { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); - (void)CHECK_GL_ERROR(); - glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - (void)CHECK_GL_ERROR(); -/* if (GLEW_VERSION_4_2 && !_gpuObject.getTexelFormat().isCompressed()) { - // Get the dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip); - glTexStorage2D(_target, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } else {*/ - for (uint16_t l = _minMip; l <= _maxMip; l++) { - // Get the mip level dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuObject.evalMipDimensions(l); - for (GLenum target : getFaceTargets(_target)) { - glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); - (void)CHECK_GL_ERROR(); - } - } - //} -} - -void GLESTexture::updateSize() const { - setSize(_virtualSize); - if (!_id) { - return; - } - - if (_gpuObject.getTexelFormat().isCompressed()) { - GLenum proxyType = GL_TEXTURE_2D; - GLuint numFaces = 1; - if (_gpuObject.getType() == gpu::Texture::TEX_CUBE) { - proxyType = CUBE_FACE_LAYOUT[0]; - numFaces = (GLuint)CUBE_NUM_FACES; - } - GLint gpuSize{ 0 }; - glGetTexLevelParameteriv(proxyType, 0, GL_TEXTURE_COMPRESSED, &gpuSize); - (void)CHECK_GL_ERROR(); - - if (gpuSize) { - for (GLuint level = _minMip; level < _maxMip; level++) { - GLint levelSize{ 0 }; - //glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); - //qDebug() << "TODO: GLBackendTexture.cpp:updateSize GL_TEXTURE_COMPRESSED_IMAGE_SIZE"; - levelSize *= numFaces; - - if (levelSize <= 0) { - break; - } - gpuSize += levelSize; - } - (void)CHECK_GL_ERROR(); - setSize(gpuSize); - return; - } - } -} - -// Move content bits from the CPU to the GPU for a given mip / face -void GLESTexture::transferMip(uint16_t mipLevel, uint8_t face) const { - auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), _gpuObject.getStoredMipFormat()); - //GLenum target = getFaceTargets()[face]; - GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; - auto size = _gpuObject.evalMipDimensions(mipLevel); - glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - (void)CHECK_GL_ERROR(); -} - -void GLESTexture::startTransfer() { - PROFILE_RANGE(render_gpu_gl, __FUNCTION__); - Parent::startTransfer(); - - glBindTexture(_target, _id); - (void)CHECK_GL_ERROR(); - - // transfer pixels from each faces - uint8_t numFaces = (Texture::TEX_CUBE == _gpuObject.getType()) ? CUBE_NUM_FACES : 1; - for (uint8_t f = 0; f < numFaces; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuObject.isStoredMipFaceAvailable(i, f)) { - transferMip(i, f); - } - } +bool isCompressedFormat(GLenum internalFormat) { + switch (internalFormat) { + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + case GL_COMPRESSED_RGBA_ASTC_4x4: + case GL_COMPRESSED_RGBA_ASTC_5x4: + case GL_COMPRESSED_RGBA_ASTC_5x5: + case GL_COMPRESSED_RGBA_ASTC_6x5: + case GL_COMPRESSED_RGBA_ASTC_6x6: + case GL_COMPRESSED_RGBA_ASTC_8x5: + case GL_COMPRESSED_RGBA_ASTC_8x6: + case GL_COMPRESSED_RGBA_ASTC_8x8: + case GL_COMPRESSED_RGBA_ASTC_10x5: + case GL_COMPRESSED_RGBA_ASTC_10x6: + case GL_COMPRESSED_RGBA_ASTC_10x8: + case GL_COMPRESSED_RGBA_ASTC_10x10: + case GL_COMPRESSED_RGBA_ASTC_12x10: + case GL_COMPRESSED_RGBA_ASTC_12x12: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12: + return true; + default: + return false; } } -void GLESBackend::GLESTexture::syncSampler() const { + +Size GLESTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const { + Size amountCopied = sourceSize; + if (GL_TEXTURE_2D == _target) { + if (isCompressedFormat(internalFormat)) { + glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat, + static_cast(sourceSize), sourcePointer); + } else { + glTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer); + } + } else if (GL_TEXTURE_CUBE_MAP == _target) { + auto target = GLTexture::CUBE_FACE_LAYOUT[face]; + if (isCompressedFormat(internalFormat)) { + glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat, + static_cast(sourceSize), sourcePointer); + } else { + glTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, format, type, sourcePointer); + } + } else { + // TODO: implement for android + assert(false); + amountCopied = 0; + } + (void)CHECK_GL_ERROR(); + return amountCopied; +} + +void GLESTexture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); + const auto& fm = FILTER_MODES[sampler.getFilter()]; glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); if (sampler.doComparison()) { - glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); // GL_COMPARE_R_TO_TEXTURE glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); @@ -160,17 +210,452 @@ void GLESBackend::GLESTexture::syncSampler() const { glTexParameteri(_target, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); - glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR_EXT, (const float*)&sampler.getBorderColor()); - + glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + //glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + (void)CHECK_GL_ERROR(); - //qDebug() << "[GPU-GL-GLBackend] syncSampler 12 " << _target << "," << sampler.getMaxAnisotropy(); - //glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); - //(void)CHECK_GL_ERROR(); - //qDebug() << "[GPU-GL-GLBackend] syncSampler end"; +} + +using GLESFixedAllocationTexture = GLESBackend::GLESFixedAllocationTexture; + +GLESFixedAllocationTexture::GLESFixedAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : GLESTexture(backend, texture), _size(texture.evalTotalSize()) { + withPreservedTexture([&] { + allocateStorage(); + syncSampler(); + }); +} + +GLESFixedAllocationTexture::~GLESFixedAllocationTexture() { +} + +void GLESFixedAllocationTexture::allocateStorage() const { + const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + const auto numMips = _gpuObject.getNumMips(); + + // glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); + for (GLint level = 0; level < numMips; level++) { + Vec3u dimensions = _gpuObject.evalMipDimensions(level); + for (GLenum target : getFaceTargets(_target)) { + glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, nullptr); + } + } + + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, numMips - 1); + (void)CHECK_GL_ERROR(); +} + +void GLESFixedAllocationTexture::syncSampler() const { + Parent::syncSampler(); + const Sampler& sampler = _gpuObject.getSampler(); + auto baseMip = std::max(sampler.getMipOffset(), sampler.getMinMip()); + + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip); + glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); + glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip())); +} + +// Renderbuffer attachment textures +using GLESAttachmentTexture = GLESBackend::GLESAttachmentTexture; + +GLESAttachmentTexture::GLESAttachmentTexture(const std::weak_ptr& backend, const Texture& texture) : GLESFixedAllocationTexture(backend, texture) { + Backend::textureFramebufferCount.increment(); + Backend::textureFramebufferGPUMemSize.update(0, size()); +} + +GLESAttachmentTexture::~GLESAttachmentTexture() { + Backend::textureFramebufferCount.decrement(); + Backend::textureFramebufferGPUMemSize.update(size(), 0); +} + +// Strict resource textures +using GLESStrictResourceTexture = GLESBackend::GLESStrictResourceTexture; + +GLESStrictResourceTexture::GLESStrictResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GLESFixedAllocationTexture(backend, texture) { + Backend::textureResidentCount.increment(); + Backend::textureResidentGPUMemSize.update(0, size()); + + withPreservedTexture([&] { + + auto mipLevels = _gpuObject.getNumMips(); + for (uint16_t sourceMip = 0; sourceMip < mipLevels; sourceMip++) { + uint16_t targetMip = sourceMip; + size_t maxFace = GLTexture::getFaceCount(_target); + for (uint8_t face = 0; face < maxFace; face++) { + copyMipFaceFromTexture(sourceMip, targetMip, face); + } + } + }); + + if (texture.isAutogenerateMips()) { + generateMips(); + } +} + +GLESStrictResourceTexture::~GLESStrictResourceTexture() { + Backend::textureResidentCount.decrement(); + Backend::textureResidentGPUMemSize.update(size(), 0); +} + +using GLESVariableAllocationTexture = GLESBackend::GLESVariableAllocationTexture; + +GLESVariableAllocationTexture::GLESVariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : + GLESTexture(backend, texture) +{ + Backend::textureResourceCount.increment(); + + auto mipLevels = texture.getNumMips(); + _allocatedMip = mipLevels; + _maxAllocatedMip = _populatedMip = mipLevels; + _minAllocatedMip = texture.minAvailableMipLevel(); + + uvec3 mipDimensions; + for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) { + if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) { + _maxAllocatedMip = _populatedMip = mip; + break; + } + } + + auto targetMip = _populatedMip - std::min(_populatedMip, 2); + uint16_t allocatedMip = std::max(_minAllocatedMip, targetMip); + + allocateStorage(allocatedMip); + _memoryPressureStateStale = true; + copyMipsFromTexture(); + + syncSampler(); +} + +GLESVariableAllocationTexture::~GLESVariableAllocationTexture() { + Backend::textureResourceCount.decrement(); + Backend::textureResourceGPUMemSize.update(_size, 0); + Backend::textureResourcePopulatedGPUMemSize.update(_populatedSize, 0); +} + +void GLESVariableAllocationTexture::allocateStorage(uint16 allocatedMip) { + _allocatedMip = allocatedMip; + + const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + const auto dimensions = _gpuObject.evalMipDimensions(_allocatedMip); + const auto totalMips = _gpuObject.getNumMips(); + const auto mips = totalMips - _allocatedMip; + withPreservedTexture([&] { + // FIXME technically GL 4.2, but OSX includes the ARB_texture_storage extension + glTexStorage2D(_target, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); CHECK_GL_ERROR(); + }); + auto mipLevels = _gpuObject.getNumMips(); + _size = 0; + for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) { + _size += _gpuObject.evalMipSize(mip); + } + Backend::textureResourceGPUMemSize.update(0, _size); } +Size GLESVariableAllocationTexture::copyMipsFromTexture() { + auto mipLevels = _gpuObject.getNumMips(); + size_t maxFace = GLTexture::getFaceCount(_target); + Size amount = 0; + for (uint16_t sourceMip = _populatedMip; sourceMip < mipLevels; ++sourceMip) { + uint16_t targetMip = sourceMip - _allocatedMip; + for (uint8_t face = 0; face < maxFace; ++face) { + amount += copyMipFaceFromTexture(sourceMip, targetMip, face); + } + } + + + return amount; +} + +Size GLESVariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const { + Size amountCopied = 0; + withPreservedTexture([&] { + amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer); + }); + incrementPopulatedSize(amountCopied); + return amountCopied; +} + +void GLESVariableAllocationTexture::syncSampler() const { + withPreservedTexture([&] { + Parent::syncSampler(); + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); + }); +} + + +void copyUncompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { + // DestID must be bound to the GLESBackend::RESOURCE_TRANSFER_TEX_UNIT + + GLuint fbo { 0 }; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + + uint16_t mips = numMips; + // copy pre-existing mips + for (uint16_t mip = populatedMips; mip < mips; ++mip) { + auto mipDimensions = texture.evalMipDimensions(mip); + uint16_t targetMip = mip - destMipOffset; + uint16_t sourceMip = mip - srcMipOffset; + for (GLenum target : GLTexture::getFaceTargets(texTarget)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, srcId, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); + (void)CHECK_GL_ERROR(); + } + } + + // destroy the transfer framebuffer + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); +} + +void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { + // DestID must be bound to the GLESBackend::RESOURCE_TRANSFER_TEX_UNIT + + struct MipDesc { + GLint _faceSize; + GLint _size; + GLint _offset; + GLint _width; + GLint _height; + }; + std::vector sourceMips(numMips); + + std::vector bytes; + + glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_EXTRA_TEX_UNIT); + glBindTexture(texTarget, srcId); + const auto& faceTargets = GLTexture::getFaceTargets(texTarget); + GLint internalFormat { 0 }; + + // Collect the mip description from the source texture + GLint bufferOffset { 0 }; + for (uint16_t mip = populatedMips; mip < numMips; ++mip) { + auto& sourceMip = sourceMips[mip]; + + uint16_t sourceLevel = mip - srcMipOffset; + + // Grab internal format once + if (internalFormat == 0) { + glGetTexLevelParameteriv(faceTargets[0], sourceLevel, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat); + } + + // Collect the size of the first face, and then compute the total size offset needed for this mip level + auto mipDimensions = texture.evalMipDimensions(mip); + sourceMip._width = mipDimensions.x; + sourceMip._height = mipDimensions.y; +#ifdef DEBUG_COPY + glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_WIDTH, &sourceMip._width); + glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_HEIGHT, &sourceMip._height); +#endif + // TODO: retrieve the size of a compressed image + assert(false); + //glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &sourceMip._faceSize); + sourceMip._size = (GLint)faceTargets.size() * sourceMip._faceSize; + sourceMip._offset = bufferOffset; + bufferOffset += sourceMip._size; + gpu::gl::checkGLError(); + } + (void)CHECK_GL_ERROR(); + + // Allocate the PBO to accomodate for all the mips to copy + GLuint pbo { 0 }; + glGenBuffers(1, &pbo); + glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); + glBufferData(GL_PIXEL_PACK_BUFFER, bufferOffset, nullptr, GL_STATIC_COPY); + (void)CHECK_GL_ERROR(); + + // Transfer from source texture to pbo + for (uint16_t mip = populatedMips; mip < numMips; ++mip) { + auto& sourceMip = sourceMips[mip]; + + uint16_t sourceLevel = mip - srcMipOffset; + + for (GLint f = 0; f < (GLint)faceTargets.size(); f++) { + // TODO: implement for android + //glGetCompressedTexImage(faceTargets[f], sourceLevel, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); + } + (void)CHECK_GL_ERROR(); + } + + // Now populate the new texture from the pbo + glBindTexture(texTarget, 0); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + + glActiveTexture(GL_TEXTURE0 + GLESBackend::RESOURCE_TRANSFER_TEX_UNIT); + + // Transfer from pbo to new texture + for (uint16_t mip = populatedMips; mip < numMips; ++mip) { + auto& sourceMip = sourceMips[mip]; + + uint16_t destLevel = mip - destMipOffset; + + for (GLint f = 0; f < (GLint)faceTargets.size(); f++) { +#ifdef DEBUG_COPY + GLint destWidth, destHeight, destSize; + glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_WIDTH, &destWidth); + glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_HEIGHT, &destHeight); + glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &destSize); +#endif + glCompressedTexSubImage2D(faceTargets[f], destLevel, 0, 0, sourceMip._width, sourceMip._height, internalFormat, + sourceMip._faceSize, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize)); + gpu::gl::checkGLError(); + } + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + glDeleteBuffers(1, &pbo); +} + +void GLESVariableAllocationTexture::copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) { + uint16_t numMips = _gpuObject.getNumMips(); + withPreservedTexture([&] { + if (_texelFormat.isCompressed()) { + copyCompressedTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); + } else { + copyUncompressedTexGPUMem(_gpuObject, _target, srcId, destId, numMips, srcMipOffset, destMipOffset, populatedMips); + } + }); +} + +void GLESVariableAllocationTexture::promote() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + Q_ASSERT(_allocatedMip > 0); + + uint16_t targetAllocatedMip = _allocatedMip - std::min(_allocatedMip, 2); + targetAllocatedMip = std::max(_minAllocatedMip, targetAllocatedMip); + + GLuint oldId = _id; + auto oldSize = _size; + uint16_t oldAllocatedMip = _allocatedMip; + + // create new texture + const_cast(_id) = allocate(_gpuObject); + + // allocate storage for new level + allocateStorage(targetAllocatedMip); + + // copy pre-existing mips + copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); + + // destroy the old texture + glDeleteTextures(1, &oldId); + + // Update sampler + syncSampler(); + + // update the memory usage + Backend::textureResourceGPUMemSize.update(oldSize, 0); + // no change to Backend::textureResourcePopulatedGPUMemSize + + populateTransferQueue(); +} + +void GLESVariableAllocationTexture::demote() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + Q_ASSERT(_allocatedMip < _maxAllocatedMip); + auto oldId = _id; + auto oldSize = _size; + auto oldPopulatedMip = _populatedMip; + + // allocate new texture + const_cast(_id) = allocate(_gpuObject); + uint16_t oldAllocatedMip = _allocatedMip; + allocateStorage(_allocatedMip + 1); + _populatedMip = std::max(_populatedMip, _allocatedMip); + + + // copy pre-existing mips + copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip); + + // destroy the old texture + glDeleteTextures(1, &oldId); + + // Update sampler + syncSampler(); + + // update the memory usage + Backend::textureResourceGPUMemSize.update(oldSize, 0); + // Demoting unpopulate the memory delta + if (oldPopulatedMip != _populatedMip) { + auto numPopulatedDemoted = _populatedMip - oldPopulatedMip; + Size amountUnpopulated = 0; + for (int i = 0; i < numPopulatedDemoted; i++) { + amountUnpopulated += _gpuObject.evalMipSize(oldPopulatedMip + i); + } + decrementPopulatedSize(amountUnpopulated); + } + populateTransferQueue(); +} + + +void GLESVariableAllocationTexture::populateTransferQueue() { + PROFILE_RANGE(render_gpu_gl, __FUNCTION__); + if (_populatedMip <= _allocatedMip) { + return; + } + _pendingTransfers = TransferQueue(); + + const uint8_t maxFace = GLTexture::getFaceCount(_target); + uint16_t sourceMip = _populatedMip; + do { + --sourceMip; + auto targetMip = sourceMip - _allocatedMip; + auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip); + for (uint8_t face = 0; face < maxFace; ++face) { + if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) { + continue; + } + + // If the mip is less than the max transfer size, then just do it in one transfer + if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) { + // Can the mip be transferred in one go + _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face)); + continue; + } + + // break down the transfers into chunks so that no single transfer is + // consuming more than X bandwidth + // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block + auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); + const auto lines = mipDimensions.y; + const uint32_t BLOCK_NUM_LINES { 4 }; + const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES; + auto bytesPerBlock = mipSize / numBlocks; + Q_ASSERT(0 == (mipSize % lines)); + uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock); + uint32_t lineOffset = 0; + while (lineOffset < lines) { + uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); + _pendingTransfers.emplace(new TransferJob(*this, sourceMip, targetMip, face, linesToCopy, lineOffset)); + lineOffset += linesToCopy; + } + } + + // queue up the sampler and populated mip change for after the transfer has completed + _pendingTransfers.emplace(new TransferJob(*this, [=] { + _populatedMip = sourceMip; + syncSampler(); + })); + } while (sourceMip != _allocatedMip); +} + +// resource textures +using GLESResourceTexture = GLESBackend::GLESResourceTexture; + +GLESResourceTexture::GLESResourceTexture(const std::weak_ptr& backend, const Texture& texture) : GLESVariableAllocationTexture(backend, texture) { + if (texture.isAutogenerateMips()) { + generateMips(); + } +} + +GLESResourceTexture::~GLESResourceTexture() { +} + + diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp index 5050db6edd..7d33ca822d 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp @@ -38,9 +38,9 @@ void GLESBackend::transferTransformState(const Batch& batch) const { } if (!batch._objects.empty()) { - glBindBuffer(GL_SHADER_STORAGE_BUFFER, _transform._objectBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, batch._objects.size() * sizeof(Batch::TransformObject), batch._objects.data(), GL_STREAM_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + glBindBuffer(GL_TEXTURE_BUFFER, _transform._objectBuffer); + glBufferData(GL_TEXTURE_BUFFER, batch._objects.size() * sizeof(Batch::TransformObject), batch._objects.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_TEXTURE_BUFFER, 0); } if (!batch._namedData.empty()) { @@ -58,10 +58,44 @@ void GLESBackend::transferTransformState(const Batch& batch) const { glBindBuffer(GL_ARRAY_BUFFER, 0); } - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _transform._objectBuffer); + glActiveTexture(GL_TEXTURE0 + GLESBackend::TRANSFORM_OBJECT_SLOT); + glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); + if (!batch._objects.empty()) { + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); + } CHECK_GL_ERROR(); // Make sure the current Camera offset is unknown before render Draw _transform._currentCameraOffset = INVALID_OFFSET; } + + +void GLESBackend::updateTransform(const Batch& batch) { + _transform.update(_commandIndex, _stereo); + + auto& drawCallInfoBuffer = batch.getDrawCallInfoBuffer(); + if (batch._currentNamedCall.empty()) { + auto& drawCallInfo = drawCallInfoBuffer[_currentDraw]; + if (_transform._enabledDrawcallInfoBuffer) { + glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is disabled + _transform._enabledDrawcallInfoBuffer = false; + } + glVertexAttribI4i(gpu::Stream::DRAW_CALL_INFO, drawCallInfo.index, drawCallInfo.unused, 0, 0); + //glVertexAttribI2i(gpu::Stream::DRAW_CALL_INFO, drawCallInfo.index, drawCallInfo.unused); + } else { + if (!_transform._enabledDrawcallInfoBuffer) { + glEnableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is enabled +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, (isStereo() ? 2 : 1)); +#else + glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, 1); +#endif + _transform._enabledDrawcallInfoBuffer = true; + } + glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); + glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_UNSIGNED_SHORT, 0, _transform._drawCallInfoOffsets[batch._currentNamedCall]); + } + + (void)CHECK_GL_ERROR(); +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index fca220c224..79f08ea5cc 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -269,6 +269,10 @@ public: void _glColor4f(float red, float green, float blue, float alpha); + // Maybe useful but shoudln't be public. Please convince me otherwise + // Well porting to gles i need it... + void runLambda(std::function f); + enum Command { COMMAND_draw = 0, COMMAND_drawIndexed, @@ -497,8 +501,7 @@ protected: void startNamedCall(const std::string& name); void stopNamedCall(); - // Maybe useful but shoudln't be public. Please convince me otherwise - void runLambda(std::function f); + void captureDrawCallInfoImpl(); }; diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index b3cf0fbba3..f470cc8aa9 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -134,7 +134,7 @@ public: float getAspectRatio() const { return getWidth() / (float) getHeight() ; } -#ifndef ANDROID +#if !defined(Q_OS_ANDROID) static const uint32 MAX_NUM_RENDER_BUFFERS = 8; #else static const uint32 MAX_NUM_RENDER_BUFFERS = 4; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 06208179e0..ad3dc5fada 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -539,7 +539,7 @@ public: static TexturePointer build(const ktx::KTXDescriptor& descriptor); static TexturePointer unserialize(const std::string& ktxFile); - static TexturePointer unserialize(const cache::FilePointer& cacheEntry); + static TexturePointer unserialize(const cache::FilePointer& cacheEntry, const std::string& source = std::string()); static bool evalKTXFormat(const Element& mipFormat, const Element& texelFormat, ktx::Header& header); static bool evalTextureFormat(const ktx::Header& header, Element& mipFormat, Element& texelFormat); diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 883d9abf15..754bdca445 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -499,7 +499,9 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { GPUKTXPayload gpuktxKeyValue; if (!GPUKTXPayload::findInKeyValues(descriptor.keyValues, gpuktxKeyValue)) { qCWarning(gpulogging) << "Could not find GPUKTX key values."; - return TexturePointer(); + // FIXME use sensible defaults based on the texture type and format + gpuktxKeyValue._usageType = TextureUsageType::RESOURCE; + gpuktxKeyValue._usage = Texture::Usage::Builder().withColor().withAlpha().build(); } auto texture = create(gpuktxKeyValue._usageType, @@ -525,7 +527,7 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { return texture; } -TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry) { +TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry, const std::string& source) { std::unique_ptr ktxPointer = ktx::KTX::create(std::make_shared(cacheEntry->getFilepath().c_str())); if (!ktxPointer) { return nullptr; @@ -534,6 +536,9 @@ TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry) { auto texture = build(ktxPointer->toDescriptor()); if (texture) { texture->setKtxBacking(cacheEntry); + if (texture->source().empty()) { + texture->setSource(source); + } } return texture; @@ -548,6 +553,7 @@ TexturePointer Texture::unserialize(const std::string& ktxfile) { auto texture = build(ktxPointer->toDescriptor()); if (texture) { texture->setKtxBacking(ktxfile); + texture->setSource(ktxfile); } return texture; diff --git a/libraries/graphics/src/graphics/Skybox.cpp b/libraries/graphics/src/graphics/Skybox.cpp index 0e241cb71d..f4dd9ae052 100755 --- a/libraries/graphics/src/graphics/Skybox.cpp +++ b/libraries/graphics/src/graphics/Skybox.cpp @@ -89,12 +89,14 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky auto skyFS = skybox_frag::getShader(); auto skyShader = gpu::Shader::createProgram(skyVS, skyFS); - gpu::Shader::BindingSet bindings; - bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); - bindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), SKYBOX_CONSTANTS_SLOT)); - if (!gpu::Shader::makeProgram(*skyShader, bindings)) { + batch.runLambda([skyShader] { + gpu::Shader::BindingSet bindings; + bindings.insert(gpu::Shader::Binding(std::string("cubeMap"), SKYBOX_SKYMAP_SLOT)); + bindings.insert(gpu::Shader::Binding(std::string("skyboxBuffer"), SKYBOX_CONSTANTS_SLOT)); + if (!gpu::Shader::makeProgram(*skyShader, bindings)) { - } + } + }); auto skyState = std::make_shared(); // Must match PrepareStencil::STENCIL_BACKGROUND diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index f78ed1a583..9115dd09e2 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -19,15 +19,6 @@ #include #include - -#if defined(Q_OS_ANDROID) -#define CPU_MIPMAPS 0 -#else -#define CPU_MIPMAPS 1 -#include -#endif - - #include #include #include @@ -37,6 +28,12 @@ using namespace gpu; +#if defined(Q_OS_ANDROID) +#define CPU_MIPMAPS 1 +#else +#define CPU_MIPMAPS 1 +#include +#endif static const glm::uvec2 SPARSE_PAGE_SIZE(128); static const glm::uvec2 MAX_TEXTURE_SIZE(4096); @@ -51,25 +48,24 @@ static std::atomic compressNormalTextures { false }; static std::atomic compressGrayscaleTextures { false }; static std::atomic compressCubeTextures { false }; -bool needsSparseRectification(const glm::uvec2& size) { - // Don't attempt to rectify small textures (textures less than the sparse page size in any dimension) - if (glm::any(glm::lessThan(size, SPARSE_PAGE_SIZE))) { - return false; +uint rectifyDimension(const uint& dimension) { + if (dimension == 0) { + return 0; } - - // Don't rectify textures that are already an exact multiple of sparse page size - if (glm::uvec2(0) == (size % SPARSE_PAGE_SIZE)) { - return false; + if (dimension < SPARSE_PAGE_SIZE.x) { + uint newSize = SPARSE_PAGE_SIZE.x; + while (dimension <= newSize / 2) { + newSize /= 2; + } + return newSize; + } else { + uint pages = (dimension / SPARSE_PAGE_SIZE.x) + (dimension % SPARSE_PAGE_SIZE.x == 0 ? 0 : 1); + return pages * SPARSE_PAGE_SIZE.x; } - - // Texture is not sparse compatible, but is bigger than the sparse page size in both dimensions, rectify! - return true; } -glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { - glm::uvec2 pages = ((size / SPARSE_PAGE_SIZE) + glm::clamp(size % SPARSE_PAGE_SIZE, glm::uvec2(0), glm::uvec2(1))); - glm::uvec2 result = pages * SPARSE_PAGE_SIZE; - return result; +glm::uvec2 rectifySize(const glm::uvec2& size) { + return { rectifyDimension(size.x), rectifyDimension(size.y) }; } @@ -329,9 +325,12 @@ QImage processSourceImage(QImage&& srcImage, bool cubemap) { ++DECIMATED_TEXTURE_COUNT; } - if (!cubemap && needsSparseRectification(targetSize)) { - ++RECTIFIED_TEXTURE_COUNT; - targetSize = rectifyToSparseSize(targetSize); + if (!cubemap) { + auto rectifiedSize = rectifySize(targetSize); + if (rectifiedSize != targetSize) { + ++RECTIFIED_TEXTURE_COUNT; + targetSize = rectifiedSize; + } } if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) { @@ -680,6 +679,7 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS +#if !defined(Q_OS_ANDROID) PROFILE_RANGE(resource_parse, "generateMips"); if (image.format() == QIMAGE_HDR_FORMAT) { @@ -687,6 +687,16 @@ void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic } else { generateLDRMips(texture, std::move(image), abortProcessing, face); } + +#else + //texture->setAutoGenerateMips(false); + texture->assignStoredMip(0, image.byteCount(), image.constBits()); + for (uint16 level = 1; level < texture->getNumMips(); ++level) { + QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level)); + QImage mipImage = image.scaled(mipSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + texture->assignStoredMip(level, mipImage.byteCount(), mipImage.constBits()); + } +#endif #else texture->setAutoGenerateMips(true); #endif @@ -727,9 +737,15 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma bool validAlpha = image.hasAlphaChannel(); bool alphaAsMask = false; +#if !defined(Q_OS_ANDROID) if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } +#else + if (image.format() != QImage::Format_RGBA8888) { + image = image.convertToFormat(QImage::Format_RGBA8888); + } +#endif if (validAlpha) { processTextureAlpha(image, validAlpha, alphaAsMask); @@ -769,6 +785,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma } theTexture->setUsage(usage.build()); theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); generateMips(theTexture.get(), std::move(image), abortProcessing); } @@ -875,6 +892,7 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); generateMips(theTexture.get(), std::move(image), abortProcessing); } @@ -911,6 +929,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); generateMips(theTexture.get(), std::move(image), abortProcessing); } diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 69c35c4a20..1f190111f2 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -163,7 +163,7 @@ void Midi::sendRawMessage(int device, int raw) { void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); if (broadcastEnabled) { - for (int i = 0; i < midihout.size(); i++) { + for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) if (midihout[i] != NULL) { midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); } @@ -174,9 +174,9 @@ void Midi::sendMessage(int device, int channel, int type, int note, int velocity } void Midi::sendNote(int status, int note, int velocity) { - for (int i = 0; i < midihout.size(); i++) { + for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth) if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); + midiOutShortMsg(midihout[i], status | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); } } } @@ -283,9 +283,6 @@ void Midi::midiHardwareChange() { Midi::Midi() { instance = this; -#if defined Q_OS_WIN32 - midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags) -#endif MidiSetup(); } diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 8153dba3a5..fb75123b43 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -50,8 +50,10 @@ Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image") Q_LOGGING_CATEGORY(trace_resource_parse_image_raw, "trace.resource.parse.image.raw") Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.ktx") +#if !defined(DISABLE_KTX_CACHE) const std::string TextureCache::KTX_DIRNAME { "ktx_cache" }; const std::string TextureCache::KTX_EXT { "ktx" }; +#endif static const QString RESOURCE_SCHEME = "resource"; static const QUrl SPECTATOR_CAMERA_FRAME_URL("resource://spectatorCameraFrame"); @@ -61,7 +63,9 @@ static const float SKYBOX_LOAD_PRIORITY { 10.0f }; // Make sure skybox loads fir static const float HIGH_MIPS_LOAD_PRIORITY { 9.0f }; // Make sure high mips loads after skybox but before models TextureCache::TextureCache() { +#if !defined(DISABLE_KTX_CACHE) _ktxCache->initialize(); +#endif setUnusedResourceCacheSize(0); setObjectName("TextureCache"); } @@ -261,8 +265,12 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { /// Returns a texture version of an image file gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); + if (image.isNull()) { + qCWarning(networking) << "Unable to load required resource texture" << path; + return nullptr; + } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(std::move(image), QUrl::fromLocalFile(path).fileName().toStdString(), false)); + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -742,16 +750,20 @@ void NetworkTexture::handleFinishedInitialLoad() { gpu::TexturePointer texture = textureCache->getTextureByHash(hash); +#if !defined(DISABLE_KTX_CACHE) if (!texture) { auto ktxFile = textureCache->_ktxCache->getFile(hash); if (ktxFile) { texture = gpu::Texture::unserialize(ktxFile); if (texture) { texture = textureCache->cacheTextureByHash(hash, texture); + if (texture->source().empty()) { + texture->setSource(url.toString().toStdString()); + } } } } - +#endif if (!texture) { auto memKtx = ktx::KTX::createBare(*header, keyValues); @@ -766,6 +778,7 @@ void NetworkTexture::handleFinishedInitialLoad() { // Move ktx to file const char* data = reinterpret_cast(memKtx->_storage->data()); +#if !defined(DISABLE_KTX_CACHE) size_t length = memKtx->_storage->size(); cache::FilePointer file; auto& ktxCache = textureCache->_ktxCache; @@ -777,11 +790,14 @@ void NetworkTexture::handleFinishedInitialLoad() { Q_ARG(int, 0)); return; } +#endif auto newKtxDescriptor = memKtx->toDescriptor(); texture = gpu::Texture::build(newKtxDescriptor); +#if !defined(DISABLE_KTX_CACHE) texture->setKtxBacking(file); +#endif texture->setSource(filename); auto& images = originalKtxDescriptor->images; @@ -924,11 +940,12 @@ void ImageReader::read() { // If we already have a live texture with the same hash, use it auto texture = textureCache->getTextureByHash(hash); +#if !defined(DISABLE_KTX_CACHE) // If there is no live texture, check if there's an existing KTX file if (!texture) { auto ktxFile = textureCache->_ktxCache->getFile(hash); if (ktxFile) { - texture = gpu::Texture::unserialize(ktxFile); + texture = gpu::Texture::unserialize(ktxFile, _url.toString().toStdString()); if (texture) { texture = textureCache->cacheTextureByHash(hash, texture); } else { @@ -936,6 +953,7 @@ void ImageReader::read() { } } } +#endif // If we found the texture either because it's in use or via KTX deserialization, // set the image and return immediately. @@ -971,6 +989,7 @@ void ImageReader::read() { // Save the image into a KTXFile if (texture && textureCache) { +#if !defined(DISABLE_KTX_CACHE) auto memKtx = gpu::Texture::serialize(*texture); // Move the texture into a memory mapped file @@ -987,7 +1006,7 @@ void ImageReader::read() { } else { qCWarning(modelnetworking) << "Unable to serialize texture to KTX " << _url; } - +#endif // We replace the texture with the one stored in the cache. This deals with the possible race condition of two different // images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will // be the winner diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 742d003d02..74395d8948 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -194,10 +194,12 @@ private: TextureCache(); virtual ~TextureCache(); +#if !defined(DISABLE_KTX_CACHE) static const std::string KTX_DIRNAME; static const std::string KTX_EXT; std::shared_ptr _ktxCache { std::make_shared(KTX_DIRNAME, KTX_EXT) }; +#endif // Map from image hashes to texture weak pointers std::unordered_map> _texturesByHashes; std::mutex _texturesByHashesMutex; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 21815e065a..fa5507fcf7 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -28,15 +29,11 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -#ifdef Q_OS_ANDROID -const QString DEFAULT_HIFI_ADDRESS = "hifi://android/0.0,0.0,-200"; -#else #if USE_STABLE_GLOBAL_SERVICES const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello"; #else const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello"; #endif -#endif const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -761,11 +758,21 @@ void AddressManager::refreshPreviousLookup() { } void AddressManager::copyAddress() { + if (QThread::currentThread() != qApp->thread()) { + QMetaObject::invokeMethod(this, "copyAddress"); + return; + } + // assume that the address is being copied because the user wants a shareable address QApplication::clipboard()->setText(currentShareableAddress().toString()); } void AddressManager::copyPath() { + if (QThread::currentThread() != qApp->thread()) { + QMetaObject::invokeMethod(this, "copyPath"); + return; + } + QApplication::clipboard()->setText(currentPath()); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 7302e0e997..0173a1fad7 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -73,6 +73,7 @@ public: * Get Interface's protocol version. * @function location.protocolVersion * @returns {string} A string uniquely identifying the version of the metaverse protocol that Interface is using. + * @deprecated This function is deprecated and will be removed. Use {@link Window.protocolSignature} instead. */ Q_INVOKABLE QString protocolVersion(); diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp index dfff21dae6..b55f1839a0 100644 --- a/libraries/networking/src/FileResourceRequest.cpp +++ b/libraries/networking/src/FileResourceRequest.cpp @@ -11,24 +11,37 @@ #include "FileResourceRequest.h" -#include - -#include +#include +#include +#include #include +#include + +#include "ResourceManager.h" void FileResourceRequest::doSend() { auto statTracker = DependencyManager::get(); statTracker->incrementStat(STAT_FILE_REQUEST_STARTED); int fileSize = 0; - QString filename = _url.toLocalFile(); - - // sometimes on windows, we see the toLocalFile() return null, - // in this case we will attempt to simply use the url as a string - if (filename.isEmpty()) { - filename = _url.toString(); + QString filename; + if (_url.scheme() == URL_SCHEME_QRC) { + filename = ":/" + _url.path(); + } else { + filename = _url.toLocalFile(); + // sometimes on windows, we see the toLocalFile() return null, + // in this case we will attempt to simply use the url as a string + if (filename.isEmpty()) { + filename = _url.toString(); + } } + + // Allow platform specific versions of files loaded out of a resource cache via file:// + QFileSelector fileSelector; + fileSelector.setExtraSelectors(FileUtils::getFileSelectors()); + filename = fileSelector.select(filename); + if (!_byteRange.isValid()) { _result = ResourceRequest::InvalidByteRange; } else { diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 1b80c3b3af..0950cb5556 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -63,13 +63,6 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : _packetStatTimer(), _permissions(NodePermissions()) { - static bool firstCall = true; - if (firstCall) { - NodeType::init(); - - firstCall = false; - } - qRegisterMetaType("ConnectionStep"); auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get(); _nodeSocket.bind(QHostAddress::AnyIPv4, port); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 25eef38dbd..bd895c8ef1 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -29,28 +29,25 @@ int NodePtrMetaTypeId = qRegisterMetaType("Node*"); int sharedPtrNodeMetaTypeId = qRegisterMetaType>("QSharedPointer"); int sharedNodePtrMetaTypeId = qRegisterMetaType("SharedNodePointer"); -void NodeType::init() { - QHash& TypeNameHash = Node::getTypeNameHash(); - - TypeNameHash.insert(NodeType::DomainServer, "Domain Server"); - TypeNameHash.insert(NodeType::EntityServer, "Entity Server"); - TypeNameHash.insert(NodeType::Agent, "Agent"); - TypeNameHash.insert(NodeType::AudioMixer, "Audio Mixer"); - TypeNameHash.insert(NodeType::AvatarMixer, "Avatar Mixer"); - TypeNameHash.insert(NodeType::MessagesMixer, "Messages Mixer"); - TypeNameHash.insert(NodeType::AssetServer, "Asset Server"); - TypeNameHash.insert(NodeType::EntityScriptServer, "Entity Script Server"); - TypeNameHash.insert(NodeType::UpstreamAudioMixer, "Upstream Audio Mixer"); - TypeNameHash.insert(NodeType::UpstreamAvatarMixer, "Upstream Avatar Mixer"); - TypeNameHash.insert(NodeType::DownstreamAudioMixer, "Downstream Audio Mixer"); - TypeNameHash.insert(NodeType::DownstreamAvatarMixer, "Downstream Avatar Mixer"); - TypeNameHash.insert(NodeType::Unassigned, "Unassigned"); -} +static const QHash TYPE_NAME_HASH { + { NodeType::DomainServer, "Domain Server" }, + { NodeType::EntityServer, "Entity Server" }, + { NodeType::Agent, "Agent" }, + { NodeType::AudioMixer, "Audio Mixer" }, + { NodeType::AvatarMixer, "Avatar Mixer" }, + { NodeType::MessagesMixer, "Messages Mixer" }, + { NodeType::AssetServer, "Asset Server" }, + { NodeType::EntityScriptServer, "Entity Script Server" }, + { NodeType::UpstreamAudioMixer, "Upstream Audio Mixer" }, + { NodeType::UpstreamAvatarMixer, "Upstream Avatar Mixer" }, + { NodeType::DownstreamAudioMixer, "Downstream Audio Mixer" }, + { NodeType::DownstreamAvatarMixer, "Downstream Avatar Mixer" }, + { NodeType::Unassigned, "Unassigned" } +}; const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { - QHash& TypeNameHash = Node::getTypeNameHash(); - QHash::iterator matchedTypeName = TypeNameHash.find(nodeType); - return matchedTypeName != TypeNameHash.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME; + const auto matchedTypeName = TYPE_NAME_HASH.find(nodeType); + return matchedTypeName != TYPE_NAME_HASH.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME; } bool NodeType::isUpstream(NodeType_t nodeType) { @@ -84,8 +81,7 @@ NodeType_t NodeType::downstreamType(NodeType_t primaryType) { } NodeType_t NodeType::fromString(QString type) { - QHash& TypeNameHash = Node::getTypeNameHash(); - return TypeNameHash.key(type, NodeType::Unassigned); + return TYPE_NAME_HASH.key(type, NodeType::Unassigned); } diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 00d3c61fd0..93b6a649d4 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -89,11 +89,6 @@ public: bool isIgnoreRadiusEnabled() const { return _ignoreRadiusEnabled; } - static QHash& getTypeNameHash() { - static QHash TypeNameHash; - return TypeNameHash; - } - private: // privatize copy and assignment operator to disallow Node copying Node(const Node &otherNode); diff --git a/libraries/networking/src/NodeType.h b/libraries/networking/src/NodeType.h index 0130e92cfc..2b2cc4e011 100644 --- a/libraries/networking/src/NodeType.h +++ b/libraries/networking/src/NodeType.h @@ -31,8 +31,6 @@ namespace NodeType { const NodeType_t DownstreamAvatarMixer = 'w'; const NodeType_t Unassigned = 1; - void init(); - const QString& getNodeTypeName(NodeType_t nodeType); bool isUpstream(NodeType_t nodeType); bool isDownstream(NodeType_t nodeType); diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 3ee66f89c1..17dcd9728d 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -11,6 +11,8 @@ #include "ResourceManager.h" +#include + #include #include #include @@ -24,7 +26,6 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" - ResourceManager::ResourceManager() { _thread.setObjectName("Resource Manager Thread"); @@ -53,7 +54,7 @@ QString ResourceManager::normalizeURL(const QString& urlString) { copy = _prefixMap; } - foreach(const auto& entry, copy) { + foreach (const auto& entry, copy) { const auto& prefix = entry.first; const auto& replacement = entry.second; if (result.startsWith(prefix)) { @@ -64,13 +65,24 @@ QString ResourceManager::normalizeURL(const QString& urlString) { return result; } +const QSet& getKnownUrls() { + static QSet knownUrls; + static std::once_flag once; + std::call_once(once, [] { + knownUrls.insert(URL_SCHEME_QRC); + knownUrls.insert(URL_SCHEME_FILE); + knownUrls.insert(URL_SCHEME_HTTP); + knownUrls.insert(URL_SCHEME_HTTPS); + knownUrls.insert(URL_SCHEME_FTP); + knownUrls.insert(URL_SCHEME_ATP); + }); + return knownUrls; +} + QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) { QUrl url = QUrl(normalizeURL(originalUrl.toString())); auto scheme = url.scheme(); - if (!(scheme == URL_SCHEME_FILE || - scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP || - scheme == URL_SCHEME_ATP)) { - + if (!getKnownUrls().contains(scheme)) { // check the degenerative file case: on windows we can often have urls of the form c:/filename // this checks for and works around that case. QUrl urlWithFileScheme{ URL_SCHEME_FILE + ":///" + url.toString() }; @@ -94,7 +106,7 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q ResourceRequest* request = nullptr; - if (scheme == URL_SCHEME_FILE) { + if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { request = new FileResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { request = new HTTPResourceRequest(normalizedURL); @@ -113,15 +125,14 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q return request; } - bool ResourceManager::resourceExists(const QUrl& url) { auto scheme = url.scheme(); if (scheme == URL_SCHEME_FILE) { - QFileInfo file { url.toString() }; + QFileInfo file{ url.toString() }; return file.exists(); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { auto& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest request { url }; + QNetworkRequest request{ url }; request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -159,4 +170,3 @@ bool ResourceManager::resourceExists(const QUrl& url) { qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); return false; } - diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index fdfd05736e..5728a7bd32 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -22,6 +22,7 @@ #include "ResourceRequest.h" +const QString URL_SCHEME_QRC = "qrc"; const QString URL_SCHEME_FILE = "file"; const QString URL_SCHEME_HTTP = "http"; const QString URL_SCHEME_HTTPS = "https"; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index db670c0a88..c48c6bfc0b 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::ZoneStageRemoved); + return static_cast(EntityVersion::SoftEntities); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); @@ -38,7 +38,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::UpdatedMannequinDefaultAvatar); + return static_cast(AvatarMixerPacketVersion::AvatarJointDefaultPoseFlags); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index acc1ff01db..0f69691bd5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -205,7 +205,8 @@ enum class EntityVersion : PacketVersion { StaticCertJsonVersionOne, OwnershipChallengeFix, ZoneLightInheritModes = 82, - ZoneStageRemoved + ZoneStageRemoved, + SoftEntities }; enum class EntityScriptCallMethodVersion : PacketVersion { @@ -246,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentitySequenceFront, IsReplicatedInAvatarIdentity, AvatarIdentityLookAtSnapping, - UpdatedMannequinDefaultAvatar + UpdatedMannequinDefaultAvatar, + AvatarJointDefaultPoseFlags }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 33ac887f4f..420da5a1e0 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -256,25 +256,32 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { assert(_entity); assert(entityTreeIsLocked()); measureBodyAcceleration(); - bool positionSuccess; - _entity->setWorldPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false); - if (!positionSuccess) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " - "setPosition failed.*"); - qCDebug(physics) << "EntityMotionState::setWorldTransform setPosition failed" << _entity->getID(); + + // If transform or velocities are flagged as dirty it means a network or scripted change + // occured between the beginning and end of the stepSimulation() and we DON'T want to apply + // these physics simulation results. + uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + // flags are clear + _entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation())); + _entity->setWorldVelocity(getBodyLinearVelocity()); + _entity->setWorldAngularVelocity(getBodyAngularVelocity()); + _entity->setLastSimulated(usecTimestampNow()); + } else { + // only set properties NOT flagged + if (!(flags & Simulation::DIRTY_TRANSFORM)) { + _entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation())); + } + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + _entity->setWorldVelocity(getBodyLinearVelocity()); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + _entity->setWorldAngularVelocity(getBodyAngularVelocity()); + } + if (flags != (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + _entity->setLastSimulated(usecTimestampNow()); + } } - bool orientationSuccess; - _entity->setWorldOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false); - if (!orientationSuccess) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " - "setOrientation failed.*"); - qCDebug(physics) << "EntityMotionState::setWorldTransform setOrientation failed" << _entity->getID(); - } - _entity->setVelocity(getBodyLinearVelocity()); - _entity->setAngularVelocity(getBodyAngularVelocity()); - _entity->setLastSimulated(usecTimestampNow()); if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; @@ -530,9 +537,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (!_body->isActive()) { // make sure all derivatives are zero - _entity->setVelocity(Vectors::ZERO); - _entity->setAngularVelocity(Vectors::ZERO); - _entity->setAcceleration(Vectors::ZERO); + zeroCleanObjectVelocities(); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); @@ -559,9 +564,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - glm::vec3 zero(0.0f); - _entity->setVelocity(zero); - _entity->setAngularVelocity(zero); + zeroCleanObjectVelocities(); } } _numInactiveUpdates = 0; @@ -818,3 +821,22 @@ bool EntityMotionState::shouldBeLocallyOwned() const { void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { _outgoingPriority = glm::max(_outgoingPriority, priority); } + +void EntityMotionState::zeroCleanObjectVelocities() const { + // If transform or velocities are flagged as dirty it means a network or scripted change + // occured between the beginning and end of the stepSimulation() and we DON'T want to apply + // these physics simulation results. + uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + _entity->setWorldVelocity(glm::vec3(0.0f)); + _entity->setWorldAngularVelocity(glm::vec3(0.0f)); + } else { + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + _entity->setWorldVelocity(glm::vec3(0.0f)); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + _entity->setWorldAngularVelocity(glm::vec3(0.0f)); + } + } + _entity->setAcceleration(glm::vec3(0.0f)); +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index ddfd7e1e4c..784273d600 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -87,6 +87,7 @@ public: protected: // changes _outgoingPriority only if priority is larger void upgradeOutgoingPriority(uint8_t priority); + void zeroCleanObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index a78516e9d7..294c7d59d2 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -11,9 +11,12 @@ #include "DeferredLightingEffect.h" +#include + #include #include #include +#include #include #include @@ -559,9 +562,9 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Haze if (haze) { - batch.setUniformBuffer(HAZE_MODEL_BUFFER_SLOT, haze->getHazeParametersBuffer()); + batch.setUniformBuffer(HAZE_MODEL_BUFFER_SLOT, haze->getHazeParametersBuffer()); } - + batch.draw(gpu::TRIANGLE_STRIP, 4); deferredLightingEffect->unsetKeyLightBatch(batch, locations->lightBufferUnit, locations->ambientBufferUnit, SKYBOX_MAP_UNIT); @@ -728,9 +731,11 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) { PROFILE_RANGE(render, "Process Default Skybox"); auto textureCache = DependencyManager::get(); - auto skyboxUrl = PathUtils::resourcesPath().toStdString() + "images/Default-Sky-9-cubemap.ktx"; + QFileSelector fileSelector; + fileSelector.setExtraSelectors(FileUtils::getFileSelectors()); + auto skyboxUrl = fileSelector.select(PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.ktx"); - _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl); + _defaultSkyboxTexture = gpu::Texture::unserialize(skyboxUrl.toStdString()); _defaultSkyboxAmbientTexture = _defaultSkyboxTexture; _defaultSkybox->setCubemap(_defaultSkyboxTexture); diff --git a/libraries/render-utils/src/Fade.slh b/libraries/render-utils/src/Fade.slh index b85a824ca3..a06c8c869e 100644 --- a/libraries/render-utils/src/Fade.slh +++ b/libraries/render-utils/src/Fade.slh @@ -34,7 +34,7 @@ vec2 hash2D(vec3 position) { } float noise3D(vec3 position) { - float n = textureLod(fadeMaskMap, hash2D(position), 0).r; + float n = textureLod(fadeMaskMap, hash2D(position), 0.0).r; return pow(n, 1.0/2.2); // Remove sRGB. Need to fix this later directly in the texture } @@ -44,7 +44,7 @@ float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) { vec3 noisePositionFloored = floor(noisePosition); vec3 noisePositionFraction = fract(noisePosition); - noisePositionFraction = noisePositionFraction*noisePositionFraction*(3 - 2*noisePositionFraction); + noisePositionFraction = noisePositionFraction*noisePositionFraction*(3.0 - 2.0*noisePositionFraction); float noiseLowXLowYLowZ = noise3D(noisePositionFloored); float noiseLowXHighYLowZ = noise3D(noisePositionFloored+vec3(0,1,0)); @@ -84,7 +84,7 @@ float evalFadeAlpha(FadeObjectParams params, vec3 position) { } void applyFadeClip(FadeObjectParams params, vec3 position) { - if (evalFadeAlpha(params, position) < 0) { + if (evalFadeAlpha(params, position) < 0.0) { discard; } } @@ -95,14 +95,14 @@ void applyFade(FadeObjectParams params, vec3 position, out vec3 emissive) { alpha = -alpha; } - if (alpha < 0) { + if (alpha < 0.0) { discard; } float edgeMask = alpha * fadeParameters[params.category]._edgeWidthInvWidth.y; - float edgeAlpha = 1.0-clamp(edgeMask, 0, 1); + float edgeAlpha = 1.0-clamp(edgeMask, 0.0, 1.0); - edgeMask = step(edgeMask, 1.f); + edgeMask = step(edgeMask, 1.0); edgeAlpha *= edgeAlpha; // Square to have a nice ease out vec4 color = mix(fadeParameters[params.category]._innerEdgeColor, fadeParameters[params.category]._outerEdgeColor, edgeAlpha); emissive = color.rgb * edgeMask * color.a; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 2814beb219..a082a681c9 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -639,7 +639,7 @@ render::ShapePipelinePointer GeometryCache::_simpleWirePipeline; uint8_t GeometryCache::CUSTOM_PIPELINE_NUMBER = 0; -render::ShapePipelinePointer GeometryCache::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key) { +render::ShapePipelinePointer GeometryCache::shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch) { initializeShapePipelines(); if (key.isWireframe()) { @@ -2001,11 +2001,11 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const } void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { - if (!_standardDrawPipeline) { + static std::once_flag once; + std::call_once(once, [&]() { auto vs = standardTransformPNTC_vert::getShader(); auto ps = standardDrawTexture_frag::getShader(); auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); auto state = std::make_shared(); @@ -2021,9 +2021,15 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); - gpu::Shader::makeProgram((*programNoBlend)); + _standardDrawPipelineNoBlend = gpu::Pipeline::create(programNoBlend, stateNoBlend); - } + + batch.runLambda([program, programNoBlend] { + gpu::Shader::makeProgram((*program)); + gpu::Shader::makeProgram((*programNoBlend)); + }); + }); + if (noBlend) { batch.setPipeline(_standardDrawPipelineNoBlend); } else { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 31aa4b10ea..63af30bb79 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -158,7 +158,7 @@ public: static void computeSimpleHullPointListForShape(int entityShape, const glm::vec3 &entityExtents, QVector &outPointList); static uint8_t CUSTOM_PIPELINE_NUMBER; - static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key); + static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch); static void registerShapePipeline() { if (!CUSTOM_PIPELINE_NUMBER) { CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory); diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index d96c565b81..be8330f198 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -133,7 +133,7 @@ SurfaceData initSurfaceData(float roughness, vec3 normal, vec3 eyeDir) { SurfaceData surface; surface.eyeDir = eyeDir; surface.normal = normal; - surface.roughness = mix(0.001, 1.0, roughness); + surface.roughness = mix(0.01, 1.0, roughness); surface.roughness2 = surface.roughness * surface.roughness; surface.roughness4 = surface.roughness2 * surface.roughness2; surface.ndotv = clamp(dot(normal, eyeDir), 0.0, 1.0); @@ -181,12 +181,12 @@ float fresnelSchlickScalar(float fresnelScalar, SurfaceData surface) { float specularDistribution(SurfaceData surface) { // See https://www.khronos.org/assets/uploads/developers/library/2017-web3d/glTF-2.0-Launch_Jun17.pdf // for details of equations, especially page 20 - float denom = (surface.ndoth*surface.ndoth * (surface.roughness2 - 1.0) + 1.0); + float denom = (surface.ndoth*surface.ndoth * (surface.roughness4 - 1.0) + 1.0); denom *= denom; // Add geometric factors G1(n,l) and G1(n,v) float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl); denom *= surface.smithInvG1NdotV * smithInvG1NdotL; - // Don't divide by PI as it will be done later + // Don't divide by PI as this is part of the light normalization factor float power = surface.roughness4 / denom; return power; } @@ -202,12 +202,11 @@ vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) { vec3 specular = fresnelColor * power * angleAttenuation; float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x); - diffuse /= 3.1415926; - // Diffuse is divided by PI but specular isn't because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") - return vec4(specular, diffuse); } @@ -222,9 +221,9 @@ vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) { vec3 specular = vec3(fresnelScalar) * power * angleAttenuation; float diffuse = angleAttenuation * (1.0 - fresnelScalar); - diffuse /= 3.1415926; - // Diffuse is divided by PI but specular isn't because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, diffuse); @@ -239,8 +238,9 @@ vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) { float power = specularDistribution(surface); vec3 specular = fresnelColor * power * angleAttenuation; - // Specular isn't divided by PI because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, 0.f); diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 6c77fc4a91..40709b3668 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -17,7 +17,9 @@ const int MAX_TEXCOORDS = 2; struct TexMapArray { - mat4 _texcoordTransforms[MAX_TEXCOORDS]; +// mat4 _texcoordTransforms[MAX_TEXCOORDS]; + mat4 _texcoordTransforms0; + mat4 _texcoordTransforms1; vec4 _lightmapParams; }; @@ -31,13 +33,13 @@ TexMapArray getTexMapArray() { <@func evalTexMapArrayTexcoord0(texMapArray, inTexcoord0, outTexcoord0)@> { - <$outTexcoord0$> = (<$texMapArray$>._texcoordTransforms[0] * vec4(<$inTexcoord0$>.st, 0.0, 1.0)).st; + <$outTexcoord0$> = (<$texMapArray$>._texcoordTransforms0 * vec4(<$inTexcoord0$>.st, 0.0, 1.0)).st; } <@endfunc@> <@func evalTexMapArrayTexcoord1(texMapArray, inTexcoord1, outTexcoord1)@> { - <$outTexcoord1$> = (<$texMapArray$>._texcoordTransforms[1] * vec4(<$inTexcoord1$>.st, 0.0, 1.0)).st; + <$outTexcoord1$> = (<$texMapArray$>._texcoordTransforms1 * vec4(<$inTexcoord1$>.st, 0.0, 1.0)).st; } <@endfunc@> diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d7c038ea7d..45c77a606d 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -145,7 +145,13 @@ void Model::setTransformNoUpdateRenderItems(const Transform& transform) { } Transform Model::getTransform() const { - if (_spatiallyNestableOverride) { + if (_overrideModelTransform) { + Transform transform; + transform.setTranslation(getOverrideTranslation()); + transform.setRotation(getOverrideRotation()); + transform.setScale(getScale()); + return transform; + } else if (_spatiallyNestableOverride) { bool success; Transform transform = _spatiallyNestableOverride->getTransform(success); if (success) { @@ -1092,7 +1098,9 @@ void Blender::run() { int index = blendshape.indices.at(j); meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; - meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient; + if (blendshape.tangents.size() > j) { + meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient; + } } } } @@ -1259,25 +1267,6 @@ void Model::updateClusterMatrices() { } } -void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) { - const FBXGeometry& geometry = getFBXGeometry(); - const QVector& freeLineage = geometry.joints.at(endIndex).freeLineage; - glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); - _rig.inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform); -} - -bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) { - const FBXGeometry& geometry = getFBXGeometry(); - const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; - return _rig.restoreJointPosition(jointIndex, fraction, priority, freeLineage); -} - -float Model::getLimbLength(int jointIndex) const { - const FBXGeometry& geometry = getFBXGeometry(); - const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; - return _rig.getLimbLength(jointIndex, freeLineage, _scale, geometry.joints); -} - bool Model::maybeStartBlender() { if (isLoaded()) { const FBXGeometry& fbxGeometry = getFBXGeometry(); @@ -1313,7 +1302,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo normalsAndTangents.clear(); normalsAndTangents.resize(normals.size()+tangents.size()); - assert(normalsAndTangents.size() == 2 * vertexCount); +// assert(normalsAndTangents.size() == 2 * vertexCount); // Interleave normals and tangents #if 0 @@ -1381,6 +1370,14 @@ void Model::deleteGeometry() { _collisionGeometry.reset(); } +void Model::overrideModelTransformAndOffset(const Transform& transform, const glm::vec3& offset) { + _overrideTranslation = transform.getTranslation(); + _overrideRotation = transform.getRotation(); + _overrideModelTransform = true; + setScale(transform.getScale()); + setOffset(offset); +} + AABox Model::getRenderableMeshBound() const { if (!isLoaded()) { return AABox(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6c1f9cb75c..560aa82f0c 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -199,8 +199,6 @@ public: /// Returns the index of the parent of the indexed joint, or -1 if not found. int getParentJointIndex(int jointIndex) const; - void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority); - /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; @@ -212,10 +210,15 @@ public: void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); + void overrideModelTransformAndOffset(const Transform& transform, const glm::vec3& offset); + bool isOverridingModelTransformAndOffset() { return _overrideModelTransform; }; + void stopTransformAndOffsetOverride() { _overrideModelTransform = false; }; void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK const glm::vec3& getTranslation() const { return _translation; } const glm::quat& getRotation() const { return _rotation; } + const glm::vec3& getOverrideTranslation() const { return _overrideTranslation; } + const glm::quat& getOverrideRotation() const { return _overrideRotation; } glm::vec3 getNaturalDimensions() const; @@ -343,6 +346,9 @@ protected: glm::quat _rotation; glm::vec3 _scale; + glm::vec3 _overrideTranslation; + glm::quat _overrideRotation; + // For entity models this is the translation for the minimum extent of the model (in original mesh coordinate space) // to the model's registration point. For avatar models this is the translation from the avatar's hips, as determined // by the default pose, to the origin. @@ -368,16 +374,6 @@ protected: void computeMeshPartLocalBounds(); virtual void updateRig(float deltaTime, glm::mat4 parentTransform); - /// Restores the indexed joint to its default position. - /// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to - /// the original position - /// \return true if the joint was found - bool restoreJointPosition(int jointIndex, float fraction = 1.0f, float priority = 0.0f); - - /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's - /// first free ancestor. - float getLimbLength(int jointIndex) const; - /// Allow sub classes to force invalidating the bboxes void invalidCalculatedMeshBoxes() { _triangleSetsValid = false; @@ -403,6 +399,7 @@ protected: QMutex _mutex; + bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; void calculateTriangleSets(); QVector _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp new file mode 100644 index 0000000000..a824a2221b --- /dev/null +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -0,0 +1,240 @@ +// +// Created by Bradley Austin Davis on 2018/01/09 +// 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 "RenderCommonTask.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "LightingModel.h" +#include "StencilMaskPass.h" +#include "DebugDeferredBuffer.h" +#include "DeferredFramebuffer.h" +#include "DeferredLightingEffect.h" +#include "SurfaceGeometryPass.h" +#include "FramebufferCache.h" +#include "TextureCache.h" +#include "ZoneRenderer.h" +#include "FadeEffect.h" +#include "RenderUtilsLogging.h" + +#include "AmbientOcclusionEffect.h" +#include "AntialiasingEffect.h" +#include "ToneMappingEffect.h" +#include "SubsurfaceScattering.h" +#include "DrawHaze.h" +#include "BloomEffect.h" +#include "HighlightEffect.h" + +#include + +using namespace render; +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); + +void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { + timer = _gpuTimer; + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + _gpuTimer->begin(batch); + }); +} + +void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer) { + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + timer->end(batch); + }); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); +} + +DrawOverlay3D::DrawOverlay3D(bool opaque) : + _shapePlumber(std::make_shared()), + _opaquePass(opaque) { + initOverlay3DPipelines(*_shapePlumber); +} + +void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + + auto config = std::static_pointer_cast(renderContext->jobConfig); + + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); + + config->setNumDrawn((int)inItems.size()); + emit config->numDrawnChanged(); + + if (!inItems.empty()) { + RenderArgs* args = renderContext->args; + + // Clear the framebuffer without stereo + // Needs to be distinct from the other batch because using the clear call + // while stereo is enabled triggers a warning + if (_opaquePass) { + gpu::doInBatch(args->_context, [&](gpu::Batch& batch){ + batch.enableStereo(false); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); + }); + } + + // Render the items + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + // Setup lighting model for all items; + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + + renderShapes(renderContext, _shapePlumber, inItems, _maxDrawn); + args->_batch = nullptr; + }); + } +} + +void CompositeHUD::run(const RenderContextPointer& renderContext) { + assert(renderContext->args); + assert(renderContext->args->_context); + + // We do not want to render HUD elements in secondary camera + if (renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { + return; + } + + // Grab the HUD texture +#if !defined(DISABLE_QML) + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + if (renderContext->args->_hudOperator) { + renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); + } + }); +#endif +} + +void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { + assert(renderContext->args); + assert(renderContext->args->_context); + + RenderArgs* renderArgs = renderContext->args; + auto blitFbo = renderArgs->_blitFramebuffer; + + if (!blitFbo) { + qCWarning(renderutils) << "Blit::run - no blit frame buffer."; + return; + } + + // Determine size from viewport + int width = renderArgs->_viewport.z; + int height = renderArgs->_viewport.w; + + // Blit primary to blit FBO + auto primaryFbo = srcFramebuffer; + + gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { + batch.setFramebuffer(blitFbo); + + if (renderArgs->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + if (renderArgs->isStereo()) { + gpu::Vec4i srcRectLeft; + srcRectLeft.z = width / 2; + srcRectLeft.w = height; + + gpu::Vec4i srcRectRight; + srcRectRight.x = width / 2; + srcRectRight.z = width; + srcRectRight.w = height; + + gpu::Vec4i destRectLeft; + destRectLeft.x = srcRectLeft.z; + destRectLeft.z = srcRectLeft.x; + destRectLeft.y = srcRectLeft.y; + destRectLeft.w = srcRectLeft.w; + + gpu::Vec4i destRectRight; + destRectRight.x = srcRectRight.z; + destRectRight.z = srcRectRight.x; + destRectRight.y = srcRectRight.y; + destRectRight.w = srcRectRight.w; + + // Blit left to right and right to left in stereo + batch.blit(primaryFbo, srcRectRight, blitFbo, destRectLeft); + batch.blit(primaryFbo, srcRectLeft, blitFbo, destRectRight); + } else { + gpu::Vec4i srcRect; + srcRect.z = width; + srcRect.w = height; + + gpu::Vec4i destRect; + destRect.x = width; + destRect.y = 0; + destRect.z = 0; + destRect.w = height; + + batch.blit(primaryFbo, srcRect, blitFbo, destRect); + } + } else { + gpu::Vec4i rect; + rect.z = width; + rect.w = height; + + batch.blit(primaryFbo, rect, blitFbo, rect); + } + }); +} + +void ExtractFrustums::run(const render::RenderContextPointer& renderContext, Output& output) { + assert(renderContext->args); + assert(renderContext->args->_context); + + RenderArgs* args = renderContext->args; + + // Return view frustum + auto& viewFrustum = output[VIEW_FRUSTUM].edit(); + if (!viewFrustum) { + viewFrustum = std::make_shared(args->getViewFrustum()); + } else { + *viewFrustum = args->getViewFrustum(); + } + + // Return shadow frustum + auto lightStage = args->_scene->getStage(LightStage::getName()); + for (auto i = 0; i < SHADOW_CASCADE_FRUSTUM_COUNT; i++) { + auto& shadowFrustum = output[SHADOW_CASCADE0_FRUSTUM+i].edit(); + if (lightStage) { + auto globalShadow = lightStage->getCurrentKeyShadow(); + + if (globalShadow && i<(int)globalShadow->getCascadeCount()) { + auto& cascade = globalShadow->getCascade(i); + shadowFrustum = cascade.getFrustum(); + } else { + shadowFrustum.reset(); + } + } else { + shadowFrustum.reset(); + } + } +} diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h new file mode 100644 index 0000000000..767b6893cd --- /dev/null +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -0,0 +1,116 @@ +// +// Created by Bradley Austin Davis on 2018/01/09 +// 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_RenderCommonTask_h +#define hifi_RenderCommonTask_h + +#include +#include +#include "LightingModel.h" + +class BeginGPURangeTimer { +public: + using JobModel = render::Job::ModelO; + + BeginGPURangeTimer(const std::string& name) : _gpuTimer(std::make_shared(name)) {} + + void run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer); + +protected: + gpu::RangeTimerPointer _gpuTimer; +}; + +using GPURangeTimerConfig = render::GPUJobConfig; + +class EndGPURangeTimer { +public: + using Config = GPURangeTimerConfig; + using JobModel = render::Job::ModelI; + + EndGPURangeTimer() {} + + void configure(const Config& config) {} + void run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer); + +protected: +}; + +class DrawOverlay3DConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) + Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) +public: + int getNumDrawn() { return numDrawn; } + void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); } + + int maxDrawn{ -1 }; + +signals: + void numDrawnChanged(); + void dirty(); + +protected: + int numDrawn{ 0 }; +}; + +class DrawOverlay3D { +public: + using Inputs = render::VaryingSet2 ; + + using Config = DrawOverlay3DConfig; + using JobModel = render::Job::ModelI; + + DrawOverlay3D(bool opaque); + + void configure(const Config& config) { _maxDrawn = config.maxDrawn; } + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +protected: + render::ShapePlumberPointer _shapePlumber; + int _maxDrawn; // initialized by Config + bool _opaquePass{ true }; +}; + +class CompositeHUD { +public: + using JobModel = render::Job::Model; + + CompositeHUD() {} + void run(const render::RenderContextPointer& renderContext); +}; + +class Blit { +public: + using JobModel = render::Job::ModelI; + + void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer); +}; + +class ExtractFrustums { +public: + + enum Frustum { + SHADOW_CASCADE0_FRUSTUM = 0, + SHADOW_CASCADE1_FRUSTUM, + SHADOW_CASCADE2_FRUSTUM, + SHADOW_CASCADE3_FRUSTUM, + + SHADOW_CASCADE_FRUSTUM_COUNT, + + VIEW_FRUSTUM = SHADOW_CASCADE_FRUSTUM_COUNT, + + FRUSTUM_COUNT + }; + + using Output = render::VaryingArray; + using JobModel = render::Job::ModelO; + + void run(const render::RenderContextPointer& renderContext, Output& output); +}; + +#endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 8b97f902ac..a3c01f7fb3 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -25,6 +25,7 @@ #include #include +#include "RenderCommonTask.h" #include "LightingModel.h" #include "StencilMaskPass.h" #include "DebugDeferredBuffer.h" @@ -289,23 +290,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("Blit", primaryFramebuffer); } -void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { - timer = _gpuTimer; - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { - _gpuTimer->begin(batch); - }); -} - -void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer) { - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { - timer->end(batch); - }); - - auto config = std::static_pointer_cast(renderContext->jobConfig); - config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); -} - - void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -335,11 +319,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); - // Setup haze iff curretn zone has haze + // Setup haze iff current zone has haze auto hazeStage = args->_scene->getStage(); if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { graphics::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); - batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + if (hazePointer) { + batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + } } // From the lighting model define a global shapKey ORED with individiual keys @@ -410,177 +396,3 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const config->setNumDrawn((int)inItems.size()); } -DrawOverlay3D::DrawOverlay3D(bool opaque) : - _shapePlumber(std::make_shared()), - _opaquePass(opaque) { - initOverlay3DPipelines(*_shapePlumber); -} - -void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - - auto config = std::static_pointer_cast(renderContext->jobConfig); - - const auto& inItems = inputs.get0(); - const auto& lightingModel = inputs.get1(); - - config->setNumDrawn((int)inItems.size()); - emit config->numDrawnChanged(); - - if (!inItems.empty()) { - RenderArgs* args = renderContext->args; - - // Clear the framebuffer without stereo - // Needs to be distinct from the other batch because using the clear call - // while stereo is enabled triggers a warning - if (_opaquePass) { - gpu::doInBatch(args->_context, [&](gpu::Batch& batch){ - batch.enableStereo(false); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); - }); - } - - // Render the items - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - - // Setup lighting model for all items; - batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); - - renderShapes(renderContext, _shapePlumber, inItems, _maxDrawn); - args->_batch = nullptr; - }); - } -} - -void CompositeHUD::run(const RenderContextPointer& renderContext) { - assert(renderContext->args); - assert(renderContext->args->_context); - - // We do not want to render HUD elements in secondary camera - if (renderContext->args->_renderMode == RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) { - return; - } - - // Grab the HUD texture - gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { - if (renderContext->args->_hudOperator) { - renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); - } - }); -} - -void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { - assert(renderContext->args); - assert(renderContext->args->_context); - - RenderArgs* renderArgs = renderContext->args; - auto blitFbo = renderArgs->_blitFramebuffer; - - if (!blitFbo) { - qCWarning(renderutils) << "Blit::run - no blit frame buffer."; - return; - } - - // Determine size from viewport - int width = renderArgs->_viewport.z; - int height = renderArgs->_viewport.w; - - // Blit primary to blit FBO - auto primaryFbo = srcFramebuffer; - - gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) { - batch.setFramebuffer(blitFbo); - - if (renderArgs->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { - if (renderArgs->isStereo()) { - gpu::Vec4i srcRectLeft; - srcRectLeft.z = width / 2; - srcRectLeft.w = height; - - gpu::Vec4i srcRectRight; - srcRectRight.x = width / 2; - srcRectRight.z = width; - srcRectRight.w = height; - - gpu::Vec4i destRectLeft; - destRectLeft.x = srcRectLeft.z; - destRectLeft.z = srcRectLeft.x; - destRectLeft.y = srcRectLeft.y; - destRectLeft.w = srcRectLeft.w; - - gpu::Vec4i destRectRight; - destRectRight.x = srcRectRight.z; - destRectRight.z = srcRectRight.x; - destRectRight.y = srcRectRight.y; - destRectRight.w = srcRectRight.w; - - // Blit left to right and right to left in stereo - batch.blit(primaryFbo, srcRectRight, blitFbo, destRectLeft); - batch.blit(primaryFbo, srcRectLeft, blitFbo, destRectRight); - } else { - gpu::Vec4i srcRect; - srcRect.z = width; - srcRect.w = height; - - gpu::Vec4i destRect; - destRect.x = width; - destRect.y = 0; - destRect.z = 0; - destRect.w = height; - - batch.blit(primaryFbo, srcRect, blitFbo, destRect); - } - } else { - gpu::Vec4i rect; - rect.z = width; - rect.w = height; - - batch.blit(primaryFbo, rect, blitFbo, rect); - } - }); -} - -void ExtractFrustums::run(const render::RenderContextPointer& renderContext, Output& output) { - assert(renderContext->args); - assert(renderContext->args->_context); - - RenderArgs* args = renderContext->args; - - // Return view frustum - auto& viewFrustum = output[VIEW_FRUSTUM].edit(); - if (!viewFrustum) { - viewFrustum = std::make_shared(args->getViewFrustum()); - } else { - *viewFrustum = args->getViewFrustum(); - } - - // Return shadow frustum - auto lightStage = args->_scene->getStage(LightStage::getName()); - for (auto i = 0; i < SHADOW_CASCADE_FRUSTUM_COUNT; i++) { - auto& shadowFrustum = output[SHADOW_CASCADE0_FRUSTUM+i].edit(); - if (lightStage) { - auto globalShadow = lightStage->getCurrentKeyShadow(); - - if (globalShadow && i<(int)globalShadow->getCascadeCount()) { - auto& cascade = globalShadow->getCascade(i); - shadowFrustum = cascade.getFrustum(); - } else { - shadowFrustum.reset(); - } - } else { - shadowFrustum.reset(); - } - } -} diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index e41b7edc77..00804f7e7b 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -16,42 +16,17 @@ #include #include "LightingModel.h" -class BeginGPURangeTimer { -public: - using JobModel = render::Job::ModelO; - - BeginGPURangeTimer(const std::string& name) : _gpuTimer(std::make_shared(name)) {} - - void run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer); - -protected: - gpu::RangeTimerPointer _gpuTimer; -}; - -using GPURangeTimerConfig = render::GPUJobConfig; - -class EndGPURangeTimer { -public: - using Config = GPURangeTimerConfig; - using JobModel = render::Job::ModelI; - - EndGPURangeTimer() {} - - void configure(const Config& config) {} - void run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer); - -protected: -}; - -class DrawConfig : public render::Job::Config { +class DrawDeferredConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY newStats) Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) public: - int getNumDrawn() { return _numDrawn; } - void setNumDrawn(int numDrawn) { _numDrawn = numDrawn; emit newStats(); } + void setNumDrawn(int numDrawn) { + _numDrawn = numDrawn; + emit newStats(); + } int maxDrawn{ -1 }; @@ -65,29 +40,32 @@ protected: class DrawDeferred { public: - using Inputs = render::VaryingSet2 ; - using Config = DrawConfig; + using Inputs = render::VaryingSet2; + using Config = DrawDeferredConfig; using JobModel = render::Job::ModelI; - DrawDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} + DrawDeferred(render::ShapePlumberPointer shapePlumber) + : _shapePlumber{ shapePlumber } {} void configure(const Config& config) { _maxDrawn = config.maxDrawn; } void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; - int _maxDrawn; // initialized by Config + int _maxDrawn; // initialized by Config }; class DrawStateSortConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) - Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) - Q_PROPERTY(bool stateSort MEMBER stateSort NOTIFY dirty) + Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) + Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) + Q_PROPERTY(bool stateSort MEMBER stateSort NOTIFY dirty) public: - int getNumDrawn() { return numDrawn; } - void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); } + void setNumDrawn(int num) { + numDrawn = num; + emit numDrawnChanged(); + } int maxDrawn{ -1 }; bool stateSort{ true }; @@ -102,101 +80,32 @@ protected: class DrawStateSortDeferred { public: - using Inputs = render::VaryingSet2 ; + using Inputs = render::VaryingSet2; using Config = DrawStateSortConfig; using JobModel = render::Job::ModelI; - DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} + DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) + : _shapePlumber{ shapePlumber } {} - void configure(const Config& config) { _maxDrawn = config.maxDrawn; _stateSort = config.stateSort; } + void configure(const Config& config) { + _maxDrawn = config.maxDrawn; + _stateSort = config.stateSort; + } void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; - int _maxDrawn; // initialized by Config + int _maxDrawn; // initialized by Config bool _stateSort; }; -class DrawOverlay3DConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) - Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty) -public: - int getNumDrawn() { return numDrawn; } - void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); } - - int maxDrawn{ -1 }; - -signals: - void numDrawnChanged(); - void dirty(); - -protected: - int numDrawn{ 0 }; -}; - -class DrawOverlay3D { -public: - using Inputs = render::VaryingSet2 ; - - using Config = DrawOverlay3DConfig; - using JobModel = render::Job::ModelI; - - DrawOverlay3D(bool opaque); - - void configure(const Config& config) { _maxDrawn = config.maxDrawn; } - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - -protected: - render::ShapePlumberPointer _shapePlumber; - int _maxDrawn; // initialized by Config - bool _opaquePass { true }; -}; - -class CompositeHUD { -public: - using JobModel = render::Job::Model; - - CompositeHUD() {} - void run(const render::RenderContextPointer& renderContext); -}; - -class Blit { -public: - using JobModel = render::Job::ModelI; - - void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer); -}; - -class ExtractFrustums { -public: - - enum Frustum { - SHADOW_CASCADE0_FRUSTUM = 0, - SHADOW_CASCADE1_FRUSTUM, - SHADOW_CASCADE2_FRUSTUM, - SHADOW_CASCADE3_FRUSTUM, - - SHADOW_CASCADE_FRUSTUM_COUNT, - - VIEW_FRUSTUM = SHADOW_CASCADE_FRUSTUM_COUNT, - - FRUSTUM_COUNT - }; - - using Output = render::VaryingArray; - using JobModel = render::Job::ModelO; - - void run(const render::RenderContextPointer& renderContext, Output& output); -}; - class RenderDeferredTaskConfig : public render::Task::Config { Q_OBJECT - Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty) - Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty) - Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty) - Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty) + Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty) + Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty) + Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty) + Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty) public: float fadeScale{ 0.5f }; float fadeDuration{ 3.0f }; @@ -205,7 +114,6 @@ public: signals: void dirty(); - }; class RenderDeferredTask { @@ -220,9 +128,11 @@ public: void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); private: - - static const render::Varying addSelectItemJobs(JobModel& task, const char* selectionName, - const render::Varying& metas, const render::Varying& opaques, const render::Varying& transparents); + static const render::Varying addSelectItemJobs(JobModel& task, + const char* selectionName, + const render::Varying& metas, + const render::Varying& opaques, + const render::Varying& transparents); }; -#endif // hifi_RenderDeferredTask_h +#endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 1eeac7f449..c201e8a106 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -11,59 +11,95 @@ // #include "RenderForwardTask.h" -#include "RenderDeferredTask.h" #include #include #include #include -#include "StencilMaskPass.h" +#include +#include +#include "StencilMaskPass.h" +#include "ZoneRenderer.h" +#include "FadeEffect.h" +#include "BackgroundStage.h" #include "FramebufferCache.h" #include "TextureCache.h" - -#include +#include "RenderCommonTask.h" +#include "LightStage.h" #include "nop_frag.h" using namespace render; -extern void initForwardPipelines(ShapePlumber& plumber); +extern void initForwardPipelines(ShapePlumber& plumber, + const render::ShapePipeline::BatchSetter& batchSetter, + const render::ShapePipeline::ItemSetter& itemSetter); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { auto items = input.get(); + auto fadeEffect = DependencyManager::get(); // Prepare the ShapePipelines ShapePlumberPointer shapePlumber = std::make_shared(); - initForwardPipelines(*shapePlumber); + initForwardPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); // Extract opaques / transparents / lights / metas / overlays / background const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; -// const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; -// const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT]; -// const auto& metas = items.get0()[RenderFetchCullSortTask::META]; -// const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; -// const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; - const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND]; -// const auto& spatialSelection = items[1]; + const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + // const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT]; + const auto& metas = items.get0()[RenderFetchCullSortTask::META]; + // const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; + // const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; + //const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND]; + // const auto& spatialSelection = items[1]; + fadeEffect->build(task, opaques); + + // Prepare objects shared by several jobs + const auto lightingModel = task.addJob("LightingModel"); + + // Filter zones from the general metas bucket + const auto zones = task.addJob("ZoneRenderer", metas); + + // GPU jobs: Start preparing the main framebuffer const auto framebuffer = task.addJob("PrepareFramebuffer"); - task.addJob("DrawOpaques", opaques, shapePlumber); - task.addJob("Stencil"); - task.addJob("DrawBackground", background); + task.addJob("PrepareForward", lightingModel); - // Bounds do not draw on stencil buffer, so they must come last - task.addJob("DrawBounds", opaques); + // draw a stencil mask in hidden regions of the framebuffer. + task.addJob("PrepareStencil", framebuffer); + + // Draw opaques forward + const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying(); + task.addJob("DrawOpaques", opaqueInputs, shapePlumber); + + // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job + task.addJob("DrawBackgroundDeferred", lightingModel); + + // Draw transparent objects forward + const auto transparentInputs = DrawForward::Inputs(transparents, lightingModel).asVarying(); + task.addJob("DrawTransparents", transparentInputs, shapePlumber); + + { // Debug the bounds of the rendered items, still look at the zbuffer + + task.addJob("DrawMetaBounds", metas); + task.addJob("DrawBounds", opaques); + task.addJob("DrawTransparentBounds", transparents); + + task.addJob("DrawZones", zones); + } + + // Layered Overlays + + // Composite the HUD and HUD overlays + task.addJob("HUD"); // Blit! task.addJob("Blit", framebuffer); } -void PrepareFramebuffer::run(const RenderContextPointer& renderContext, - gpu::FramebufferPointer& framebuffer) { - auto framebufferCache = DependencyManager::get(); - auto framebufferSize = framebufferCache->getFrameBufferSize(); - glm::uvec2 frameSize(framebufferSize.width(), framebufferSize.height()); +void PrepareFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& framebuffer) { + glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w); // Resizing framebuffers instead of re-building them seems to cause issues with threaded rendering if (_framebuffer && _framebuffer->getSize() != frameSize) { @@ -75,11 +111,13 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, auto colorFormat = gpu::Element::COLOR_SRGBA_32; auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - auto colorTexture = gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + auto colorTexture = + gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); _framebuffer->setRenderBuffer(0, colorTexture); - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format - auto depthTexture = gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + auto depthTexture = + gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); _framebuffer->setDepthStencilBuffer(depthTexture, depthFormat); } @@ -90,23 +128,86 @@ void PrepareFramebuffer::run(const RenderContextPointer& renderContext, batch.setStateScissorRect(args->_viewport); batch.setFramebuffer(_framebuffer); - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | - gpu::Framebuffer::BUFFER_DEPTH | + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH | gpu::Framebuffer::BUFFER_STENCIL, - vec4(vec3(0), 1), 1.0, 0, true); + vec4(vec3(0, 1.0, 0.0), 0), 1.0, 0, true); }); framebuffer = _framebuffer; } -void Draw::run(const RenderContextPointer& renderContext, - const Inputs& items) { +enum ForwardShader_MapSlot { + DEFERRED_BUFFER_COLOR_UNIT = 0, + DEFERRED_BUFFER_NORMAL_UNIT = 1, + DEFERRED_BUFFER_EMISSIVE_UNIT = 2, + DEFERRED_BUFFER_DEPTH_UNIT = 3, + DEFERRED_BUFFER_OBSCURANCE_UNIT = 4, + SHADOW_MAP_UNIT = 5, + SKYBOX_MAP_UNIT = SHADOW_MAP_UNIT + 4, + DEFERRED_BUFFER_LINEAR_DEPTH_UNIT, + DEFERRED_BUFFER_CURVATURE_UNIT, + DEFERRED_BUFFER_DIFFUSED_CURVATURE_UNIT, + SCATTERING_LUT_UNIT, + SCATTERING_SPECULAR_UNIT, +}; +enum ForwardShader_BufferSlot { + DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, + CAMERA_CORRECTION_BUFFER_SLOT, + SCATTERING_PARAMETERS_BUFFER_SLOT, + LIGHTING_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::LIGHTING_MODEL, + LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, + LIGHT_AMBIENT_SLOT = render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER, + HAZE_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::HAZE_MODEL, + LIGHT_INDEX_GPU_SLOT, + LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, + LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, + LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, +}; + +void PrepareForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { RenderArgs* args = renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + graphics::LightPointer keySunLight; + auto lightStage = args->_scene->getStage(); + if (lightStage) { + keySunLight = lightStage->getCurrentKeyLight(); + } + + graphics::LightPointer keyAmbiLight; + if (lightStage) { + keyAmbiLight = lightStage->getCurrentAmbientLight(); + } + + if (keySunLight) { + if (LIGHT_GPU_SLOT >= 0) { + batch.setUniformBuffer(LIGHT_GPU_SLOT, keySunLight->getLightSchemaBuffer()); + } + } + + if (keyAmbiLight) { + if (LIGHT_AMBIENT_SLOT >= 0) { + batch.setUniformBuffer(LIGHT_AMBIENT_SLOT, keyAmbiLight->getAmbientSchemaBuffer()); + } + + if (keyAmbiLight->getAmbientMap() && (SKYBOX_MAP_UNIT >= 0)) { + batch.setResourceTexture(SKYBOX_MAP_UNIT, keyAmbiLight->getAmbientMap()); + } + } + }); +} + +void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& inputs) { + RenderArgs* args = renderContext->args; + + const auto& inItems = inputs.get0(); + const auto& lightingModel = inputs.get1(); gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + // Setup projection glm::mat4 projMat; Transform viewMat; @@ -116,66 +217,23 @@ void Draw::run(const RenderContextPointer& renderContext, batch.setViewTransform(viewMat); batch.setModelTransform(Transform()); + // Setup lighting model for all items; + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + + // From the lighting model define a global shapeKey ORED with individiual keys + ShapeKey::Builder keyBuilder; + if (lightingModel->isWireframeEnabled()) { + keyBuilder.withWireframe(); + } + ShapeKey globalKey = keyBuilder.build(); + args->_globalShapeKey = globalKey._flags.to_ulong(); + // Render items - renderStateSortShapes(renderContext, _shapePlumber, items, -1); + renderStateSortShapes(renderContext, _shapePlumber, inItems, -1, globalKey); + + args->_batch = nullptr; + args->_globalShapeKey = 0; }); - args->_batch = nullptr; -} - -const gpu::PipelinePointer Stencil::getPipeline() { - if (!_stencilPipeline) { - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = nop_frag::getShader(); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program); - - auto state = std::make_shared(); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - PrepareStencil::drawBackground(*state); - - _stencilPipeline = gpu::Pipeline::create(program, state); - } - return _stencilPipeline; -} - -void Stencil::run(const RenderContextPointer& renderContext) { - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - batch.setPipeline(getPipeline()); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - args->_batch = nullptr; -} - -void DrawBackground::run(const RenderContextPointer& renderContext, - const Inputs& background) { - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.enableSkybox(true); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - // Setup projection - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - - renderItems(renderContext, background); - }); - args->_batch = nullptr; } diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 63d087b3c5..22e75d4bdc 100755 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -38,37 +38,28 @@ private: gpu::FramebufferPointer _framebuffer; }; -class Draw { +class PrepareForward { public: - using Inputs = render::ItemBounds; - using JobModel = render::Job::ModelI; + using Inputs = LightingModelPointer; + using JobModel = render::Job::ModelI; - Draw(const render::ShapePlumberPointer& shapePlumber) : _shapePlumber(shapePlumber) {} void run(const render::RenderContextPointer& renderContext, - const Inputs& items); + const Inputs& inputs); + +private: +}; + +class DrawForward{ +public: + using Inputs = render::VaryingSet2; + using JobModel = render::Job::ModelI; + + DrawForward(const render::ShapePlumberPointer& shapePlumber) : _shapePlumber(shapePlumber) {} + void run(const render::RenderContextPointer& renderContext, + const Inputs& inputs); private: render::ShapePlumberPointer _shapePlumber; }; -class Stencil { -public: - using JobModel = render::Job::Model; - - void run(const render::RenderContextPointer& renderContext); - -private: - const gpu::PipelinePointer getPipeline(); - gpu::PipelinePointer _stencilPipeline; -}; - -class DrawBackground { -public: - using Inputs = render::ItemBounds; - using JobModel = render::Job::ModelI; - - void run(const render::RenderContextPointer& renderContext, - const Inputs& background); -}; - #endif // hifi_RenderForwardTask_h diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 99c207e11b..cdd6f2b805 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -106,7 +106,7 @@ using namespace std::placeholders; void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest = false); void initDeferredPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); -void initForwardPipelines(ShapePlumber& plumber); +void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); void addPlumberPipeline(ShapePlumber& plumber, @@ -436,12 +436,13 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelShadowFadeVertex, modelShadowFadePixel, batchSetter, itemSetter); } -void initForwardPipelines(render::ShapePlumber& plumber) { +void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { // Vertex shaders auto modelVertex = model_vert::getShader(); auto modelNormalMapVertex = model_normal_map_vert::getShader(); auto skinModelVertex = skin_model_vert::getShader(); auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader(); + auto skinModelNormalMapFadeVertex = skin_model_normal_map_fade_vert::getShader(); // Pixel shaders auto modelPixel = forward_model_frag::getShader(); @@ -449,38 +450,43 @@ void initForwardPipelines(render::ShapePlumber& plumber) { auto modelNormalMapPixel = forward_model_normal_map_frag::getShader(); auto modelSpecularMapPixel = forward_model_specular_map_frag::getShader(); auto modelNormalSpecularMapPixel = forward_model_normal_specular_map_frag::getShader(); + auto modelNormalMapFadePixel = model_normal_map_fade_frag::getShader(); using Key = render::ShapeKey; - auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, nullptr, nullptr); + auto addPipeline = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4, _5); // Opaques addPipeline( Key::Builder().withMaterial(), - modelVertex, modelPixel); + modelVertex, modelPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withUnlit(), - modelVertex, modelUnlitPixel); + modelVertex, modelUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTangents(), - modelNormalMapVertex, modelNormalMapPixel); + modelNormalMapVertex, modelNormalMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSpecular(), - modelVertex, modelSpecularMapPixel); + modelVertex, modelSpecularMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTangents().withSpecular(), - modelNormalMapVertex, modelNormalSpecularMapPixel); + modelNormalMapVertex, modelNormalSpecularMapPixel, nullptr, nullptr); // Skinned addPipeline( Key::Builder().withMaterial().withSkinned(), - skinModelVertex, modelPixel); + skinModelVertex, modelPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTangents(), - skinModelNormalMapVertex, modelNormalMapPixel); + skinModelNormalMapVertex, modelNormalMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withSpecular(), - skinModelVertex, modelSpecularMapPixel); + skinModelVertex, modelSpecularMapPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTangents().withSpecular(), - skinModelNormalMapVertex, modelNormalSpecularMapPixel); + skinModelNormalMapVertex, modelNormalSpecularMapPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withTangents().withFade(), + skinModelNormalMapFadeVertex, modelNormalMapFadePixel, batchSetter, itemSetter, nullptr, nullptr); + } void addPlumberPipeline(ShapePlumber& plumber, diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 49d0df3d2c..a7edfb14a6 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -39,12 +39,12 @@ mat4 dualQuatToMat4(vec4 real, vec4 dual) { twoRealXZ - twoRealYW, 0.0); vec4 col1 = vec4(twoRealXY - twoRealZW, - 1 - twoRealXSq - twoRealZSq, + 1.0 - twoRealXSq - twoRealZSq, twoRealYZ + twoRealXW, 0.0); vec4 col2 = vec4(twoRealXZ + twoRealYW, twoRealYZ - twoRealXW, - 1 - twoRealXSq - twoRealYSq, + 1.0 - twoRealXSq - twoRealYSq, 0.0); vec4 col3 = vec4(2.0 * (-dual.w * real.x + dual.x * real.w - dual.y * real.z + dual.z * real.y), 2.0 * (-dual.w * real.y + dual.x * real.z + dual.y * real.w - dual.z * real.x), @@ -72,7 +72,7 @@ void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPositio // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; - if (dot(real, polarityReference) < 0) { + if (dot(real, polarityReference) < 0.0) { dqClusterWeight = -clusterWeight; } @@ -110,7 +110,7 @@ void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inP // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; - if (dot(real, polarityReference) < 0) { + if (dot(real, polarityReference) < 0.0) { dqClusterWeight = -clusterWeight; } @@ -149,7 +149,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v // to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity. float dqClusterWeight = clusterWeight; - if (dot(real, polarityReference) < 0) { + if (dot(real, polarityReference) < 0.0) { dqClusterWeight = -clusterWeight; } diff --git a/libraries/render-utils/src/forward_model.slf b/libraries/render-utils/src/forward_model.slf index 41ff4afc81..65556e2939 100644 --- a/libraries/render-utils/src/forward_model.slf +++ b/libraries/render-utils/src/forward_model.slf @@ -68,7 +68,7 @@ void main(void) { vec3 fragNormal; <$transformEyeToWorldDir(cam, _normal, fragNormal)$> - /* vec4 color = vec4(evalSkyboxGlobalColor( + vec4 color = vec4(evalSkyboxGlobalColor( cam._viewInverse, 1.0, 1.0, @@ -81,8 +81,6 @@ void main(void) { opacity); color.rgb += emissive * isEmissiveEnabled(); - */ - - _fragColor = vec4(albedo, opacity); - +// _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/forward_model_normal_map.slf b/libraries/render-utils/src/forward_model_normal_map.slf index e5fcab4945..d22cc667e1 100644 --- a/libraries/render-utils/src/forward_model_normal_map.slf +++ b/libraries/render-utils/src/forward_model_normal_map.slf @@ -12,10 +12,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardBufferWrite.slh@> + !> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> <@include graphics/Material.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, _SCRIBE_NULL, EMISSIVE, OCCLUSION, SCATTERING)$> @@ -26,6 +32,8 @@ in vec3 _normal; in vec3 _tangent; in vec3 _color; +out vec4 _fragColor; + void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); @@ -40,6 +48,15 @@ void main(void) { <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; + float metallic = getMaterialMetallic(mat); + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } else { + fresnel = albedo; + metallic = 1.0; + } + float roughness = getMaterialRoughness(mat); <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; @@ -52,13 +69,24 @@ void main(void) { float scattering = getMaterialScattering(mat); <$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>; - packForwardFragment( - viewNormal, - opacity, + vec3 fragPosition = _position.xyz; + + TransformCamera cam = getTransformCamera(); + + vec4 color = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + viewNormal, albedo, - roughness, - getMaterialMetallic(mat), - emissive, - occlusionTex, - scattering); + fresnel, + metallic, + roughness), + opacity); + + color.rgb += emissive * isEmissiveEnabled(); + +// _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/forward_model_normal_specular_map.slf b/libraries/render-utils/src/forward_model_normal_specular_map.slf index 01a4251e8d..68e5b755f8 100644 --- a/libraries/render-utils/src/forward_model_normal_specular_map.slf +++ b/libraries/render-utils/src/forward_model_normal_specular_map.slf @@ -12,10 +12,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardBufferWrite.slh@> + + !> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> <@include graphics/Material.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> @@ -26,6 +33,8 @@ in vec3 _normal; in vec3 _tangent; in vec3 _color; +out vec4 _fragColor; + void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); @@ -51,16 +60,32 @@ void main(void) { float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } + else { + fresnel = albedo; + metallic = 1.0; + } + vec3 fragPosition = _position.xyz; - float scattering = getMaterialScattering(mat); + TransformCamera cam = getTransformCamera(); - packForwardFragment( - normalize(viewNormal.xyz), - opacity, + vec4 color = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + viewNormal, albedo, - roughness, + fresnel, metallic, - emissive, - occlusionTex, - scattering); + roughness), + opacity); + + color.rgb += emissive * isEmissiveEnabled(); + + // _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/forward_model_specular_map.slf b/libraries/render-utils/src/forward_model_specular_map.slf index 77d34d4309..057ec2cf53 100644 --- a/libraries/render-utils/src/forward_model_specular_map.slf +++ b/libraries/render-utils/src/forward_model_specular_map.slf @@ -12,10 +12,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include ForwardBufferWrite.slh@> + + !> + +<@include ForwardGlobalLight.slh@> +<$declareEvalSkyboxGlobalColor()$> <@include graphics/Material.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, METALLIC, EMISSIVE, OCCLUSION)$> @@ -25,12 +32,13 @@ in vec2 _texCoord1; in vec3 _normal; in vec3 _color; +out vec4 _fragColor; void main(void) { Material mat = getMaterial(); int matKey = getMaterialKey(mat); <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex)$> - <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> + !> float opacity = 1.0; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; @@ -48,7 +56,14 @@ void main(void) { float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; - + vec3 fresnel = vec3(0.03); // Default Di-electric fresnel value + if (metallic <= 0.5) { + metallic = 0.0; + } + else { + fresnel = albedo; + metallic = 1.0; + }/* float scattering = getMaterialScattering(mat); packForwardFragment( @@ -60,4 +75,26 @@ void main(void) { emissive, occlusionTex, scattering); + */ + vec3 fragPosition = _position.xyz; + + TransformCamera cam = getTransformCamera(); + vec3 fragNormal; + <$transformEyeToWorldDir(cam, _normal, fragNormal)$> + + vec4 color = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + 1.0, + fragPosition, + fragNormal, + albedo, + fresnel, + metallic, + roughness), + opacity); + color.rgb += emissive * isEmissiveEnabled(); + + // _fragColor = vec4(albedo, opacity); + _fragColor = color; } diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index bcd14a4fbc..f9508b343f 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -369,11 +369,15 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c batch.setPipeline(((*color).a < 1.0f || layered) ? _transparentPipeline : _pipeline); batch.setResourceTexture(_fontLoc, _texture); - batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT)); + if (_outlineLoc >= 0) { + batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT)); + } // need the gamma corrected color here glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color); - batch._glUniform4fv(_colorLoc, 1, (const float*)&lrgba); + if (_colorLoc >= 0) { + batch._glUniform4fv(_colorLoc, 1, (const float*)&lrgba); + } batch.setInputFormat(_format); batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 4254280fa1..eb279f18af 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -87,7 +87,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); - slotBindings.insert(gpu::Shader::Binding(std::string("hazeParametersBuffer"), Slot::BUFFER::HAZE_MODEL)); + slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL)); gpu::Shader::makeProgram(*program, slotBindings); @@ -131,7 +131,7 @@ const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Ke auto factoryIt = ShapePipeline::_globalCustomFactoryMap.find(key.getCustom()); if ((factoryIt != ShapePipeline::_globalCustomFactoryMap.end()) && (factoryIt)->second) { // found a factory for the custom key, can now generate a shape pipeline for this case: - addPipelineHelper(Filter(key), key, 0, (factoryIt)->second(*this, key)); + addPipelineHelper(Filter(key), key, 0, (factoryIt)->second(*this, key, *(args->_batch))); return pickPipeline(args, key); } else { diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index be77e2f95e..9e05cfb81b 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -301,7 +301,7 @@ protected: ItemSetter _itemSetter; public: using CustomKey = uint8_t; - using CustomFactory = std::function (const ShapePlumber& plumber, const ShapeKey& key)>; + using CustomFactory = std::function (const ShapePlumber& plumber, const ShapeKey& key, gpu::Batch& batch)>; using CustomFactoryMap = std::map; static CustomFactoryMap _globalCustomFactoryMap; diff --git a/libraries/script-engine/src/ArrayBufferClass.h b/libraries/script-engine/src/ArrayBufferClass.h index 69c2cc0799..166a21b773 100644 --- a/libraries/script-engine/src/ArrayBufferClass.h +++ b/libraries/script-engine/src/ArrayBufferClass.h @@ -19,6 +19,7 @@ #include #include #include +#include class ScriptEngine; diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.cpp b/libraries/script-engine/src/ConsoleScriptingInterface.cpp index f3ceee63f7..b4ef98938d 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.cpp +++ b/libraries/script-engine/src/ConsoleScriptingInterface.cpp @@ -15,6 +15,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include "ConsoleScriptingInterface.h" #include "ScriptEngine.h" diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c7034adf35..9d073453ae 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1390,7 +1390,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const { } QUrl ScriptEngine::resourcesPath() const { - return QUrl::fromLocalFile(PathUtils::resourcesPath()); + return QUrl(PathUtils::resourcesUrl()); } void ScriptEngine::print(const QString& message) { diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index f9b835df5c..3d61765988 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -7,5 +7,9 @@ if (WIN32) target_link_libraries(${TARGET_NAME} Wbemuuid.lib) endif() +if (ANDROID) + target_link_libraries(${TARGET_NAME} android) +endif() + target_zlib() target_nsight() \ No newline at end of file diff --git a/libraries/shared/src/BitVectorHelpers.h b/libraries/shared/src/BitVectorHelpers.h new file mode 100644 index 0000000000..71826036eb --- /dev/null +++ b/libraries/shared/src/BitVectorHelpers.h @@ -0,0 +1,58 @@ +// +// BitVectorHelpers.h +// libraries/shared/src +// +// Created by Anthony Thibault on 1/19/18. +// Copyright 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_BitVectorHelpers_h +#define hifi_BitVectorHelpers_h + +size_t calcBitVectorSize(int numBits) { + return ((numBits - 1) >> 3) + 1; +} + +// func should be of type bool func(int index) +template +size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) { + size_t totalBytes = ((numBits - 1) >> 3) + 1; + uint8_t* cursor = destinationBuffer; + uint8_t byte = 0; + uint8_t bit = 0; + + for (int i = 0; i < numBits; i++) { + if (func(i)) { + byte |= (1 << bit); + } + if (++bit == BITS_IN_BYTE) { + *cursor++ = byte; + byte = 0; + bit = 0; + } + } + return totalBytes; +} + +// func should be of type 'void func(int index, bool value)' +template +size_t readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) { + size_t totalBytes = ((numBits - 1) >> 3) + 1; + const uint8_t* cursor = sourceBuffer; + uint8_t bit = 0; + + for (int i = 0; i < numBits; i++) { + bool value = (bool)(*cursor & (1 << bit)); + func(i, value); + if (++bit == BITS_IN_BYTE) { + cursor++; + bit = 0; + } + } + return totalBytes; +} + +#endif diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 973998b927..4f761a4aac 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -253,6 +253,7 @@ glm::vec2 getFacingDir2D(const glm::mat4& m); inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } +inline bool isNaN(const glm::mat3& value) { return isNaN(value * glm::vec3(1.0f)); } glm::mat4 orthoInverse(const glm::mat4& m); diff --git a/libraries/shared/src/JointData.h b/libraries/shared/src/JointData.h index a05b5c649a..f4c8b89e7a 100644 --- a/libraries/shared/src/JointData.h +++ b/libraries/shared/src/JointData.h @@ -5,14 +5,27 @@ #include #include -// Used by the avatar mixer to describe a single joint -// These are relative to their parent and translations are in meters -class JointData { +class EntityJointData { public: glm::quat rotation; + glm::vec3 translation; bool rotationSet = false; - glm::vec3 translation; // meters bool translationSet = false; }; +// Used by the avatar mixer to describe a single joint +// Translations relative to their parent and are in meters. +// Rotations are absolute (i.e. not relative to parent) and are in rig space. +class JointData { +public: + glm::quat rotation; + glm::vec3 translation; + + // This indicates that the rotation or translation is the same as the defaultPose for the avatar. + // if true, it also means that the rotation or translation value in this structure is not valid and + // should be replaced by the avatar's actual default pose value. + bool rotationIsDefaultPose = true; + bool translationIsDefaultPose = true; +}; + #endif diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index b33b330fc0..35d7554f39 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -11,16 +11,25 @@ #include "PathUtils.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #include // std::once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(Q_OS_OSX) +#include +#endif + + #include "shared/GlobalAppProperties.h" #include "SharedUtil.h" @@ -28,14 +37,14 @@ // Example: ... QString TEMP_DIR_FORMAT { "%1-%2-%3" }; -const QString& PathUtils::resourcesPath() { -#ifdef Q_OS_MAC - static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; +#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) +#if defined(Q_OS_OSX) +static bool USE_SOURCE_TREE_RESOURCES = true; #else - static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; +static const QString USE_SOURCE_TREE_RESOURCES_FLAG("HIFI_USE_SOURCE_TREE_RESOURCES"); +static bool USE_SOURCE_TREE_RESOURCES = QProcessEnvironment::systemEnvironment().contains(USE_SOURCE_TREE_RESOURCES_FLAG); +#endif #endif - return staticResourcePath; -} #ifdef DEV_BUILD const QString& PathUtils::projectRootPath() { @@ -49,16 +58,71 @@ const QString& PathUtils::projectRootPath() { } #endif -const QString& PathUtils::qmlBasePath() { -#ifdef DEV_BUILD - static const QString staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/qml/").toString(); +const QString& PathUtils::resourcesPath() { + static QString staticResourcePath; + static std::once_flag once; + std::call_once(once, [&]{ + +#if defined(Q_OS_OSX) + // FIXME fix the OSX installer to install the resources.rcc instead of the + // individual resource files + // FIXME the first call to fetch the resources location seems to return + // nothing for QCoreApplication::applicationDirPath() + char buffer[8192]; + uint32_t bufferSize = sizeof(buffer); + _NSGetExecutablePath(buffer, &bufferSize); + staticResourcePath = QDir::cleanPath(QFileInfo(buffer).dir().absoluteFilePath("../Resources")) + "/"; #else - static const QString staticResourcePath = "qrc:///qml/"; + staticResourcePath = ":/"; #endif + +#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) + if (USE_SOURCE_TREE_RESOURCES) { + // For dev builds, optionally load content from the Git source tree + staticResourcePath = projectRootPath() + "/interface/resources/"; + } +#endif + qDebug() << "Resource path resolved to " << staticResourcePath; + }); return staticResourcePath; } +const QString& PathUtils::resourcesUrl() { + static QString staticResourcePath; + static std::once_flag once; + std::call_once(once, [&]{ + +#if defined(Q_OS_OSX) + staticResourcePath = QUrl::fromLocalFile(resourcesPath()).toString(); +#else + staticResourcePath = "qrc:///"; +#endif + +#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD) + if (USE_SOURCE_TREE_RESOURCES) { + // For dev builds, optionally load content from the Git source tree + staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/").toString(); + } +#endif + qDebug() << "Resource url resolved to " << staticResourcePath; + }); + return staticResourcePath; +} + +QUrl PathUtils::resourcesUrl(const QString& relativeUrl) { + return QUrl(resourcesUrl() + relativeUrl); +} + +const QString& PathUtils::qmlBaseUrl() { + static const QString staticResourcePath = resourcesUrl() + "qml/"; + return staticResourcePath; +} + +QUrl PathUtils::qmlUrl(const QString& relativeUrl) { + return QUrl(qmlBaseUrl() + relativeUrl); +} + QString PathUtils::getAppDataPath() { return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/"; } @@ -71,7 +135,11 @@ QString PathUtils::getAppLocalDataPath() { } // otherwise return standard path +#ifdef Q_OS_ANDROID + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/"; +#else return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/"; +#endif } QString PathUtils::getAppDataFilePath(const QString& filename) { diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 2b4fe35d97..ec71ccee6a 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -33,8 +33,11 @@ class PathUtils : public QObject, public Dependency { Q_PROPERTY(QString resources READ resourcesPath CONSTANT) Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT) public: + static const QString& resourcesUrl(); + static QUrl resourcesUrl(const QString& relative); static const QString& resourcesPath(); - static const QString& qmlBasePath(); + static const QString& qmlBaseUrl(); + static QUrl qmlUrl(const QString& relative); #ifdef DEV_BUILD static const QString& projectRootPath(); #endif @@ -54,7 +57,6 @@ public: // note: this is FS-case-sensitive version of parentURL.isParentOf(childURL) static bool isDescendantOf(const QUrl& childURL, const QUrl& parentURL); static QUrl defaultScriptsLocation(const QString& newDefault = ""); - }; QString fileNameWithoutExtension(const QString& fileName, const QVector possibleExtensions); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 20a5a76b73..324cee3417 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -464,6 +464,36 @@ glm::vec3 SpatiallyNestable::localToWorldDimensions(const glm::vec3& dimensions, return dimensions; } +void SpatiallyNestable::setWorldTransform(const glm::vec3& position, const glm::quat& orientation) { + // guard against introducing NaN into the transform + if (isNaN(orientation) || isNaN(position)) { + return; + } + + bool changed = false; + bool success = true; + Transform parentTransform = getParentTransform(success); + _transformLock.withWriteLock([&] { + Transform myWorldTransform; + Transform::mult(myWorldTransform, parentTransform, _transform); + if (myWorldTransform.getRotation() != orientation) { + changed = true; + myWorldTransform.setRotation(orientation); + } + if (myWorldTransform.getTranslation() != position) { + changed = true; + myWorldTransform.setTranslation(position); + } + if (changed) { + Transform::inverseMult(_transform, parentTransform, myWorldTransform); + _translationChanged = usecTimestampNow(); + } + }); + if (success && changed) { + locationChanged(false); + } +} + glm::vec3 SpatiallyNestable::getWorldPosition(bool& success) const { return getTransform(success).getTranslation(); } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 2a315e9230..090ca4c266 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -87,6 +87,7 @@ public: virtual Transform getParentTransform(bool& success, int depth = 0) const; + void setWorldTransform(const glm::vec3& position, const glm::quat& orientation); virtual glm::vec3 getWorldPosition(bool& success) const; virtual glm::vec3 getWorldPosition() const; virtual void setWorldPosition(const glm::vec3& position, bool& success, bool tellPhysics = true); diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index dba0af7b16..7b88321e99 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -12,6 +12,8 @@ #include "FileUtils.h" +#include + #include #include #include @@ -21,8 +23,21 @@ #include #include + #include "../SharedLogging.h" +const QStringList& FileUtils::getFileSelectors() { + static std::once_flag once; + static QStringList extraSelectors; + std::call_once(once, [] { +#if defined(USE_GLES) + extraSelectors << "gles"; +#endif + }); + return extraSelectors; + +} + QString FileUtils::readFile(const QString& filename) { QFile file(filename); @@ -84,7 +99,11 @@ void FileUtils::locateFile(QString filePath) { QString FileUtils::standardPath(QString subfolder) { // standard path // Mac: ~/Library/Application Support/Interface +#ifdef Q_OS_ANDROID + QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); +#else QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); +#endif if (!subfolder.startsWith("/")) { subfolder.prepend("/"); } diff --git a/libraries/shared/src/shared/FileUtils.h b/libraries/shared/src/shared/FileUtils.h index d68fcd8a44..9a5e2d5aba 100644 --- a/libraries/shared/src/shared/FileUtils.h +++ b/libraries/shared/src/shared/FileUtils.h @@ -13,10 +13,11 @@ #define hifi_FileUtils_h #include - +#include class FileUtils { public: + static const QStringList& getFileSelectors(); static void locateFile(QString fileName); static QString standardPath(QString subfolder); static QString readFile(const QString& filename); diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index 7e9f86b049..683903a4e8 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -55,7 +55,7 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u if (!file.resize(size)) { throw std::runtime_error("Unable to resize file"); } - { + if (data) { auto mapped = file.map(0, size); if (!mapped) { throw std::runtime_error("Unable to map file"); @@ -79,12 +79,14 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) { } if (opened) { - _mapped = _file.map(0, _file.size()); - if (_mapped) { - _valid = true; - } else { - qCWarning(storagelogging) << "Failed to map file " << filename; - } + _size = _file.size(); + _mapped = _file.map(0, _size); + if (!_mapped) { + qCDebug(storagelogging) << "Failed to map file, falling back to memory storage " << filename; + _fallback = _file.readAll(); + _mapped = (uint8_t*)_fallback.data(); + } + _valid = true; } else { qCWarning(storagelogging) << "Failed to open file " << filename; } @@ -92,7 +94,9 @@ FileStorage::FileStorage(const QString& filename) : _file(filename) { FileStorage::~FileStorage() { if (_mapped) { - _file.unmap(_mapped); + if (_fallback.isEmpty()) { + _file.unmap(_mapped); + } _mapped = nullptr; } if (_file.isOpen()) { diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h index d7946738cf..0e5032bb62 100644 --- a/libraries/shared/src/shared/Storage.h +++ b/libraries/shared/src/shared/Storage.h @@ -61,10 +61,12 @@ namespace storage { const uint8_t* data() const override { return _mapped; } uint8_t* mutableData() override { return _hasWriteAccess ? _mapped : nullptr; } - size_t size() const override { return _file.size(); } + size_t size() const override { return _size; } operator bool() const override { return _valid; } private: - + // For compressed QRC files we can't map the file object, so we need to read it into memory + QByteArray _fallback; + size_t _size { 0 }; bool _valid { false }; bool _hasWriteAccess { false }; QFile _file; diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp new file mode 100644 index 0000000000..31aa7a22e2 --- /dev/null +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -0,0 +1,46 @@ +#include "DesktopPreviewProvider.h" +#include +#include +#include +#include + +DesktopPreviewProvider::DesktopPreviewProvider() { +} + +constexpr const char* DesktopPreviewProvider::imagePaths[]; + +QSharedPointer DesktopPreviewProvider::getInstance() { + static QSharedPointer instance = DependencyManager::get(); + return instance; +} + +QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const { + + auto imageIndex = vsyncEnabled ? VSYNC : m_previewDisabledReason; + assert(imageIndex >= 0 && imageIndex <= VSYNC); + + return !m_previewDisabled[imageIndex].isNull() ? m_previewDisabled[imageIndex] : loadPreviewImage(m_previewDisabled[imageIndex], PathUtils::resourcesPath() + imagePaths[imageIndex]); +} + +void DesktopPreviewProvider::setPreviewDisabledReason(PreviewDisabledReasons reason) { + if (reason == VSYNC) { + qDebug() << "Preview disabled reason can't be forced to " << QMetaEnum::fromType().valueToKey(reason); + return; // Not settable via this interface, as VSYNC is controlled by HMD plugin.. + } + + m_previewDisabledReason = reason; +} + +void DesktopPreviewProvider::setPreviewDisabledReason(const QString& reasonString) { + PreviewDisabledReasons reason = USER; + bool ok = false; + + reason = (PreviewDisabledReasons) QMetaEnum::fromType().keyToValue(reasonString.toLatin1().data(), &ok); + if (ok) { + setPreviewDisabledReason(reason); + } +} + +QImage& DesktopPreviewProvider::loadPreviewImage(QImage& image, const QString& path) const { + return image = QImage(path).mirrored().convertToFormat(QImage::Format_RGBA8888); +} diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h new file mode 100644 index 0000000000..449c3723e1 --- /dev/null +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -0,0 +1,47 @@ +// +// Created by Alexander Ivash on 2018/01/08 +// Copyright 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 +#include + +class DesktopPreviewProvider : public QObject, public Dependency { + SINGLETON_DEPENDENCY + Q_OBJECT + + DesktopPreviewProvider(); + DesktopPreviewProvider(const DesktopPreviewProvider& other) = delete; + + constexpr static const char* imagePaths[] = { + "images/preview-disabled.png", // USER + "images/preview-privacy.png", // SECURE_SCREEN + "images/preview.png", // VSYNC + }; + +public: + enum PreviewDisabledReasons { + USER = 0, + SECURE_SCREEN, + VSYNC // Not settable via this interface, as VSYNC is controlled by HMD plugin.. + }; + Q_ENUM(PreviewDisabledReasons) + + static QSharedPointer getInstance(); + + QImage getPreviewDisabledImage(bool vsyncEnabled) const; + void setPreviewDisabledReason(PreviewDisabledReasons reason); + +public slots: + void setPreviewDisabledReason(const QString& reason); + +private: + QImage& loadPreviewImage(QImage& image, const QString& path) const; + + PreviewDisabledReasons m_previewDisabledReason = { USER }; + + mutable QImage m_previewDisabled[3]; +}; diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index cb80e3f6db..650d43831c 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -46,7 +46,7 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue registerType(); QUrl url; if (QDir(path).isRelative()) { - url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path); + url = PathUtils::resourcesUrl(path); } else { url = QUrl::fromLocalFile(path); } diff --git a/libraries/ui/src/OffscreenQmlElement.h b/libraries/ui/src/OffscreenQmlElement.h index 4e07fcccd9..aa4b8cf1a9 100644 --- a/libraries/ui/src/OffscreenQmlElement.h +++ b/libraries/ui/src/OffscreenQmlElement.h @@ -13,6 +13,9 @@ #define hifi_OffscreenQmlElement_h #include + +#include + class QQmlContext; #define HIFI_QML_DECL \ @@ -40,7 +43,7 @@ public: \ private: #define HIFI_QML_DEF(x) \ - const QUrl x::QML = QUrl(#x ".qml"); \ + const QUrl x::QML = PathUtils::qmlUrl(#x ".qml"); \ const QString x::NAME = #x; \ \ void x::registerType() { \ diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 905a224ef4..221f5013bf 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -666,7 +666,6 @@ void OffscreenUi::createDesktop(const QUrl& url) { Q_UNUSED(context) _desktop = static_cast(newObject); getSurfaceContext()->setContextProperty("desktop", _desktop); - _toolWindow = _desktop->findChild("ToolWindow"); _vrMenu = new VrMenu(this); for (const auto& menuInitializer : _queuedMenuInitializers) { @@ -686,10 +685,6 @@ QObject* OffscreenUi::getRootMenu() { return getRootItem()->findChild("rootMenu"); } -QQuickItem* OffscreenUi::getToolWindow() { - return _toolWindow; -} - void OffscreenUi::unfocusWindows() { bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows"); Q_ASSERT(invokeResult); diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 76e4261cd3..ab3a209820 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -80,7 +80,6 @@ public: QObject* getFlags(); Q_INVOKABLE bool isPointOnDesktopWindow(QVariant point); QQuickItem* getDesktop(); - QQuickItem* getToolWindow(); QObject* getRootMenu(); enum Icon { ICON_NONE = 0, @@ -261,7 +260,6 @@ private: ModalDialogListener* assetDialogAsync(const QVariantMap& properties); QQuickItem* _desktop { nullptr }; - QQuickItem* _toolWindow { nullptr }; QList _modalDialogListeners; std::unordered_map _pressedKeys; VrMenu* _vrMenu { nullptr }; diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 90b91c5ec2..1209d39dcf 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -32,7 +32,6 @@ static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge"; static const char* const WIDTH_PROPERTY = "width"; static const char* const HEIGHT_PROPERTY = "height"; static const char* const VISIBILE_PROPERTY = "visible"; -static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 }; static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 }; @@ -53,9 +52,6 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { if (context->argument(3).isNumber()) { properties[HEIGHT_PROPERTY] = context->argument(3).toInt32(); } - if (context->argument(4).isBool()) { - properties[TOOLWINDOW_PROPERTY] = context->argument(4).toBool(); - } } else { properties = context->argument(0).toVariant().toMap(); } @@ -96,52 +92,37 @@ void QmlWindowClass::initQml(QVariantMap properties) { auto offscreenUi = DependencyManager::get(); _source = properties[SOURCE_PROPERTY].toString(); -#if QML_TOOL_WINDOW - _toolWindow = properties.contains(TOOLWINDOW_PROPERTY) && properties[TOOLWINDOW_PROPERTY].toBool(); - if (_toolWindow) { - // Build the event bridge and wrapper on the main thread - _qmlWindow = offscreenUi->getToolWindow(); - properties[EVENT_BRIDGE_PROPERTY] = QVariant::fromValue(this); - QVariant newTabVar; - bool invokeResult = QMetaObject::invokeMethod(_qmlWindow, "addWebTab", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, newTabVar), - Q_ARG(QVariant, QVariant::fromValue(properties))); - Q_ASSERT(invokeResult); - } else { -#endif - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) { - _qmlWindow = object; - context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); - context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); - context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); - if (properties.contains(TITLE_PROPERTY)) { - object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); - } - if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) { - uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() }; - requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE); - asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y)); - } + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) { + _qmlWindow = object; + context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); + context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); + if (properties.contains(TITLE_PROPERTY)) { + object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); + } + if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) { + uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() }; + requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE); + asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y)); + } - bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool(); - object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); - object->setProperty(SOURCE_PROPERTY, _source); + bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool(); + object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); + object->setProperty(SOURCE_PROPERTY, _source); - const QMetaObject *metaObject = _qmlWindow->metaObject(); - // Forward messages received from QML on to the script - connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); - connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); + const QMetaObject *metaObject = _qmlWindow->metaObject(); + // Forward messages received from QML on to the script + connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); + connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); + + if (metaObject->indexOfSignal("resized") >= 0) + connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection); + if (metaObject->indexOfSignal("moved") >= 0) + connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection); + connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection); + }); - if (metaObject->indexOfSignal("resized") >= 0) - connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection); - if (metaObject->indexOfSignal("moved") >= 0) - connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection); - connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection); - }); -#if QML_TOOL_WINDOW - } -#endif Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); } @@ -215,11 +196,6 @@ QmlWindowClass::~QmlWindowClass() { } QQuickItem* QmlWindowClass::asQuickItem() const { -#if QML_TOOL_WINDOW - if (_toolWindow) { - return DependencyManager::get()->getToolWindow(); - } -#endif return _qmlWindow.isNull() ? nullptr : dynamic_cast(_qmlWindow.data()); } @@ -230,14 +206,6 @@ void QmlWindowClass::setVisible(bool visible) { } QQuickItem* targetWindow = asQuickItem(); -#if QML_TOOL_WINDOW - if (_toolWindow) { - // For tool window tabs we special case visibility as a function call on the tab parent - // The tool window itself has special logic based on whether any tabs are visible - QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible)); - return; - } -#endif targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); } @@ -253,12 +221,6 @@ bool QmlWindowClass::isVisible() { return false; } -#if QML_TOOL_WINDOW - if (_toolWindow) { - return dynamic_cast(_qmlWindow.data())->isEnabled(); - } -#endif - return asQuickItem()->isVisible(); } @@ -343,17 +305,6 @@ void QmlWindowClass::close() { return; } -#if QML_TOOL_WINDOW - if (_toolWindow) { - auto offscreenUi = DependencyManager::get(); - auto toolWindow = offscreenUi->getToolWindow(); - auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::DirectConnection, - Q_ARG(QVariant, _source)); - Q_ASSERT(invokeResult); - return; - } -#endif - if (_qmlWindow) { _qmlWindow->deleteLater(); } diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index e01bc8f14b..f274501d35 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -19,8 +19,6 @@ class QScriptEngine; class QScriptContext; -#define QML_TOOL_WINDOW 0 - // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWindowClass : public QObject { Q_OBJECT @@ -87,12 +85,6 @@ protected: virtual QString qmlSource() const { return "QmlWindow.qml"; } -#if QML_TOOL_WINDOW - // FIXME needs to be initialized in the ctor once we have support - // for tool window panes in QML - bool _toolWindow { false }; -#endif - QPointer _qmlWindow; QString _source; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 902f91f9b8..db3df34dc5 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -6,14 +6,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OffscreenQmlSurface.h" -#include "ImageProvider.h" -// Has to come before Qt GL includes -#include +#include #include #include +#include + #include #include #include @@ -29,7 +29,6 @@ #include #include -#include #include #include #include @@ -46,8 +45,10 @@ #include #include #include +#include #include +#include "ImageProvider.h" #include "types/FileTypeProfile.h" #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" @@ -70,7 +71,7 @@ public: withWriteLock([&] { for (auto url : urls) { if (url.isRelative()) { - url = QUrl(PathUtils::qmlBasePath() + url.toString()); + url = PathUtils::qmlUrl(url.toString()); } _callbacks[url].push_back(callback); } @@ -282,7 +283,7 @@ private: 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(Q_OS_ANDROID) +#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); @@ -443,7 +444,7 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { auto rootContext = engine->rootContext(); rootContext->setContextProperty("GL", ::getGLContextData()); rootContext->setContextProperty("urlHandler", new UrlHandler()); - rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); + rootContext->setContextProperty("resourceDirectoryUrl", QUrl(PathUtils::resourcesUrl())); rootContext->setContextProperty("pathToFonts", "../../"); rootContext->setContextProperty("ApplicationInterface", qApp); auto javaScriptToInject = getEventBridgeJavascript(); @@ -519,6 +520,7 @@ QOpenGLContext* OffscreenQmlSurface::getSharedContext() { void OffscreenQmlSurface::cleanup() { _isCleaned = true; +#if !defined(DISABLE_QML) _canvas->makeCurrent(); _renderControl->invalidate(); @@ -536,9 +538,11 @@ void OffscreenQmlSurface::cleanup() { offscreenTextures.releaseSize(_size); _canvas->doneCurrent(); +#endif } void OffscreenQmlSurface::render() { +#if !defined(DISABLE_QML) if (nsightActive()) { return; } @@ -578,6 +582,7 @@ void OffscreenQmlSurface::render() { _quickWindow->resetOpenGLState(); _lastRenderTime = usecTimestampNow(); _canvas->doneCurrent(); +#endif } bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) { @@ -622,7 +627,9 @@ OffscreenQmlSurface::~OffscreenQmlSurface() { cleanup(); auto engine = _qmlContext->engine(); +#if !defined(DISABLE_QML) _canvas->deleteLater(); +#endif _rootItem->deleteLater(); _quickWindow->deleteLater(); releaseEngine(engine); @@ -647,6 +654,7 @@ void OffscreenQmlSurface::disconnectAudioOutputTimer() { void OffscreenQmlSurface::create() { qCDebug(uiLogging) << "Building QML surface"; +#if !defined(DISABLE_QML) _renderControl = new QMyQuickRenderControl(); connect(_renderControl, &QQuickRenderControl::renderRequested, this, [this] { _render = true; }); connect(_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { _render = _polish = true; }); @@ -663,26 +671,30 @@ void OffscreenQmlSurface::create() { _quickWindow->setClearBeforeRendering(false); _renderControl->_renderWindow = _proxyWindow; - _canvas = new OffscreenGLCanvas(); if (!_canvas->create(getSharedContext())) { qFatal("Failed to create OffscreenGLCanvas"); return; }; - connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); - // acquireEngine interrogates the GL context, so we need to have the context current here if (!_canvas->makeCurrent()) { qFatal("Failed to make context current for QML Renderer"); return; } +#else + _quickWindow = new QQuickWindow(); +#endif + + + connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // Create a QML engine. auto qmlEngine = acquireEngine(_quickWindow); _qmlContext = new QQmlContext(qmlEngine->rootContext()); - _qmlContext->setBaseUrl(QUrl{ PathUtils::qmlBasePath() }); + _qmlContext->setBaseUrl(QUrl{ PathUtils::qmlBaseUrl() }); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("eventBridge", this); _qmlContext->setContextProperty("webEntity", this); @@ -691,7 +703,10 @@ void OffscreenQmlSurface::create() { // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated _qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext)); + +#if !defined(DISABLE_QML) _renderControl->initialize(_canvas->getContext()); +#endif #if !defined(Q_OS_ANDROID) // Connect with the audio client and listen for audio device changes @@ -791,6 +806,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { return; } +#if !defined(DISABLE_QML) qCDebug(uiLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height(); gl::withSavedContext([&] { _canvas->makeCurrent(); @@ -827,6 +843,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) { _canvas->doneCurrent(); }); +#endif } QQuickItem* OffscreenQmlSurface::getRootItem() { @@ -1009,7 +1026,9 @@ void OffscreenQmlSurface::updateQuick() { if (_polish) { PROFILE_RANGE(render_qml, "OffscreenQML polish") +#if !defined(DISABLE_QML) _renderControl->polishItems(); +#endif _polish = false; } @@ -1097,6 +1116,27 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even } break; } +#if defined(Q_OS_ANDROID) + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: { + QTouchEvent *originalEvent = static_cast(event); + QTouchEvent *fakeEvent = new QTouchEvent(*originalEvent); + auto newTouchPoints = fakeEvent->touchPoints(); + for (size_t i = 0; i < newTouchPoints.size(); ++i) { + const auto &originalPoint = originalEvent->touchPoints()[i]; + auto &newPoint = newTouchPoints[i]; + newPoint.setPos(originalPoint.pos()); + } + fakeEvent->setTouchPoints(newTouchPoints); + if (QCoreApplication::sendEvent(_quickWindow, fakeEvent)) { + qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeEvent->type() + << "_quickWindow handled it... accepted:" << fakeEvent->isAccepted(); + return false; //event->isAccepted(); + } + break; + } +#endif default: break; } @@ -1279,9 +1319,11 @@ bool OffscreenQmlSurface::isPaused() const { void OffscreenQmlSurface::setProxyWindow(QWindow* window) { _proxyWindow = window; +#if !defined(DISABLE_QML) if (_renderControl) { _renderControl->_renderWindow = window; } +#endif } QObject* OffscreenQmlSurface::getEventHandler() { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 4251bb6a9d..7aeac8d7c3 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -168,10 +168,13 @@ public slots: private: QQuickWindow* _quickWindow { nullptr }; - QMyQuickRenderControl* _renderControl{ nullptr }; QQmlContext* _qmlContext { nullptr }; QQuickItem* _rootItem { nullptr }; + +#if !defined(DISABLE_QML) + QMyQuickRenderControl* _renderControl{ nullptr }; OffscreenGLCanvas* _canvas { nullptr }; +#endif QTimer _updateTimer; uint32_t _fbo { 0 }; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index c69ec1ce84..0191baca5e 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -31,6 +31,8 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml"; +const QString BUTTON_SORT_ORDER_KEY = "sortOrder"; +const int DEFAULT_BUTTON_SORT_ORDER = 100; static QString getUsername() { QString username = "Unknown user"; @@ -74,11 +76,21 @@ QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { } TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + QVariantMap newProperties = properties.toMap(); + if (newProperties.find(BUTTON_SORT_ORDER_KEY) == newProperties.end()) { + newProperties[BUTTON_SORT_ORDER_KEY] = DEFAULT_BUTTON_SORT_ORDER; + } + int index = computeNewButtonIndex(newProperties); + auto button = QSharedPointer(new TabletButtonProxy(newProperties)); beginResetModel(); - _buttons.push_back(tabletButtonProxy); + int numButtons = (int)_buttons.size(); + if (index < numButtons) { + _buttons.insert(_buttons.begin() + index, button); + } else { + _buttons.push_back(button); + } endResetModel(); - return tabletButtonProxy.data(); + return button.data(); } void TabletButtonListModel::removeButton(TabletButtonProxy* button) { @@ -92,6 +104,20 @@ void TabletButtonListModel::removeButton(TabletButtonProxy* button) { endResetModel(); } +int TabletButtonListModel::computeNewButtonIndex(const QVariantMap& newButtonProperties) { + int numButtons = (int)_buttons.size(); + int newButtonSortOrder = newButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); + if (newButtonSortOrder == DEFAULT_BUTTON_SORT_ORDER) return numButtons; + for (int i = 0; i < numButtons; i++) { + QVariantMap tabletButtonProperties = _buttons[i]->getProperties(); + int tabletButtonSortOrder = tabletButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); + if (newButtonSortOrder <= tabletButtonSortOrder) { + return i; + } + } + return numButtons; +} + TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } @@ -172,9 +198,8 @@ void TabletScriptingInterface::preloadSounds() { //preload audio events const QStringList &audioSettings = tabletSoundsButtonClick.get(); for (int i = 0; i < TabletAudioEvents::Last; i++) { - QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + audioSettings.at(i)); SharedSoundPointer sound = DependencyManager::get()-> - getSound(QUrl::fromLocalFile(inf.absoluteFilePath())); + getSound(PathUtils::resourcesUrl(audioSettings.at(i))); _audioEvents.insert(static_cast(i), sound); } } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 56e3ae257b..34827117f0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -115,6 +115,7 @@ protected: friend class TabletProxy; TabletButtonProxy* addButton(const QVariant& properties); void removeButton(TabletButtonProxy* button); + int computeNewButtonIndex(const QVariantMap& newButtonProperties); using List = std::list>; static QHash _roles; static Qt::ItemFlags _flags; diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h b/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h index e38549937e..8be2974782 100644 --- a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h +++ b/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h @@ -11,14 +11,20 @@ #ifndef hifi_HFTabletWebEngineRequestInterceptor_h #define hifi_HFTabletWebEngineRequestInterceptor_h +#if !defined(Q_OS_ANDROID) +#include #include -class HFTabletWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor { +class HFTabletWebEngineRequestInterceptor + : public QWebEngineUrlRequestInterceptor +{ public: - HFTabletWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {}; - + HFTabletWebEngineRequestInterceptor(QObject* parent) + : QWebEngineUrlRequestInterceptor(parent) + {}; virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; }; +#endif #endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index 55eb9c7b85..59986d103e 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -6,7 +6,7 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -if (WIN32) +if (WIN32 AND (NOT USE_GLES)) # we're using static GLEW, so define GLEW_STATIC add_definitions(-DGLEW_STATIC) @@ -19,9 +19,10 @@ if (WIN32) set(TARGET_NAME oculus) setup_hifi_plugin(Multimedia) link_hifi_libraries( - shared gl gpu gpu-gl controllers ui + shared gl gpu controllers ui plugins ui-plugins display-plugins input-plugins audio-client networking render-utils + ${PLATFORM_GL_BACKEND} ) include_hifi_library_headers(octree) diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 7e7fcc0db3..d03419d025 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "OculusHelpers.h" @@ -92,9 +93,7 @@ glm::mat4 OculusBaseDisplayPlugin::getCullingProjection(const glm::mat4& basePro // DLL based display plugins MUST initialize GLEW inside the DLL code. void OculusBaseDisplayPlugin::customizeContext() { - glewExperimental = true; - GLenum err = glewInit(); - glGetError(); // clear the potential error from glewExperimental + gl::initModuleGl(); Parent::customizeContext(); } diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index 12d0236cc2..00e90fb6d7 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -13,7 +13,7 @@ if (APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() - link_hifi_libraries(shared gl gpu gpu-gl plugins ui ui-plugins display-plugins input-plugins midi) + link_hifi_libraries(shared gl gpu plugins ui ui-plugins display-plugins input-plugins midi ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(octree) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 804ff7d62e..e6b555443f 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -9,6 +9,8 @@ #include +#include + #include #include #include @@ -192,14 +194,9 @@ void OculusLegacyDisplayPlugin::internalDeactivate() { _container->makeRenderingContextCurrent(); } -// DLL based display plugins MUST initialize GLEW inside the DLL code. +// DLL based display plugins MUST initialize GL inside the DLL code. void OculusLegacyDisplayPlugin::customizeContext() { - static std::once_flag once; - std::call_once(once, []{ - glewExperimental = true; - glewInit(); - glGetError(); - }); + gl::initModuleGl(); _hmdWindow->requestActivate(); QThread::msleep(1000); Parent::customizeContext(); diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index 9ae9b25b65..3581009284 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -6,14 +6,14 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -if (WIN32) +if (WIN32 AND (NOT USE_GLES)) # we're using static GLEW, so define GLEW_STATIC add_definitions(-DGLEW_STATIC) set(TARGET_NAME openvr) setup_hifi_plugin(OpenGL Script Qml Widgets Multimedia) link_hifi_libraries(shared gl networking controllers ui plugins display-plugins ui-plugins input-plugins script-engine - audio-client render-utils graphics gpu gpu-gl render model-networking fbx ktx image procedural) + audio-client render-utils graphics gpu render model-networking fbx ktx image procedural ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(octree) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 96ad19f46b..4403eaeff7 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -175,12 +175,12 @@ public: while (!_queue.empty()) { auto& front = _queue.front(); - auto result = glClientWaitSync(front.fence, 0, 0); + auto result = glClientWaitSync((GLsync)front.fence, 0, 0); if (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) { break; } else if (GL_CONDITION_SATISFIED == result || GL_ALREADY_SIGNALED == result) { - glDeleteSync(front.fence); + glDeleteSync((GLsync)front.fence); } else { assert(false); } @@ -510,14 +510,8 @@ void OpenVrDisplayPlugin::internalDeactivate() { } void OpenVrDisplayPlugin::customizeContext() { - // Display plugins in DLLs must initialize glew locally - static std::once_flag once; - std::call_once(once, [] { - glewExperimental = true; - GLenum err = glewInit(); - glGetError(); // clear the potential error from glewExperimental - }); - + // Display plugins in DLLs must initialize GL locally + gl::initModuleGl(); Parent::customizeContext(); if (_threadedSubmit) { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 6aea6ef575..f681852cd5 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -27,9 +27,9 @@ struct CompositeInfo { using Array = std::array; gpu::TexturePointer texture; - GLuint textureID { 0 }; + uint32_t textureID { 0 }; glm::mat4 pose; - GLsync fence{ 0 }; + void* fence{ 0 }; }; class OpenVrDisplayPlugin : public HmdDisplayPlugin { diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js new file mode 100644 index 0000000000..a115e498da --- /dev/null +++ b/scripts/+android/defaultScripts.js @@ -0,0 +1,110 @@ +"use strict"; +/* jslint vars: true, plusplus: true */ + +// +// defaultScripts.js +// examples +// +// Copyright 2014 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 +// + +var DEFAULT_SCRIPTS_COMBINED = [ + "system/progress.js"/*, + "system/away.js", + "system/controllers/controllerDisplayManager.js", + "system/controllers/handControllerGrabAndroid.js", + "system/controllers/handControllerPointerAndroid.js", + "system/controllers/squeezeHands.js", + "system/controllers/grab.js", + "system/controllers/teleport.js", + "system/controllers/toggleAdvancedMovementForHandControllers.js", + "system/dialTone.js", + "system/firstPersonHMD.js", + "system/bubble.js", + "system/android.js", + "developer/debugging/debugAndroidMouse.js"*/ +]; + +var DEFAULT_SCRIPTS_SEPARATE = [ ]; + +// add a menu item for debugging +var MENU_CATEGORY = "Developer"; +var MENU_ITEM = "Debug defaultScripts.js"; + +var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; +var previousSetting = Settings.getValue(SETTINGS_KEY); + +if (previousSetting === '' || previousSetting === false || previousSetting === 'false') { + previousSetting = false; +} + +if (previousSetting === true || previousSetting === 'true') { + previousSetting = true; +} + +if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { + Menu.addMenuItem({ + menuName: MENU_CATEGORY, + menuItemName: MENU_ITEM, + isCheckable: true, + isChecked: previousSetting, + grouping: "Advanced" + }); +} + +function loadSeparateDefaults() { + for (var i in DEFAULT_SCRIPTS_SEPARATE) { + Script.load(DEFAULT_SCRIPTS_SEPARATE[i]); + } +} + +function runDefaultsTogether() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.include(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); +} + +function runDefaultsSeparately() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.load(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); +} + +// start all scripts +if (Menu.isOptionChecked(MENU_ITEM)) { + // we're debugging individual default scripts + // so we load each into its own ScriptEngine instance + runDefaultsSeparately(); +} else { + // include all default scripts into this ScriptEngine + runDefaultsTogether(); +} + +function menuItemEvent(menuItem) { + if (menuItem === MENU_ITEM) { + var isChecked = Menu.isOptionChecked(MENU_ITEM); + if (isChecked === true) { + Settings.setValue(SETTINGS_KEY, true); + } else if (isChecked === false) { + Settings.setValue(SETTINGS_KEY, false); + } + Menu.triggerOption("Reload All Scripts"); + } +} + +function removeMenuItem() { + if (!Menu.isOptionChecked(MENU_ITEM)) { + Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM); + } +} + +Script.scriptEnding.connect(function() { + removeMenuItem(); +}); + +Menu.menuItemEvent.connect(menuItemEvent); diff --git a/scripts/developer/tests/avatarAttachmentTest.js b/scripts/developer/tests/avatarAttachmentTest.js index c9f2e1615b..b9dfcf9596 100644 --- a/scripts/developer/tests/avatarAttachmentTest.js +++ b/scripts/developer/tests/avatarAttachmentTest.js @@ -57,7 +57,7 @@ ToggleButtonBuddy.prototype.addToggleHandler = function (callback) { }; ToggleButtonBuddy.prototype.removeToggleHandler = function (callback) { var index = this.callbacks.indexOf(callback); - if (index != -1) { + if (index !== -1) { this.callbacks.splice(index, 1); } }; @@ -86,13 +86,23 @@ var coatButton = new ToggleButtonBuddy(buttonPositionX, buttonPositionY, BUTTON_ down: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-down.svg" }); +buttonPositionY += BUTTON_HEIGHT + BUTTON_PADDING; +var coatButton2 = new ToggleButtonBuddy(buttonPositionX, buttonPositionY, BUTTON_WIDTH, BUTTON_HEIGHT, { + up: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-up.svg", + down: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-down.svg" +}); + +var AVATAR_ATTACHMENT = 0; +var AVATAR_SOFT_ATTACHMENT = 1; +var ENTITY_ATTACHMENT = 2; + var HAT_ATTACHMENT = { modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx", jointName: "Head", translation: {"x": 0, "y": 0.25, "z": 0.03}, rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, scale: 0.052, - isSoft: false + type: AVATAR_ATTACHMENT }; var COAT_ATTACHMENT = { @@ -101,7 +111,16 @@ var COAT_ATTACHMENT = { translation: {"x": 0, "y": 0, "z": 0}, rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, scale: 1, - isSoft: true + type: AVATAR_SOFT_ATTACHMENT +}; + +var COAT_ENTITY_ATTACHMENT = { + modelURL: "https://hifi-content.s3.amazonaws.com/ozan/dev/clothes/coat/coat_rig.fbx", + jointName: "Hips", + translation: {"x": 0, "y": 0, "z": 0}, + rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, + scale: 1, + type: ENTITY_ATTACHMENT }; hatButton.addToggleHandler(function (isDown) { @@ -120,28 +139,54 @@ coatButton.addToggleHandler(function (isDown) { } }); +coatButton2.addToggleHandler(function (isDown) { + if (isDown) { + wearAttachment(COAT_ENTITY_ATTACHMENT); + } else { + removeAttachment(COAT_ENTITY_ATTACHMENT); + } +}); + function wearAttachment(attachment) { - MyAvatar.attach(attachment.modelURL, - attachment.jointName, - attachment.translation, - attachment.rotation, - attachment.scale, - attachment.isSoft); + if (attachment.type === AVATAR_ATTACHMENT || attachment.type === AVATAR_SOFT_ATTACHMENT) { + MyAvatar.attach(attachment.modelURL, + attachment.jointName, + attachment.translation, + attachment.rotation, + attachment.scale, + (attachment.type === AVATAR_SOFT_ATTACHMENT)); + } else { + attachment.entityID = Entities.addEntity({ + name: "attachment", + type: "Model", + modelURL: attachment.modelURL, + parentID: MyAvatar.sessionUUID, + relayParentJoints: true, + position: attachment.position, + rotation: attachment.rotation, + parentJointIndex: -1 + }, true); + } } function removeAttachment(attachment) { - var attachments = MyAvatar.attachmentData; - var i, l = attachments.length; - for (i = 0; i < l; i++) { - if (attachments[i].modelURL === attachment.modelURL) { - attachments.splice(i, 1); - MyAvatar.attachmentData = attachments; - break; + if (attachment.type === AVATAR_ATTACHMENT || attachment.type === AVATAR_SOFT_ATTACHMENT) { + var attachments = MyAvatar.attachmentData; + var i, l = attachments.length; + for (i = 0; i < l; i++) { + if (attachments[i].modelURL === attachment.modelURL) { + attachments.splice(i, 1); + MyAvatar.attachmentData = attachments; + break; + } } + } else { + Entities.deleteEntity(attachment.entityID); } } Script.scriptEnding.connect(function() { hatButton.destroy(); coatButton.destroy(); + coatButton2.destroy(); }); diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index c0b001eab3..e9262c9308 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -22,8 +22,7 @@ var button = tablet.addButton({ icon: Script.resolvePath("dynamicsTests.svg"), - text: "Dynamics", - sortOrder: 15 + text: "Dynamics" }); diff --git a/scripts/developer/tests/playaPerformanceTest.js b/scripts/developer/tests/playaPerformanceTest.js index 4c8a728a15..6250fe1175 100644 --- a/scripts/developer/tests/playaPerformanceTest.js +++ b/scripts/developer/tests/playaPerformanceTest.js @@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({ source: qml, height: 320, width: 640, - toolWindow: false, visible: true }); diff --git a/scripts/developer/tests/qmlTest.js b/scripts/developer/tests/qmlTest.js index 0eaabac6d1..7d248a9cc7 100644 --- a/scripts/developer/tests/qmlTest.js +++ b/scripts/developer/tests/qmlTest.js @@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({ source: "qrc:///qml/OverlayWindowTest.qml", height: 240, width: 320, - toolWindow: false, visible: true }); diff --git a/scripts/developer/tests/textureStress.js b/scripts/developer/tests/textureStress.js index 1e3cf9a367..98af4b19b7 100644 --- a/scripts/developer/tests/textureStress.js +++ b/scripts/developer/tests/textureStress.js @@ -15,7 +15,6 @@ qmlWindow = new OverlayWindow({ source: qml, height: 240, width: 320, - toolWindow: false, visible: true }); diff --git a/scripts/developer/tests/toolWindowStressTest.js b/scripts/developer/tests/toolWindowStressTest.js deleted file mode 100644 index 44b059ebda..0000000000 --- a/scripts/developer/tests/toolWindowStressTest.js +++ /dev/null @@ -1,31 +0,0 @@ -var TOGGLE_RATE = 1000; -var RUNTIME = 60 * 1000; - -var webView; -var running = true; - -function toggleWindow() { - if (!running) { - return; - } - - if (webView) { - webView.close(); - webView = null; - } else { - webView = new OverlayWebWindow({ - title: 'Entity Properties', - source: "http://www.google.com", - toolWindow: true - }); - webView.setVisible(true); - } - Script.setTimeout(toggleWindow, TOGGLE_RATE) -} - -toggleWindow(); -print("Creating window?") - -Script.setTimeout(function(){ - print("Shutting down") -}, RUNTIME) diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index e70565cec2..c2173f6e2a 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -32,8 +32,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js new file mode 100644 index 0000000000..307e509d39 --- /dev/null +++ b/scripts/developer/utilities/render/lod.js @@ -0,0 +1,76 @@ +"use strict"; + +// +// lodi.js +// tablet-engine app +// +// Copyright 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 +// + +(function() { + var TABLET_BUTTON_NAME = "LOD"; + var QMLAPP_URL = Script.resolvePath("./lod.qml"); + var ICON_URL = Script.resolvePath("../../../system/assets/images/lod-i.svg"); + var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/lod-a.svg"); + + var onScreen = false; + + function onClicked() { + if (onScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + onScreen = (url === QMLAPP_URL); + button.editProperties({isActive: onScreen}); + wireEventBridge(onScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); +}()); diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml new file mode 100644 index 0000000000..d7b9f1cd57 --- /dev/null +++ b/scripts/developer/utilities/render/lod.qml @@ -0,0 +1,92 @@ +// +// lod.qml +// scripts/developer/utilities/render +// +// Created by Andrew Meadows on 2018.01.10 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../lib/plotperf" + +Item { + id: lodIU + anchors.fill:parent + + Column { + id: stats + spacing: 8 + anchors.fill:parent + + function evalEvenHeight() { + // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? + return (height - spacing * (children.length - 1)) / children.length + } + + PlotPerf { + title: "Load Indicators" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1 + valueUnit: "ms" + plots: [ + { + prop: "presentTime", + label: "present", + color: "#FFFF00" + }, + { + prop: "engineRunTime", + label: "engineRun", + color: "#FF00FF" + }, + { + prop: "gpuTime", + label: "gpu", + color: "#00FFFF" + } + ] + } + PlotPerf { + title: "FPS" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 1 + valueUnit: "Hz" + plots: [ + { + prop: "lodIncreaseFPS", + label: "LOD++", + color: "#66FF66" + }, + { + prop: "fps", + label: "FPS", + color: "#FFFFFF" + }, + { + prop: "lodDecreaseFPS", + label: "LOD--", + color: "#FF6666" + } + ] + } + PlotPerf { + title: "LOD" + height: parent.evalEvenHeight() + object: LODManager + valueScale: 0.1 + valueUnit: "" + plots: [ + { + prop: "lodLevel", + label: "LOD", + color: "#9999FF" + } + ] + } + } +} diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js index 1e2ac1261f..6482c884ff 100644 --- a/scripts/developer/utilities/render/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -31,8 +31,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/developer/utilities/tools/currentAPI.js b/scripts/developer/utilities/tools/currentAPI.js index 175b84b8d9..6cab6a5710 100644 --- a/scripts/developer/utilities/tools/currentAPI.js +++ b/scripts/developer/utilities/tools/currentAPI.js @@ -28,8 +28,8 @@ var window = new OverlayWindow({ title: 'API Debugger', source: qml, - width: 1200, - height: 500 + width: 500, + height: 700 }); window.closed.connect(function () { diff --git a/scripts/system/assets/images/lod-a.svg b/scripts/system/assets/images/lod-a.svg new file mode 100644 index 0000000000..6845e0ff78 --- /dev/null +++ b/scripts/system/assets/images/lod-a.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/system/assets/images/lod-i.svg b/scripts/system/assets/images/lod-i.svg new file mode 100644 index 0000000000..f909f3b495 --- /dev/null +++ b/scripts/system/assets/images/lod-i.svg @@ -0,0 +1,46 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/system/assets/images/run.svg b/scripts/system/assets/images/run.svg new file mode 100644 index 0000000000..0957166346 --- /dev/null +++ b/scripts/system/assets/images/run.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 4ea684ff06..994bde49eb 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -16,10 +16,10 @@ var button; // Used for animating and disappearing the bubble var bubbleOverlayTimestamp; + // Used for rate limiting the bubble sound + var lastBubbleSoundTimestamp = 0; // Used for flashing the HUD button upon activation var bubbleButtonFlashState = false; - // Used for flashing the HUD button upon activation - var bubbleButtonTimestamp; // Affects bubble height var BUBBLE_HEIGHT_SCALE = 0.15; // The bubble model itself @@ -36,9 +36,11 @@ var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); // Is the update() function connected? var updateConnected = false; + var bubbleFlashTimer = false; var BUBBLE_VISIBLE_DURATION_MS = 3000; var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; + var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; // Hides the bubble model overlay and resets the button flash state function hideOverlays() { @@ -50,11 +52,15 @@ // Make the bubble overlay visible, set its position, and play the sound function createOverlays() { - Audio.playSound(bubbleActivateSound, { - position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, - localOnly: true, - volume: 0.2 - }); + var nowTimestamp = Date.now(); + if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) { + Audio.playSound(bubbleActivateSound, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 0.2 + }); + lastBubbleSoundTimestamp = nowTimestamp; + } hideOverlays(); if (updateConnected === true) { updateConnected = false; @@ -80,10 +86,17 @@ }, visible: true }); - bubbleOverlayTimestamp = Date.now(); - bubbleButtonTimestamp = bubbleOverlayTimestamp; + bubbleOverlayTimestamp = nowTimestamp; Script.update.connect(update); updateConnected = true; + + // Flash button + if (!bubbleFlashTimer) { + bubbleFlashTimer = Script.setInterval(function () { + writeButtonProperties(bubbleButtonFlashState); + bubbleButtonFlashState = !bubbleButtonFlashState; + }, 500); + } } // Called from the C++ scripting interface to show the bubble overlay @@ -103,12 +116,6 @@ var delay = (timestamp - bubbleOverlayTimestamp); var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); if (overlayAlpha > 0) { - // Flash button - if ((timestamp - bubbleButtonTimestamp) >= BUBBLE_VISIBLE_DURATION_MS) { - writeButtonProperties(bubbleButtonFlashState); - bubbleButtonTimestamp = timestamp; - bubbleButtonFlashState = !bubbleButtonFlashState; - } if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { Overlays.editOverlay(bubbleOverlay, { @@ -157,8 +164,11 @@ Script.update.disconnect(update); updateConnected = false; } - var bubbleActive = Users.getIgnoreRadiusEnabled(); - writeButtonProperties(bubbleActive); + if (bubbleFlashTimer) { + Script.clearTimeout(bubbleFlashTimer); + bubbleFlashTimer = false; + } + writeButtonProperties(Users.getIgnoreRadiusEnabled()); } } @@ -166,6 +176,10 @@ // NOTE: the c++ calls this with just the first param -- we added a second // just for not logging the initial state of the bubble when we startup. function onBubbleToggled(enabled, doNotLog) { + if (bubbleFlashTimer) { + Script.clearTimeout(bubbleFlashTimer); + bubbleFlashTimer = false; + } writeButtonProperties(enabled); if (doNotLog !== true) { UserActivityLogger.bubbleToggled(enabled); @@ -200,6 +214,10 @@ // Cleanup the tablet button and overlays when script is stopped Script.scriptEnding.connect(function () { button.clicked.disconnect(Users.toggleIgnoreRadius); + if (bubbleFlashTimer) { + Script.clearTimeout(bubbleFlashTimer); + bubbleFlashTimer = false; + } if (tablet) { tablet.removeButton(button); } diff --git a/scripts/system/chat.js b/scripts/system/chat.js index 0cb414e23c..b0a2e114a3 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -945,8 +945,7 @@ tabletButton = tablet.addButton({ icon: tabletButtonIcon, activeIcon: tabletButtonActiveIcon, - text: tabletButtonName, - sortOrder: 0 + text: tabletButtonName }); Messages.subscribe(channelName); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index b3e3134380..ad864622ed 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -15,6 +15,7 @@ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); + var request = Script.require('request').request; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; @@ -51,6 +52,481 @@ tablet.sendToQml(message); } + //*********************************************** + // + // BEGIN Connection logic + // + //*********************************************** + // Function Names: + // - requestJSON + // - getAvailableConnections + // - getInfoAboutUser + // - getConnectionData + // + // Description: + // - Update all the usernames that I am entitled to see, using my login but not dependent on canKick. + var METAVERSE_BASE = Account.metaverseServerURL; + function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise. + request({ + uri: url + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("Error: unable to get", url, error || response.status); + return; + } + callback(response.data); + }); + } + function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise) + url = METAVERSE_BASE + '/api/v1/users?' + if (domain) { + url += 'status=' + domain.slice(1, -1); // without curly braces + } else { + url += 'filter=connections'; // regardless of whether online + } + requestJSON(url, function (connectionsData) { + callback(connectionsData.users); + }); + } + function getInfoAboutUser(specificUsername, callback) { + url = METAVERSE_BASE + '/api/v1/users?filter=connections' + requestJSON(url, function (connectionsData) { + for (user in connectionsData.users) { + if (connectionsData.users[user].username === specificUsername) { + callback(connectionsData.users[user]); + return; + } + } + callback(false); + }); + } + function getConnectionData(specificUsername, domain) { + function frob(user) { // get into the right format + var formattedSessionId = user.location.node_id || ''; + if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) { + formattedSessionId = "{" + formattedSessionId + "}"; + } + return { + sessionId: formattedSessionId, + userName: user.username, + connection: user.connection, + profileUrl: user.images.thumbnail, + placeName: (user.location.root || user.location.domain || {}).name || '' + }; + } + if (specificUsername) { + getInfoAboutUser(specificUsername, function (user) { + if (user) { + updateUser(frob(user)); + } else { + print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); + } + }); + } else { + getAvailableConnections(domain, function (users) { + if (domain) { + users.forEach(function (user) { + updateUser(frob(user)); + }); + } else { + sendToQml({ method: 'updateConnections', connections: users.map(frob) }); + } + }); + } + } + //*********************************************** + // + // END Connection logic + // + //*********************************************** + + //*********************************************** + // + // BEGIN Avatar Selector logic + // + //*********************************************** + var UNSELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") + }; + var SELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") + }; + var HOVER_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") + }; + + var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; + var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; + var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; + var conserveResources = true; + + var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. + + function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. + overlays[key] = this; + if (hasModel) { + var modelKey = key + "-m"; + this.model = new ExtendedOverlay(modelKey, "model", { + url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), + textures: textures(selected), + ignoreRayIntersection: true + }, false, false); + } else { + this.model = undefined; + } + this.key = key; + this.selected = selected || false; // not undefined + this.hovering = false; + this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... + } + // Instance methods: + ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay + Overlays.deleteOverlay(this.activeOverlay); + delete overlays[this.key]; + }; + + ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay + Overlays.editOverlay(this.activeOverlay, properties); + }; + + function color(selected, hovering) { + var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; + function scale(component) { + var delta = 0xFF - component; + return component; + } + return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; + } + + function textures(selected, hovering) { + return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES; + } + // so we don't have to traverse the overlays to get the last one + var lastHoveringId = 0; + ExtendedOverlay.prototype.hover = function (hovering) { + this.hovering = hovering; + if (this.key === lastHoveringId) { + if (hovering) { + return; + } + lastHoveringId = 0; + } + this.editOverlay({ color: color(this.selected, hovering) }); + if (this.model) { + this.model.editOverlay({ textures: textures(this.selected, hovering) }); + } + if (hovering) { + // un-hover the last hovering overlay + if (lastHoveringId && lastHoveringId !== this.key) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + lastHoveringId = this.key; + } + }; + ExtendedOverlay.prototype.select = function (selected) { + if (this.selected === selected) { + return; + } + + this.editOverlay({ color: color(selected, this.hovering) }); + if (this.model) { + this.model.editOverlay({ textures: textures(selected) }); + } + this.selected = selected; + }; + // Class methods: + var selectedIds = []; + ExtendedOverlay.isSelected = function (id) { + return -1 !== selectedIds.indexOf(id); + }; + ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier + return overlays[key]; + }; + ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. + var key; + for (key in overlays) { + if (iterator(ExtendedOverlay.get(key))) { + return; + } + } + }; + ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) + if (lastHoveringId) { + ExtendedOverlay.get(lastHoveringId).hover(false); + } + }; + + // hit(overlay) on the one overlay intersected by pickRay, if any. + // noHit() if no ExtendedOverlay was intersected (helps with hover) + ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { + var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. + if (!pickedOverlay.intersects) { + if (noHit) { + return noHit(); + } + return; + } + ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. + if ((overlay.activeOverlay) === pickedOverlay.overlayID) { + hit(overlay); + return true; + } + }); + }; + + function HighlightedEntity(id, entityProperties) { + this.id = id; + this.overlay = Overlays.addOverlay('cube', { + position: entityProperties.position, + rotation: entityProperties.rotation, + dimensions: entityProperties.dimensions, + solid: false, + color: { + red: 0xF3, + green: 0x91, + blue: 0x29 + }, + ignoreRayIntersection: true, + drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene. + }); + HighlightedEntity.overlays.push(this); + } + HighlightedEntity.overlays = []; + HighlightedEntity.clearOverlays = function clearHighlightedEntities() { + HighlightedEntity.overlays.forEach(function (highlighted) { + Overlays.deleteOverlay(highlighted.overlay); + }); + HighlightedEntity.overlays = []; + }; + HighlightedEntity.updateOverlays = function updateHighlightedEntities() { + HighlightedEntity.overlays.forEach(function (highlighted) { + var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']); + Overlays.editOverlay(highlighted.overlay, { + position: properties.position, + rotation: properties.rotation, + dimensions: properties.dimensions + }); + }); + }; + + + function addAvatarNode(id) { + var selected = ExtendedOverlay.isSelected(id); + return new ExtendedOverlay(id, "sphere", { + drawInFront: true, + solid: true, + alpha: 0.8, + color: color(selected, false), + ignoreRayIntersection: false + }, selected, !conserveResources); + } + + var pingPong = true; + function updateOverlays() { + var eye = Camera.position; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + if (!id) { + return; // don't update ourself, or avatars we're not interested in + } + var avatar = AvatarList.getAvatar(id); + if (!avatar) { + return; // will be deleted below if there had been an overlay. + } + var overlay = ExtendedOverlay.get(id); + if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. + overlay = addAvatarNode(id); + } + var target = avatar.position; + var distance = Vec3.distance(target, eye); + var offset = 0.2; + var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) + var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can + if (headIndex > 0) { + offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; + } + + // move a bit in front, towards the camera + target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); + + // now bump it up a bit + target.y = target.y + offset; + + overlay.ping = pingPong; + overlay.editOverlay({ + color: color(ExtendedOverlay.isSelected(id), overlay.hovering), + position: target, + dimensions: 0.032 * distance + }); + if (overlay.model) { + overlay.model.ping = pingPong; + overlay.model.editOverlay({ + position: target, + scale: 0.2 * distance, // constant apparent size + rotation: Camera.orientation + }); + } + }); + pingPong = !pingPong; + ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) + if (overlay.ping === pingPong) { + overlay.deleteOverlay(); + } + }); + // We could re-populateNearbyUserList if anything added or removed, but not for now. + HighlightedEntity.updateOverlays(); + } + function removeOverlays() { + selectedIds = []; + lastHoveringId = 0; + HighlightedEntity.clearOverlays(); + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); + } + + // + // Clicks. + // + function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { + if (selectedIds[0] === id) { + var message = { + method: 'updateSelectedRecipientUsername', + userName: username === "" ? "unknown username" : username + }; + sendToQml(message); + } + } + function handleClick(pickRay) { + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + var nextSelectedStatus = !overlay.selected; + var avatarId = overlay.key; + selectedIds = nextSelectedStatus ? [avatarId] : []; + if (nextSelectedStatus) { + Users.requestUsernameFromID(avatarId); + } + var message = { + method: 'selectRecipient', + id: [avatarId], + isSelected: nextSelectedStatus, + displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', + userName: '' + }; + sendToQml(message); + + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); + + HighlightedEntity.clearOverlays(); + if (selectedIds.length) { + Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) { + // Because lastEditedBy is per session, the vast majority of entities won't match, + // so it would probably be worth reducing marshalling costs by asking for just we need. + // However, providing property name(s) is advisory and some additional properties are + // included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation', + // and 'dimensions', too, so we might as well make use of them instead of making a second + // getEntityProperties call. + // It would be nice if we could harden this against future changes by specifying all + // and only these four in an array, but see + // https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work + var properties = Entities.getEntityProperties(id, 'lastEditedBy'); + if (ExtendedOverlay.isSelected(properties.lastEditedBy)) { + new HighlightedEntity(id, properties); + } + }); + } + return true; + }); + } + function handleMouseEvent(mousePressEvent) { // handleClick if we get one. + if (!mousePressEvent.isLeftButton) { + return; + } + handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); + } + function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic + ExtendedOverlay.applyPickRay(pickRay, function (overlay) { + overlay.hover(true); + }, function () { + ExtendedOverlay.unHover(); + }); + } + + // handy global to keep track of which hand is the mouse (if any) + var currentHandPressed = 0; + var TRIGGER_CLICK_THRESHOLD = 0.85; + var TRIGGER_PRESS_THRESHOLD = 0.05; + + function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; + if (HMD.active) { + if (currentHandPressed !== 0) { + pickRay = controllerComputePickRay(currentHandPressed); + } else { + // nothing should hover, so + ExtendedOverlay.unHover(); + return; + } + } else { + pickRay = Camera.computePickRay(event.x, event.y); + } + handleMouseMove(pickRay); + } + function handleTriggerPressed(hand, value) { + // The idea is if you press one trigger, it is the one + // we will consider the mouse. Even if the other is pressed, + // we ignore it until this one is no longer pressed. + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { + currentHandPressed = isPressed ? hand : 0; + return; + } + if (currentHandPressed === hand) { + currentHandPressed = isPressed ? hand : 0; + return; + } + // otherwise, the other hand is still triggered + // so do nothing. + } + + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we don't get mousePressEvents. + var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + function controllerComputePickRay(hand) { + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; + } + } + function makeClickHandler(hand) { + return function (clicked) { + if (clicked > TRIGGER_CLICK_THRESHOLD) { + var pickRay = controllerComputePickRay(hand); + handleClick(pickRay); + } + }; + } + function makePressHandler(hand) { + return function (value) { + handleTriggerPressed(hand, value); + }; + } + triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + //*********************************************** + // + // END Avatar Selector logic + // + //*********************************************** + // Function Name: fromQml() // // Description: @@ -86,9 +562,11 @@ break; case 'disableHmdPreview': isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); + DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); break; case 'maybeEnableHmdPreview': + DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; case 'passphraseReset': @@ -110,6 +588,23 @@ case 'goToMarketplaceItemPage': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'refreshConnections': + print('Refreshing Connections...'); + getConnectionData(false); + break; + case 'enable_ChooseRecipientNearbyMode': + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } + break; + case 'disable_ChooseRecipientNearbyMode': + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + removeOverlays(); + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } @@ -148,12 +643,27 @@ // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. function onTabletScreenChanged(type, url) { - onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE); + var onWalletScreenNow = (type === "QML" && url === WALLET_QML_SOURCE); + if (!onWalletScreenNow && onWalletScreen) { + DesktopPreviewProvider.setPreviewDisabledReason("USER"); + } + onWalletScreen = onWalletScreenNow; wireEventBridge(onWalletScreen); // Change button to active when window is first openend, false otherwise. if (button) { button.editProperties({ isActive: onWalletScreen }); } + + if (onWalletScreen) { + isWired = true; + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } else { + off(); + } } // @@ -176,6 +686,23 @@ tablet.screenChanged.connect(onTabletScreenChanged); } } + var isWired = false; + var isUpdateOverlaysWired = false; + function off() { + if (isWired) { // It is not ok to disconnect these twice, hence guard. + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + isWired = false; + } + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } + triggerMapping.disable(); // It's ok if we disable twice. + triggerPressMapping.disable(); // see above + removeOverlays(); + } function shutdown() { button.clicked.disconnect(onButtonClicked); tablet.removeButton(button); @@ -185,6 +712,7 @@ tablet.gotoHomeScreen(); } } + off(); } // diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 88195d7024..32bf7316a9 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -370,20 +370,23 @@ Script.include("/~/system/libraries/Xform.js"); }; this.isReady = function (controllerData) { - if (this.notPointingAtEntity(controllerData)) { - return makeRunningValues(false, [], []); - } + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } - this.distanceHolding = false; - this.distanceRotating = false; + this.distanceHolding = false; + this.distanceRotating = false; - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.prepareDistanceRotatingData(controllerData); - return makeRunningValues(true, [], []); - } else { - this.destroyContextOverlay(); - return makeRunningValues(false, [], []); + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.prepareDistanceRotatingData(controllerData); + return makeRunningValues(true, [], []); + } else { + this.destroyContextOverlay(); + return makeRunningValues(false, [], []); + } } + return makeRunningValues(false, [], []); }; this.run = function (controllerData) { diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js index a7a186b07a..afc9256875 100644 --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js @@ -89,7 +89,7 @@ this.isReady = function (controllerData) { var otherModuleRunning = this.getOtherModule().running; - if (!otherModuleRunning) { + if (!otherModuleRunning && HMD.active) { if (this.processLaser(controllerData)) { this.running = true; return ControllerDispatcherUtils.makeRunningValues(true, [], []); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 87cd3e0faf..92ccdf6565 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -448,7 +448,7 @@ var toolBar = (function () { }); addButton("importEntitiesButton", "assets-01.svg", function() { - Window.openFileChanged.connect(onFileOpenChanged); + Window.browseChanged.connect(onFileOpenChanged); Window.browseAsync("Select Model to Import", "", "*.json"); }); @@ -1497,7 +1497,7 @@ function onFileOpenChanged(filename) { // disconnect the event, otherwise the requests will stack up try { // Not all calls to onFileOpenChanged() connect an event. - Window.openFileChanged.disconnect(onFileOpenChanged); + Window.browseChanged.disconnect(onFileOpenChanged); } catch (e) { // Ignore. } @@ -1549,7 +1549,7 @@ function handeMenuEvent(menuItem) { } } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") { if (menuItem === "Import Entities") { - Window.openFileChanged.connect(onFileOpenChanged); + Window.browseChanged.connect(onFileOpenChanged); Window.browseAsync("Select Model to Import", "", "*.json"); } else { Window.promptTextChanged.connect(onPromptTextChanged); diff --git a/scripts/system/emote.js b/scripts/system/emote.js index f1f739c126..139870fd63 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -43,7 +43,7 @@ var activeTimer = false; // used to cancel active timer if a user plays an amima var activeEmote = false; // to keep track of the currently playing emote button = tablet.addButton({ - //icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this + icon: "icons/tablet-icons/EmoteAppIcon.svg", text: EMOTE_LABEL, sortOrder: EMOTE_APP_SORT_ORDER }); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 082528ffc5..d3848da7d0 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -37,8 +37,7 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", - text: buttonName, - sortOrder: 8 + text: buttonName }); } diff --git a/scripts/system/goto.js b/scripts/system/goto.js index d364bf579e..5cc5bad844 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -41,8 +41,7 @@ if (Settings.getValue("HUDUIEnabled")) { button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", activeIcon: "icons/tablet-icons/goto-a.svg", - text: buttonName, - sortOrder: 8 + text: buttonName }); } diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html index 30ef3e17a1..0a423b9b6c 100644 --- a/scripts/system/html/EmoteApp.html +++ b/scripts/system/html/EmoteApp.html @@ -44,11 +44,11 @@ input[type=button] { font-family: 'Raleway'; font-weight: bold; - font-size: 13px; + font-size: 20px; text-transform: uppercase; vertical-align: top; - height: 28px; - min-width: 120px; + height: 105px; + min-width: 190px; padding: 0px 18px; margin-right: 6px; border-radius: 5px; @@ -98,14 +98,14 @@

Click an emotion to Emote:

-

-

-

-

-

-

-

-

+

+

+

+

+

+

+

+

diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 8819960354..ee3a9ce7ec 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -575,6 +575,11 @@ var selectionDisplay = null; // for gridTool.js to ignore method: 'purchases_showMyItems' }); break; + case 'refreshConnections': + case 'enable_ChooseRecipientNearbyMode': + case 'disable_ChooseRecipientNearbyMode': + // NOP + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 11c083dacc..728760c1e7 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -539,8 +539,14 @@ return startingUp; } - function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + function onDomainConnectionRefused(reason, reasonCode) { + // the "login error" reason means that the DS couldn't decrypt the username signature + // since this eventually resolves itself for good actors we don't need to show a notification for it + var LOGIN_ERROR_REASON_CODE = 2; + + if (reasonCode != LOGIN_ERROR_REASON_CODE) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + } } function onEditError(msg) { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index ed7059f9f3..1b93bdde32 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -685,7 +685,6 @@ function startup() { }); button.clicked.connect(onTabletButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); - Users.usernameFromIDReply.connect(usernameFromIDReply); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); @@ -708,6 +707,7 @@ function off() { Controller.mousePressEvent.disconnect(handleMouseEvent); Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); + Users.usernameFromIDReply.disconnect(usernameFromIDReply); isWired = false; ContextOverlay.enabled = true } @@ -744,6 +744,7 @@ function onTabletButtonClicked() { Script.update.connect(updateOverlays); Controller.mousePressEvent.connect(handleMouseEvent); Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + Users.usernameFromIDReply.connect(usernameFromIDReply); triggerMapping.enable(); triggerPressMapping.enable(); audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); @@ -890,7 +891,6 @@ function shutdown() { button.clicked.disconnect(onTabletButtonClicked); tablet.removeButton(button); tablet.screenChanged.disconnect(onTabletScreenChanged); - Users.usernameFromIDReply.disconnect(usernameFromIDReply); Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); diff --git a/scripts/system/run.js b/scripts/system/run.js new file mode 100644 index 0000000000..c34271b18d --- /dev/null +++ b/scripts/system/run.js @@ -0,0 +1,38 @@ +"use strict"; + +/* global Script, Tablet, MyAvatar */ + +(function() { // BEGIN LOCAL_SCOPE + var WALK_SPEED = 2.6; + var RUN_SPEED = 4.5; + var MIDDLE_SPEED = (WALK_SPEED + RUN_SPEED) / 2.0; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: Script.resolvePath("assets/images/run.svg"), + text: "RUN" + }); + + function onClicked() { + if (MyAvatar.walkSpeed < MIDDLE_SPEED) { + button.editProperties({isActive: true}); + MyAvatar.walkSpeed = RUN_SPEED; + } else { + button.editProperties({isActive: false}); + MyAvatar.walkSpeed = WALK_SPEED; + } + } + + function cleanup() { + button.clicked.disconnect(onClicked); + tablet.removeButton(button); + } + + button.clicked.connect(onClicked); + if (MyAvatar.walkSpeed < MIDDLE_SPEED) { + button.editProperties({isActive: false}); + } else { + button.editProperties({isActive: true}); + } + +}()); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 2a0e827932..43dae9625a 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -136,7 +136,7 @@ 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', - 'protocol=' + encodeURIComponent(location.protocolVersion()), + 'protocol=' + encodeURIComponent(Window.protocolSignature()), 'per_page=' + count ]; var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&'); diff --git a/scripts/system/tablet-users.js b/scripts/system/tablet-users.js index 6f37cd55eb..6181173818 100644 --- a/scripts/system/tablet-users.js +++ b/scripts/system/tablet-users.js @@ -37,8 +37,7 @@ var button = tablet.addButton({ icon: "icons/tablet-icons/users-i.svg", activeIcon: "icons/tablet-icons/users-a.svg", - text: "USERS", - sortOrder: 11 + text: "USERS" }); var onUsersScreen = false; diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index c3d25cfe2e..b5e866ccce 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -1,4 +1,5 @@ - +# FIXME Disabling test on OSX because of unexpected link error +if (NOT APPLE) set(TARGET_NAME controllers-test) # This is not a testcase -- just set it up as a regular hifi project @@ -23,3 +24,4 @@ if (WIN32) endif() package_libraries_for_deployment() +endif() \ No newline at end of file diff --git a/tests/gl/CMakeLists.txt b/tests/gl/CMakeLists.txt new file mode 100644 index 0000000000..2b2b79d8b2 --- /dev/null +++ b/tests/gl/CMakeLists.txt @@ -0,0 +1,8 @@ +set(TARGET_NAME gl-test) +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") +link_hifi_libraries(shared gl) +package_libraries_for_deployment() +target_nsight() +target_opengl() diff --git a/tests/gl/src/main.cpp b/tests/gl/src/main.cpp new file mode 100644 index 0000000000..7435c8c2bf --- /dev/null +++ b/tests/gl/src/main.cpp @@ -0,0 +1,41 @@ +// +// Created by Bradley Austin Davis on 2018/01/11 +// Copyright 2014 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 +#include +#include +#include + + +int main(int argc, char** argv) { + QGuiApplication app(argc, argv); + + GLWindow* window = new GLWindow(); + window->create(); + window->show(); + bool contextCreated = false; + QTimer* timer = new QTimer(); + QObject::connect(timer, &QTimer::timeout, [&] { + if (!contextCreated) { + window->createContext(); + contextCreated = true; + } + if (!window->makeCurrent()) { + throw std::runtime_error("Failed"); + } + glClearColor(1.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + window->swapBuffers(); + window->doneCurrent(); + }); + + timer->setInterval(15); + timer->setSingleShot(false); + timer->start(); + app.exec(); +} diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 9772320812..2092279159 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -4,7 +4,13 @@ AUTOSCRIBE_SHADER_LIB(gpu graphics render-utils) setup_hifi_project(Quick Gui OpenGL Script Widgets) setup_memory_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") -link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx graphics model-networking animation script-engine render render-utils octree image ktx) +link_hifi_libraries( + shared networking gl + ktx gpu procedural octree image + graphics model-networking fbx animation + script-engine render render-utils + ${PLATFORM_GL_BACKEND} +) if (WIN32) add_dependency_external_projects(wasapi) diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index d37be7c790..6a509afe4e 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -163,46 +163,31 @@ class MyTestWindow : public TestWindow { } }; -extern bool needsSparseRectification(const uvec2& size); -extern uvec2 rectifyToSparseSize(const uvec2& size); +extern uvec2 rectifySize(const uvec2& size); void testSparseRectify() { - std::vector> NEEDS_SPARSE_TESTS {{ + std::vector> SPARSE_SIZE_TESTS { // Already sparse - { {1024, 1024 }, false }, - { { 128, 128 }, false }, + { {1024, 1024 }, { 1024, 1024 } }, + { { 128, 128 }, { 128, 128 } }, // Too small in one dimension - { { 127, 127 }, false }, - { { 1, 1 }, false }, - { { 1000, 1 }, false }, - { { 1024, 1 }, false }, - { { 100, 100 }, false }, - // needs rectification - { { 1000, 1000 }, true }, - { { 1024, 1000 }, true }, - } }; - - for (const auto& test : NEEDS_SPARSE_TESTS) { - const auto& size = test.first; - const auto& expected = test.second; - auto result = needsSparseRectification(size); - Q_ASSERT(expected == result); - result = needsSparseRectification(uvec2(size.y, size.x)); - Q_ASSERT(expected == result); - } - - std::vector> SPARSE_SIZE_TESTS { { + { { 127, 127 }, { 128, 128 } }, + { { 1, 1 }, { 1, 1 } }, + { { 1000, 1 }, { 1024, 1 } }, + { { 1024, 1 }, { 1024, 1 } }, + { { 100, 100 }, { 128, 128 } }, + { { 57, 510 }, { 64, 512 } }, // needs rectification { { 1000, 1000 }, { 1024, 1024 } }, { { 1024, 1000 }, { 1024, 1024 } }, - } }; + }; for (const auto& test : SPARSE_SIZE_TESTS) { const auto& size = test.first; const auto& expected = test.second; - auto result = rectifyToSparseSize(size); + auto result = rectifySize(size); Q_ASSERT(expected == result); - result = rectifyToSparseSize(uvec2(size.y, size.x)); + result = rectifySize(uvec2(size.y, size.x)); Q_ASSERT(expected == uvec2(result.y, result.x)); } } diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index 9739533fd2..5314f7a45b 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -12,7 +12,15 @@ setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(shared networking graphics fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural midi ui) +link_hifi_libraries( + shared networking animation + ktx image octree gl gpu + render render-utils + graphics fbx model-networking + entities entities-renderer audio avatars script-engine + physics procedural midi ui + ${PLATFORM_GL_BACKEND} +) if (WIN32) target_link_libraries(${TARGET_NAME} Winmm.lib) diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index db0fbea9ef..2bf0c74067 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -226,9 +226,7 @@ public: _context.makeCurrent(); window->setSurfaceType(QSurface::OpenGLSurface); _context.makeCurrent(_context.qglContext(), window); -#ifdef Q_OS_WIN - wglSwapIntervalEXT(0); -#endif + gl::setSwapInterval(0); // GPU library init gpu::Context::init(); _gpuContext = std::make_shared(); diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt index 302ddd0f97..dbfb7d33db 100644 --- a/tests/render-texture-load/CMakeLists.txt +++ b/tests/render-texture-load/CMakeLists.txt @@ -12,7 +12,15 @@ setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(shared octree gl gpu gpu-gl render graphics model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics ktx image) +link_hifi_libraries( + shared networking octree + gl gpu render ktx image animation + graphics fbx model-networking + render-utils + entities entities-renderer audio avatars + script-engine physics + ${PLATFORM_GL_BACKEND} +) package_libraries_for_deployment() diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 67b80d9ba8..066b39fc40 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -147,9 +149,7 @@ public: } _context.makeCurrent(); - glewExperimental = true; - glewInit(); - glGetError(); + gl::initModuleGl(); //wglSwapIntervalEXT(0); _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); @@ -441,6 +441,7 @@ protected: } void reportMemory() { +#if !defined(USE_GLES) static GLint lastMemory = 0; GLint availableMem; glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &availableMem); @@ -449,6 +450,7 @@ protected: qDebug() << "Delta " << availableMem - lastMemory; } lastMemory = availableMem; +#endif } void derezTexture() { diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt index 4e67aef3be..e3b1523a96 100644 --- a/tests/render-utils/CMakeLists.txt +++ b/tests/render-utils/CMakeLists.txt @@ -8,7 +8,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") setup_memory_debugger() # link in the shared libraries -link_hifi_libraries(render-utils gl gpu gpu-gl shared) +link_hifi_libraries(render-utils gl gpu shared ${PLATFORM_GL_BACKEND}) target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) if (WIN32) diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index a6796b8601..76d2aa100c 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -8,9 +8,12 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") setup_memory_debugger() # link in the shared libraries -link_hifi_libraries(shared octree gl gpu gpu-gl graphics render fbx networking entities - script-engine physics - render-utils entities-renderer) +link_hifi_libraries( + shared octree gl gpu graphics render fbx networking entities + script-engine physics + render-utils entities-renderer + ${PLATFORM_GL_BACKEND} +) include_directories("${PROJECT_BINARY_DIR}/../../libraries/gpu/") include_directories("${PROJECT_BINARY_DIR}/../../libraries/render-utils/") diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index e5f2c1fb97..a875f5676a 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -1,47 +1,41 @@ -set(TARGET_NAME auto-tester) +set (TARGET_NAME auto-tester) project(${TARGET_NAME}) # Automatically run UIC and MOC. This replaces the older WRAP macros -SET(CMAKE_AUTOUIC ON) -SET(CMAKE_AUTOMOC ON) +SET (CMAKE_AUTOUIC ON) +SET (CMAKE_AUTOMOC ON) -setup_hifi_project(Core Widgets) -link_hifi_libraries() +setup_hifi_project (Core Widgets) +link_hifi_libraries () # FIX: Qt was built with -reduce-relocations if (Qt5_POSITION_INDEPENDENT_CODE) - SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + SET (CMAKE_POSITION_INDEPENDENT_CODE ON) endif() # Qt includes -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${Qt5Core_INCLUDE_DIRS}) -include_directories(${Qt5Widgets_INCLUDE_DIRS}) +include_directories (${CMAKE_CURRENT_SOURCE_DIR}) +include_directories (${Qt5Core_INCLUDE_DIRS}) +include_directories (${Qt5Widgets_INCLUDE_DIRS}) -set(QT_LIBRARIES Qt5::Core Qt5::Widgets) - -# Find all sources files -file (GLOB_RECURSE SOURCES src/*.cpp) -file (GLOB_RECURSE HEADERS src/*.h) -file (GLOB_RECURSE UIS src/ui/*.ui) +set (QT_LIBRARIES Qt5::Core Qt5::Widgets) if (WIN32) # Do not show Console - set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) + set_property (TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) endif() -add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) +target_zlib() +add_dependency_external_projects (quazip) +find_package (QuaZip REQUIRED) +target_include_directories( ${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) -target_link_libraries(PROJECT_NAME ${QT_LIBRARIES}) - -# Copy required dll's. -add_custom_command(TARGET auto-tester POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ -) +package_libraries_for_deployment() if (WIN32) + add_paths_to_fixup_libs (${QUAZIP_DLL_PATH}) + find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) if (NOT WINDEPLOYQT_COMMAND) diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 121c98e16e..94b95a5ab6 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "ImageComparer.h" +#include "common.h" #include @@ -26,11 +27,6 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co const double c1 = pow((K1 * L), 2); const double c2 = pow((K2 * L), 2); - // Coefficients for luminosity calculation - const double R_Y = 0.212655f; - const double G_Y = 0.715158f; - const double B_Y = 0.072187f; - // First go over all full 8x8 blocks // This is done in 3 loops // 1) Read the pixels into a linear array (an optimization) @@ -116,4 +112,4 @@ double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) co } return ssim / windowCounter; -}; +}; \ No newline at end of file diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 8cb36fcfca..6c637ab404 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -13,18 +13,62 @@ #include #include +#include +#include + Test::Test() { - snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); + snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.*-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg"); mismatchWindow.setModal(true); } -bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) { +bool Test::createTestResultsFolderPathIfNeeded(QString directory) { + // The test results folder is located in the root of the tests (i.e. for recursive test evaluation) + if (testResultsFolderPath == "") { + testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER; + QDir testResultsFolder(testResultsFolderPath); + + if (testResultsFolder.exists()) { + testResultsFolder.removeRecursively(); + } + + // Create a new test results folder + return QDir().mkdir(testResultsFolderPath); + } else { + return true; + } +} + +void Test::zipAndDeleteTestResultsFolder() { + QString zippedResultsFileName { testResultsFolderPath + ".zip" }; + QFileInfo fileInfo(zippedResultsFileName); + if (!fileInfo.exists()) { + QFile::remove(zippedResultsFileName); + } + + QDir testResultsFolder(testResultsFolderPath); + if (!testResultsFolder.isEmpty()) { + JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath); + } + + testResultsFolder.removeRecursively(); + + //In all cases, for the next evaluation + testResultsFolderPath = ""; + index = 1; +} + +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar) { + progressBar->setMinimum(0); + progressBar->setMaximum(expectedImages.length() - 1); + progressBar->setValue(0); + progressBar->setVisible(true); + // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD{ 0.999 }; + const double THRESHOLD { 0.999 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImages.length(); ++i) { @@ -45,42 +89,107 @@ bool Test::compareImageLists(QStringList expectedImages, QStringList resultImage } if (similarityIndex < THRESHOLD) { - mismatchWindow.setTestFailure(TestFailure{ + TestFailure testFailure = TestFailure{ (float)similarityIndex, expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image - }); + }; - mismatchWindow.exec(); + mismatchWindow.setTestFailure(testFailure); - switch (mismatchWindow.getUserResponse()) { - case USER_RESPONSE_PASS: - break; - case USE_RESPONSE_FAIL: - success = false; - break; - case USER_RESPONSE_ABORT: - keepOn = false; - success = false; - break; - default: - assert(false); - break; + if (!interactiveMode) { + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + success = false; + } else { + mismatchWindow.exec(); + + switch (mismatchWindow.getUserResponse()) { + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage()); + success = false; + break; + case USER_RESPONSE_ABORT: + keepOn = false; + success = false; + break; + default: + assert(false); + break; + } } } + + progressBar->setValue(i); } + progressBar->setVisible(false); return success; } -void Test::evaluateTests() { +void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) { + if (!QDir().exists(testResultsFolderPath)) { + messageBox.critical(0, "Internal error", "Folder " + testResultsFolderPath + " not found"); + exit(-1); + } + + QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) }; + if (!QDir().mkdir(failureFolderPath)) { + messageBox.critical(0, "Internal error", "Failed to create folder " + failureFolderPath); + exit(-1); + } + ++index; + + QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME); + if (!descriptionFile.open(QIODevice::ReadWrite)) { + messageBox.critical(0, "Internal error", "Failed to create file " + TEST_RESULTS_FILENAME); + exit(-1); + } + + // Create text file describing the failure + QTextStream stream(&descriptionFile); + stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/' + stream << "Expected image was " << testFailure._expectedImageFilename << endl; + stream << "Actual image was " << testFailure._actualImageFilename << endl; + stream << "Similarity index was " << testFailure._error << endl; + + descriptionFile.close(); + + // Copy expected and actual images, and save the difference image + QString sourceFile; + QString destinationFile; + + sourceFile = testFailure._pathname + testFailure._expectedImageFilename; + destinationFile = failureFolderPath + "/" + "Expected Image.jpg"; + if (!QFile::copy(sourceFile, destinationFile)) { + messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + sourceFile = testFailure._pathname + testFailure._actualImageFilename; + destinationFile = failureFolderPath + "/" + "Actual Image.jpg"; + if (!QFile::copy(sourceFile, destinationFile)) { + messageBox.critical(0, "Internal error", "Failed to copy " + sourceFile + " to " + destinationFile); + exit(-1); + } + + comparisonImage.save(failureFolderPath + "/" + "Difference Image.jpg"); +} + +void Test::evaluateTests(bool interactiveMode, QProgressBar* progressBar) { // Get list of JPEG images in folder, sorted by name QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); if (pathToImageDirectory == "") { return; } + // Leave if test results folder could not be created + if (!createTestResultsFolderPathIfNeeded(pathToImageDirectory)) { + return; + } + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); // Separate images into two lists. The first is the expected images, the second is the test results @@ -107,36 +216,57 @@ void Test::evaluateTests() { exit(-1); } - bool success = compareImageLists(expectedImages, resultImages); + bool success = compareImageLists(expectedImages, resultImages, pathToImageDirectory, interactiveMode, progressBar); if (success) { messageBox.information(0, "Success", "All images are as expected"); } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } + + zipAndDeleteTestResultsFolder(); +} + +bool Test::isAValidDirectory(QString pathname) { + // Only process directories + QDir dir(pathname); + if (!dir.exists()) { + return false; + } + + // Ignore '.', '..' directories + if (pathname[pathname.length() - 1] == '.') { + return false; + } + + return true; } // Two criteria are used to decide if a folder contains valid test results. // 1) a 'test'js' file exists in the folder // 2) the folder has the same number of actual and expected images -void Test::evaluateTestsRecursively() { +void Test::evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar) { // Select folder to start recursing from QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); if (topLevelDirectory == "") { return; } + // Leave if test results folder could not be created + if (!createTestResultsFolderPathIfNeeded(topLevelDirectory)) { + return; + } + bool success{ true }; QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); - if (directory[directory.length() - 1] == '.') { - // ignore '.', '..' directories + + if (!isAValidDirectory(directory)) { continue; } - // - const QString testPathname{ directory + "/" + testFilename }; + const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (!fileInfo.exists()) { // Folder does not contain 'test.js' @@ -164,7 +294,7 @@ void Test::evaluateTestsRecursively() { } // Set success to false if any test has failed - success &= compareImageLists(expectedImages, resultImages); + success &= compareImageLists(expectedImages, resultImages, directory, interactiveMode, progressBar); } if (success) { @@ -172,6 +302,8 @@ void Test::evaluateTestsRecursively() { } else { messageBox.information(0, "Failure", "One or more images are not as expected"); } + + zipAndDeleteTestResultsFolder(); } void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { @@ -191,7 +323,8 @@ void Test::createRecursiveScript() { if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { messageBox.critical(0, "Internal Error", - "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""); + "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\"" + ); exit(-1); } @@ -206,7 +339,7 @@ void Test::createRecursiveScript() { QVector testPathnames; // First test if top-level folder has a test.js file - const QString testPathname{ topLevelDirectory + "/" + testFilename }; + const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -219,12 +352,14 @@ void Test::createRecursiveScript() { QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); - if (directory[directory.length() - 1] == '.') { - // ignore '.', '..' directories + + // Only process directories + QDir dir(directory); + if (!isAValidDirectory(directory)) { continue; } - const QString testPathname{ directory + "/" + testFilename }; + const QString testPathname{ directory + "/" + TEST_FILENAME }; QFileInfo fileInfo(testPathname); if (fileInfo.exists()) { // Current folder contains a test @@ -264,7 +399,7 @@ void Test::createRecursiveScript() { // The script produced will look as follows: // if (test1HasNotStarted) { // test1HasNotStarted = false; - // test1.test(); + // test1.test("auto"); // print("******started test 1******"); // } // | @@ -287,7 +422,7 @@ void Test::createRecursiveScript() { textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl; } textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl; - textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl; + textStream << tab << tab << tab << "test" << i << "." << testFunction << "(\"auto\");" << endl; textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl; textStream << tab << tab << "}" << endl << endl; @@ -366,6 +501,41 @@ void Test::createTest() { messageBox.information(0, "Success", "Test images have been created"); } +void Test::deleteOldSnapshots() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select root folder for snapshot deletion", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + // Recurse over folders + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + + // Only process directories + QDir dir(directory); + if (!isAValidDirectory(directory)) { + continue; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); + + // Delete any file that is a snapshot (NOT the Expected Images) + QStringList expectedImages; + QStringList resultImages; + foreach(QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = directory + "/" + currentFilename; + if (isInSnapshotFilenameFormat(currentFilename)) { + if (!QFile::remove(fullCurrentFilename)) { + messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nSnapshot deletion aborted"); + exit(-1); + } + } + } + } +} + QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) { imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; @@ -374,6 +544,7 @@ QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirect return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); } +// Use regular expressions to check if files are in specific format bool Test::isInSnapshotFilenameFormat(QString filename) { return (snapshotFilenameFormat.match(filename).hasMatch()); } diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 1f7b1e92a7..aa1346fa2a 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -1,6 +1,5 @@ // // Test.h -// zone/ambientLightInheritence // // Created by Nissim Hadar on 2 Nov 2017. // Copyright 2013 High Fidelity, Inc. @@ -15,6 +14,7 @@ #include #include #include +#include #include "ImageComparer.h" #include "ui/MismatchWindow.h" @@ -23,10 +23,13 @@ class Test { public: Test(); - void evaluateTests(); - void evaluateTestsRecursively(); + void evaluateTests(bool interactiveMode, QProgressBar* progressBar); + void evaluateTestsRecursively(bool interactiveMode, QProgressBar* progressBar); void createRecursiveScript(); void createTest(); + void deleteOldSnapshots(); + + bool compareImageLists(QStringList expectedImages, QStringList resultImages, QString testDirectory, bool interactiveMode, QProgressBar* progressBar); QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); @@ -35,8 +38,17 @@ public: void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); + void appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage); + + bool createTestResultsFolderPathIfNeeded(QString directory); + void zipAndDeleteTestResultsFolder(); + + bool isAValidDirectory(QString pathname); + private: - const QString testFilename{ "test.js" }; + const QString TEST_FILENAME { "test.js" }; + const QString TEST_RESULTS_FOLDER { "TestResults" }; + const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; QMessageBox messageBox; @@ -49,7 +61,9 @@ private: ImageComparer imageComparer; - bool compareImageLists(QStringList expectedImages, QStringList resultImages); + + QString testResultsFolderPath { "" }; + int index { 1 }; }; -#endif // hifi_test_h +#endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h index 126177358f..939814df62 100644 --- a/tools/auto-tester/src/common.h +++ b/tools/auto-tester/src/common.h @@ -34,4 +34,9 @@ enum UserResponse { USER_RESPONSE_ABORT }; -#endif // hifi_common_h +// Coefficients for luminosity calculation +const double R_Y = 0.212655f; +const double G_Y = 0.715158f; +const double B_Y = 0.072187f; + +#endif // hifi_common_h \ No newline at end of file diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index 6e5e06b732..45a3743482 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -17,4 +17,4 @@ int main(int argc, char *argv[]) { autoTester.show(); return application.exec(); -} +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 105baddb92..2834ff81e0 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -12,14 +12,18 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); + + ui.checkBoxInteractiveMode->setChecked(true); + + ui.progressBar->setVisible(false); } void AutoTester::on_evaluateTestsButton_clicked() { - test.evaluateTests(); + test.evaluateTests(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { - test.evaluateTestsRecursively(); + test.evaluateTestsRecursively(ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } void AutoTester::on_createRecursiveScriptButton_clicked() { @@ -30,6 +34,10 @@ void AutoTester::on_createTestButton_clicked() { test.createTest(); } +void AutoTester::on_deleteOldSnapshotsButton_clicked() { + test.deleteOldSnapshots(); +} + void AutoTester::on_closeButton_clicked() { exit(0); } \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index acfea32ba1..35f609a89d 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -1,6 +1,5 @@ // // AutoTester.h -// zone/ambientLightInheritence // // Created by Nissim Hadar on 2 Nov 2017. // Copyright 2013 High Fidelity, Inc. @@ -22,10 +21,11 @@ public: AutoTester(QWidget *parent = Q_NULLPTR); private slots: -void on_evaluateTestsButton_clicked(); -void on_evaluateTestsRecursivelyButton_clicked(); -void on_createRecursiveScriptButton_clicked(); + void on_evaluateTestsButton_clicked(); + void on_evaluateTestsRecursivelyButton_clicked(); + void on_createRecursiveScriptButton_clicked(); void on_createTestButton_clicked(); + void on_deleteOldSnapshotsButton_clicked(); void on_closeButton_clicked(); private: diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 7032ef9710..d06255acf6 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 286 - 470 + 607 + 395 @@ -17,9 +17,9 @@ - 60 - 360 - 160 + 190 + 300 + 220 40 @@ -30,9 +30,9 @@ - 60 - 270 - 160 + 360 + 130 + 220 40 @@ -43,9 +43,9 @@ - 60 - 20 - 160 + 20 + 75 + 220 40 @@ -56,9 +56,9 @@ - 60 - 210 - 160 + 360 + 75 + 220 40 @@ -69,9 +69,9 @@ - 60 - 75 - 160 + 20 + 130 + 220 40 @@ -79,13 +79,55 @@ Evaluate Tests Recursively + + + + 23 + 40 + 131 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Interactive Mode + + + + + + 20 + 190 + 255 + 23 + + + + 24 + + + + + + 360 + 240 + 220 + 40 + + + + Delete Old Snapshots + + 0 0 - 286 + 607 21 @@ -103,4 +145,4 @@ -
+ \ No newline at end of file diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp index 07664a1667..d880a1abdc 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.cpp +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -11,11 +11,48 @@ #include +#include + MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { setupUi(this); expectedImage->setScaledContents(true); resultImage->setScaledContents(true); + diffImage->setScaledContents(true); +} + +QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { + // This is an optimization, as QImage.setPixel() is embarrassingly slow + unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; + + // loop over each pixel + for (int y = 0; y < expectedImage.height(); ++y) { + for (int x = 0; x < expectedImage.width(); ++x) { + QRgb pixelP = expectedImage.pixel(QPoint(x, y)); + QRgb pixelQ = resultImage.pixel(QPoint(x, y)); + + // Convert to luminance + double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); + + // The intensity value is modified to increase the brightness of the displayed image + double absoluteDifference = fabs(p - q) / 255.0; + double modifiedDifference = sqrt(absoluteDifference); + + int difference = (int)(modifiedDifference * 255.0); + + buffer[3 * (x + y * expectedImage.width()) + 0] = difference; + buffer[3 * (x + y * expectedImage.width()) + 1] = difference; + buffer[3 * (x + y * expectedImage.width()) + 2] = difference; + } + } + + QImage diffImage(buffer, expectedImage.width(), expectedImage.height(), QImage::Format_RGB888); + QPixmap resultPixmap = QPixmap::fromImage(diffImage); + + delete[] buffer; + + return resultPixmap; } void MismatchWindow::setTestFailure(TestFailure testFailure) { @@ -24,10 +61,19 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) { imagePath->setText("Path to test: " + testFailure._pathname); expectedFilename->setText(testFailure._expectedImageFilename); - expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); - resultFilename->setText(testFailure._actualImageFilename); - resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); + + QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename); + QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename); + + diffPixmap = computeDiffPixmap( + QImage(testFailure._pathname + testFailure._expectedImageFilename), + QImage(testFailure._pathname + testFailure._actualImageFilename) + ); + + expectedImage->setPixmap(expectedPixmap); + resultImage->setPixmap(actualPixmap); + diffImage->setPixmap(diffPixmap); } void MismatchWindow::on_passTestButton_clicked() { @@ -44,3 +90,7 @@ void MismatchWindow::on_abortTestsButton_clicked() { _userResponse = USER_RESPONSE_ABORT; close(); } + +QPixmap MismatchWindow::getComparisonImage() { + return diffPixmap; +} diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h index 7c72b7b0b7..cdbdcb4098 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.h +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -25,6 +25,9 @@ public: UserResponse getUserResponse() { return _userResponse; } + QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + QPixmap getComparisonImage(); + private slots: void on_passTestButton_clicked(); void on_failTestButton_clicked(); @@ -32,7 +35,9 @@ private slots: private: UserResponse _userResponse{ USER_RESPONSE_INVALID }; + + QPixmap diffPixmap; }; -#endif // hifi_MismatchWindow_h +#endif // hifi_MismatchWindow_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui index cab6c61e1c..72f86261ab 100644 --- a/tools/auto-tester/src/ui/MismatchWindow.ui +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1585 - 694 + 1782 + 942 @@ -16,10 +16,10 @@ - 20 - 170 - 720 - 362 + 10 + 25 + 800 + 450 @@ -29,28 +29,41 @@ - 760 - 170 - 720 - 362 + 900 + 25 + 800 + 450 result image + + + + 540 + 480 + 800 + 450 + + + + diff image + + - 760 - 90 - 800 + 60 + 660 + 480 28 - 16 + 12 @@ -60,15 +73,15 @@ - 40 - 90 - 700 + 60 + 630 + 480 28 - 16 + 12 @@ -78,15 +91,15 @@ - 40 - 30 + 20 + 600 1200 28 - 16 + 12 @@ -97,7 +110,7 @@ 30 - 600 + 790 75 23 @@ -109,8 +122,8 @@ - 330 - 600 + 120 + 790 75 23 @@ -122,36 +135,62 @@ - 630 - 600 - 75 + 210 + 790 + 121 23 - Abort Tests + Abort current test - 810 - 600 - 720 + 30 + 850 + 500 28 - 16 + 12 similarity + + + + 30 + 5 + 151 + 16 + + + + Expected Image + + + + + + 930 + 5 + 151 + 16 + + + + Actual Image + + - + \ No newline at end of file diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 535d9a49a9..edc2492e82 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -195,9 +195,9 @@ void DomainBaker::enumerateEntities() { auto filename = modelURL.fileName(); auto baseName = filename.left(filename.lastIndexOf('.')); auto subDirName = "/" + baseName; - int i = 0; + int i = 1; while (QDir(_contentOutputPath + subDirName).exists()) { - subDirName = "/" + baseName + "-" + i++; + subDirName = "/" + baseName + "-" + QString::number(i++); } QSharedPointer baker { new FBXBaker(modelURL, []() -> QThread* { diff --git a/unpublishedScripts/marketplace/camera-move/app-camera-move.js b/unpublishedScripts/marketplace/camera-move/app-camera-move.js index f58dd3d3bd..f9361c6091 100644 --- a/unpublishedScripts/marketplace/camera-move/app-camera-move.js +++ b/unpublishedScripts/marketplace/camera-move/app-camera-move.js @@ -133,7 +133,7 @@ var DEBUG_INFO = { Reticle: { supportsScale: 'scale' in Reticle, }, - protocolVersion: location.protocolVersion, + protocolVersion: Window.protocolSignature(), }; var globalState = { diff --git a/unpublishedScripts/marketplace/camera-move/modules/custom-settings-app/CustomSettingsApp.js b/unpublishedScripts/marketplace/camera-move/modules/custom-settings-app/CustomSettingsApp.js index c76adea940..95dba5c558 100644 --- a/unpublishedScripts/marketplace/camera-move/modules/custom-settings-app/CustomSettingsApp.js +++ b/unpublishedScripts/marketplace/camera-move/modules/custom-settings-app/CustomSettingsApp.js @@ -52,7 +52,7 @@ function CustomSettingsApp(options) { this.extraParams = Object.assign(options.extraParams || {}, { customSettingsVersion: CustomSettingsApp.version+'', - protocolVersion: location.protocolVersion && location.protocolVersion() + protocolVersion: Window.protocolSignature && Window.protocolSignature() }); var params = { diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index b2d8ce55db..7118b3623c 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -31,8 +31,7 @@ var activeButton = tablet.addButton({ icon: whiteIcon, activeIcon: blackIcon, text: APP_NAME, - isActive: isActive, - sortOrder: 11 + isActive: isActive }); if (isActive) { diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js index 7bc65722cd..0bd23552e4 100644 --- a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -32,8 +32,7 @@ var button = tablet.addButton({ icon: ICONS.icon, activeIcon: ICONS.activeIcon, - text: TABLET_BUTTON_NAME, - sortOrder: 1 + text: TABLET_BUTTON_NAME }); var hasEventBridge = false;