diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a2e57dd5..6956fd22c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,8 +108,10 @@ set(PLATFORM_QT_GL OpenGL) if (USE_GLES) add_definitions(-DUSE_GLES) + add_definitions(-DGPU_POINTER_STORAGE_SHARED) set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles) else() + add_definitions(-DGPU_POINTER_STORAGE_RAW) set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl) endif() diff --git a/android/apps/framePlayer/CMakeLists.txt b/android/apps/framePlayer/CMakeLists.txt new file mode 100644 index 0000000000..327c2dd1bd --- /dev/null +++ b/android/apps/framePlayer/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME framePlayer) +setup_hifi_library(AndroidExtras) +link_hifi_libraries(shared ktx shaders qml gpu gl ${PLATFORM_GL_BACKEND}) +target_link_libraries(${TARGET_NAME} android log m) +target_opengl() diff --git a/android/apps/framePlayer/build.gradle b/android/apps/framePlayer/build.gradle new file mode 100644 index 0000000000..fc8651fce1 --- /dev/null +++ b/android/apps/framePlayer/build.gradle @@ -0,0 +1,50 @@ +apply plugin: 'com.android.application' + +android { + signingConfigs { + release { + keyAlias 'key0' + keyPassword 'password' + storeFile file('C:/android/keystore.jks') + storePassword 'password' + } + } + + compileSdkVersion 28 + defaultConfig { + applicationId "io.highfidelity.frameplayer" + minSdkVersion 25 + targetSdkVersion 28 + ndk { abiFilters 'arm64-v8a' } + externalNativeBuild { + cmake { + arguments '-DHIFI_ANDROID=1', + '-DHIFI_ANDROID_APP=framePlayer', + '-DANDROID_TOOLCHAIN=clang', + '-DANDROID_STL=c++_shared', + '-DCMAKE_VERBOSE_MAKEFILE=ON' + targets = ['framePlayer'] + } + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + } + + externalNativeBuild.cmake.path '../../../CMakeLists.txt' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs') + //implementation project(':oculus') + implementation project(':qt') +} diff --git a/android/apps/framePlayer/proguard-rules.pro b/android/apps/framePlayer/proguard-rules.pro new file mode 100644 index 0000000000..b3c0078513 --- /dev/null +++ b/android/apps/framePlayer/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/apps/framePlayer/src/main/AndroidManifest.xml b/android/apps/framePlayer/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..ed576387d2 --- /dev/null +++ b/android/apps/framePlayer/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/apps/framePlayer/src/main/cpp/FramePlayer.qrc b/android/apps/framePlayer/src/main/cpp/FramePlayer.qrc new file mode 100644 index 0000000000..44fdac2666 --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/FramePlayer.qrc @@ -0,0 +1,6 @@ + + + + qml/main.qml + + diff --git a/android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp b/android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp new file mode 100644 index 0000000000..7f0ec67639 --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/PlayerWindow.cpp @@ -0,0 +1,91 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "PlayerWindow.h" + +#include +#include +#include +#include + +#include +#include + +PlayerWindow::PlayerWindow() { + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + setSurfaceType(QSurface::OpenGLSurface); + create(); + showFullScreen(); + + // Make sure the window has been created by processing events + QCoreApplication::processEvents(); + + // Start the rendering thread + _renderThread.initialize(this, &_surface); + + // Start the UI + _surface.resize(size()); + connect(&_surface, &hifi::qml::OffscreenSurface::rootContextCreated, this, [](QQmlContext* context){ + context->setContextProperty("FRAMES_FOLDER", "file:assets:/frames"); + }); + _surface.load("qrc:///qml/main.qml"); + + // Connect the UI handler + QObject::connect(_surface.getRootItem(), SIGNAL(loadFile(QString)), + this, SLOT(loadFile(QString)) + ); + + // Turn on UI input events + installEventFilter(&_surface); +} + +PlayerWindow::~PlayerWindow() { +} + +// static const char* FRAME_FILE = "assets:/frames/20190110_1635.json"; + +static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) { + QImage image; + QImageReader(filename.c_str()).read(&image); + if (layer > 0) { + return; + } + texture->assignStoredMip(0, image.byteCount(), image.constBits()); +} + +void PlayerWindow::loadFile(QString filename) { + QString realFilename = QUrl(filename).toLocalFile(); + if (QFileInfo(realFilename).exists()) { + auto frame = gpu::readFrame(realFilename.toStdString(), _renderThread._externalTexture, &textureLoader); + _surface.pause(); + _renderThread.submitFrame(frame); + } +} + +void PlayerWindow::touchEvent(QTouchEvent* event) { + // Super basic input handling when the 3D scene is active.... tap with two finders to return to the + // QML UI + static size_t touches = 0; + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + touches = std::max(touches, event->touchPoints().size()); + break; + + case QEvent::TouchEnd: + if (touches >= 2) { + _renderThread.submitFrame(nullptr); + _surface.resume(); + } + touches = 0; + break; + + default: + break; + } +} diff --git a/android/apps/framePlayer/src/main/cpp/PlayerWindow.h b/android/apps/framePlayer/src/main/cpp/PlayerWindow.h new file mode 100644 index 0000000000..b1cc7da5cd --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/PlayerWindow.h @@ -0,0 +1,35 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once +#include +#include + +#include +#include +#include "RenderThread.h" + +// Create a simple OpenGL window that renders text in various ways +class PlayerWindow : public QWindow { + Q_OBJECT + +public: + PlayerWindow(); + virtual ~PlayerWindow(); + +protected: + void touchEvent(QTouchEvent *ev) override; + +public slots: + void loadFile(QString filename); + +private: + hifi::qml::OffscreenSurface _surface; + QSettings _settings; + RenderThread _renderThread; +}; diff --git a/android/apps/framePlayer/src/main/cpp/RenderThread.cpp b/android/apps/framePlayer/src/main/cpp/RenderThread.cpp new file mode 100644 index 0000000000..76da789baa --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/RenderThread.cpp @@ -0,0 +1,162 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "RenderThread.h" + +#include + +void RenderThread::submitFrame(const gpu::FramePointer& frame) { + std::unique_lock lock(_frameLock); + _pendingFrames.push(frame); +} + +void RenderThread::move(const glm::vec3& v) { + std::unique_lock lock(_frameLock); + _correction = glm::inverse(glm::translate(mat4(), v)) * _correction; +} + +void RenderThread::setup() { + // Wait until the context has been moved to this thread + { std::unique_lock lock(_frameLock); } + + makeCurrent(); + // Disable vsync for profiling + ::gl::setSwapInterval(0); + + glClearColor(1, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glContext.swapBuffers(); + + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _gpuContext->beginFrame(); + _gpuContext->endFrame(); + makeCurrent(); + + + glGenFramebuffers(1, &_uiFbo); + glGenTextures(1, &_externalTexture); + glBindTexture(GL_TEXTURE_2D, _externalTexture); + static const glm::u8vec4 color{ 0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color); + + glClearColor(0, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glContext.swapBuffers(); +} + +void RenderThread::initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen) { + std::unique_lock lock(_frameLock); + setObjectName("RenderThread"); + Parent::initialize(); + + _offscreen = offscreen; + _window = window; + _glContext.setWindow(_window); + _glContext.create(); + _glContext.makeCurrent(); + + hifi::qml::OffscreenSurface::setSharedContext(_glContext.qglContext()); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + _glContext.swapBuffers(); + _glContext.doneCurrent(); + _glContext.moveToThread(_thread); + _thread->setObjectName("RenderThread"); +} + +void RenderThread::shutdown() { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _gpuContext->shutdown(); + _gpuContext.reset(); +} + +void RenderThread::renderFrame() { + auto windowSize = _window->geometry().size(); + uvec2 readFboSize; + uint32_t readFbo{ 0 }; + + if (_activeFrame) { + const auto &frame = _activeFrame; + _backend->recycle(); + _backend->syncCache(); + _gpuContext->enableStereo(frame->stereoState._enable); + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + } + auto &glBackend = static_cast(*_backend); + readFbo = glBackend.getFramebufferID(frame->framebuffer); + readFboSize = frame->framebuffer->getSize(); + CHECK_GL_ERROR(); + } else { + hifi::qml::OffscreenSurface::TextureAndFence newTextureAndFence; + if (_offscreen->fetchTexture(newTextureAndFence)) { + if (_uiTexture != 0) { + auto readFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _offscreen->getDiscardLambda()(_uiTexture, readFence); + _uiTexture = 0; + } + + glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)newTextureAndFence.second); + _uiTexture = newTextureAndFence.first; + glBindFramebuffer(GL_READ_FRAMEBUFFER, _uiFbo); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _uiTexture, 0); + } + + if (_uiTexture != 0) { + readFbo = _uiFbo; + readFboSize = { windowSize.width(), windowSize.height() }; + } + } + + if (readFbo) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + glBlitFramebuffer( + 0, 0, readFboSize.x, readFboSize.y, + 0, 0, windowSize.width(), windowSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } else { + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + _glContext.swapBuffers(); +} + +void RenderThread::updateFrame() { + std::queue pendingFrames; + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + } + + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + pendingFrames.pop(); + if (_activeFrame) { + _gpuContext->consumeFrameUpdates(_activeFrame); + } + } +} + +bool RenderThread::process() { + updateFrame(); + makeCurrent(); + renderFrame(); + return true; +} diff --git a/android/apps/framePlayer/src/main/cpp/RenderThread.h b/android/apps/framePlayer/src/main/cpp/RenderThread.h new file mode 100644 index 0000000000..c514874724 --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/RenderThread.h @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once + +#include + +#include +#include +#include + +class RenderThread : public GenericThread { + using Parent = GenericThread; +public: + QWindow* _window{ nullptr }; + std::mutex _mutex; + gpu::ContextPointer _gpuContext; // initialized during window creation + std::shared_ptr _backend; + std::atomic _presentCount{ 0 }; + std::mutex _frameLock; + std::queue _pendingFrames; + gpu::FramePointer _activeFrame; + uint32_t _externalTexture{ 0 }; + glm::mat4 _correction; + hifi::qml::OffscreenSurface* _offscreen{ nullptr }; + + gl::Context _glContext; + uint32_t _uiTexture{ 0 }; + uint32_t _uiFbo{ 0 }; + + void move(const glm::vec3& v); + void setup() override; + bool process() override; + void shutdown() override; + + void initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen); + + void submitFrame(const gpu::FramePointer& frame); + void updateFrame(); + void renderFrame(); + + bool makeCurrent() { + return _glContext.makeCurrent(); + } + + void doneCurrent() { + _glContext.doneCurrent(); + } +}; diff --git a/android/apps/framePlayer/src/main/cpp/main.cpp b/android/apps/framePlayer/src/main/cpp/main.cpp new file mode 100644 index 0000000000..3843583e5e --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/main.cpp @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis on 2018/11/22 +// 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 + +#include + +#include "PlayerWindow.h" + +void messageHandler(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(); + } + } +} + +int main(int argc, char** argv) { + setupHifiApplication("gpuFramePlayer"); + QGuiApplication app(argc, argv); + auto oldMessageHandler = qInstallMessageHandler(messageHandler); + DependencyManager::set(); + PlayerWindow window; + app.exec(); + qInstallMessageHandler(oldMessageHandler); + return 0; +} + + diff --git a/android/apps/framePlayer/src/main/cpp/qml/main.qml b/android/apps/framePlayer/src/main/cpp/qml/main.qml new file mode 100644 index 0000000000..34c4507f9d --- /dev/null +++ b/android/apps/framePlayer/src/main/cpp/qml/main.qml @@ -0,0 +1,36 @@ +import QtQuick 2.2 +import QtQuick.Dialogs 1.1 +import Qt.labs.folderlistmodel 2.11 + +Item { + id: root + width: 640 + height: 480 + + ListView { + anchors.fill: parent + + FolderListModel { + id: folderModel + folder: FRAMES_FOLDER + nameFilters: ["*.json"] + } + + Component { + id: fileDelegate + Text { + text: fileName + font.pointSize: 36 + MouseArea { + anchors.fill: parent + onClicked: root.loadFile(folderModel.folder + "/" + fileName); + } + } + } + + model: folderModel + delegate: fileDelegate + } + + signal loadFile(string filename); +} diff --git a/android/apps/framePlayer/src/main/res/drawable/ic_launcher.xml b/android/apps/framePlayer/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000000..03b1edc4e9 --- /dev/null +++ b/android/apps/framePlayer/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/android/apps/framePlayer/src/main/res/values/strings.xml b/android/apps/framePlayer/src/main/res/values/strings.xml new file mode 100644 index 0000000000..8bf550f74e --- /dev/null +++ b/android/apps/framePlayer/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + GPU Frame Player + diff --git a/android/settings.gradle b/android/settings.gradle index 40b5eb44bf..1e7b3c768a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -3,3 +3,6 @@ project(':qt').projectDir = new File(settingsDir, 'libraries/qt') include ':interface' project(':interface').projectDir = new File(settingsDir, 'apps/interface') + +//include ':framePlayer' +//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer') diff --git a/cmake/macros/TargetGlad.cmake b/cmake/macros/TargetGlad.cmake index 929f61c3f2..c9a2529986 100644 --- a/cmake/macros/TargetGlad.cmake +++ b/cmake/macros/TargetGlad.cmake @@ -7,6 +7,7 @@ # macro(TARGET_GLAD) if (ANDROID) + include(SelectLibraryConfigurations) set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/glad) set(GLAD_INCLUDE_DIRS "${INSTALL_DIR}/include") set(GLAD_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libglad_d.a) @@ -31,8 +32,8 @@ macro(TARGET_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}) + target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES}) endmacro() diff --git a/cmake/macros/TargetZlib.cmake b/cmake/macros/TargetZlib.cmake index 79dce01a3d..172e5bd13b 100644 --- a/cmake/macros/TargetZlib.cmake +++ b/cmake/macros/TargetZlib.cmake @@ -6,8 +6,13 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_ZLIB) - # using VCPKG for zlib - find_package(ZLIB REQUIRED) - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${ZLIB_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) + if (ANDROID) + # zlib is part of the NDK + target_link_libraries(${TARGET_NAME} z) + else() + # using VCPKG for zlib + find_package(ZLIB REQUIRED) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${ZLIB_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) + endif() endmacro() diff --git a/cmake/ports/nvtt/CONTROL b/cmake/ports/nvtt/CONTROL index 59ba36d830..94d6193116 100644 --- a/cmake/ports/nvtt/CONTROL +++ b/cmake/ports/nvtt/CONTROL @@ -1,3 +1,3 @@ Source: nvtt -Version: 8c7e6b40ee5095f227b75880fabd89c99d6f34c0 +Version: 330c4d56274a0f602a5c70596e2eb670a4ed56c2 Description: Texture processing tools with support for Direct3D 10 and 11 formats. \ No newline at end of file diff --git a/cmake/ports/nvtt/portfile.cmake b/cmake/ports/nvtt/portfile.cmake index 13b1253322..4cbe05b692 100644 --- a/cmake/ports/nvtt/portfile.cmake +++ b/cmake/ports/nvtt/portfile.cmake @@ -10,8 +10,8 @@ include(vcpkg_common_functions) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO highfidelity/nvidia-texture-tools - REF 8c7e6b40ee5095f227b75880fabd89c99d6f34c0 - SHA512 f107d19dbbd6651ef2126b1422a5db8db291bf70311ac4fb1dbacb5ceaa8752fee38becbd32964f57596f0b84e1223bb2c3ff9d9c4fdc65c3e77a47836657cef + REF 330c4d56274a0f602a5c70596e2eb670a4ed56c2 + SHA512 4c0bc2f369120d696cc27710b6d33086b27eef55f537ec66b9a5c8b1839bc2426c0413670b0f65be52c5d353468f0126dfe024be1f0690611d4d7e33ac530127 HEAD_REF master ) diff --git a/interface/resources/qml/androidAppForce/ForFolderListModel.qml b/interface/resources/qml/androidAppForce/ForFolderListModel.qml new file mode 100644 index 0000000000..3fba3f11fa --- /dev/null +++ b/interface/resources/qml/androidAppForce/ForFolderListModel.qml @@ -0,0 +1,26 @@ +import QtQuick 2.2 +import QtQuick.Dialogs 1.1 +import Qt.labs.folderlistmodel 2.11 + +Item { + width: 640 + height: 480 + + ListView { + width: 200; height: 400 + + FolderListModel { + id: folderModel + folder: "assets:/frames/" + nameFilters: ["*.json"] + } + + Component { + id: fileDelegate + Text { text: fileName } + } + + model: folderModel + delegate: fileDelegate + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 66f39a926a..bad1d40be9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3551,8 +3551,10 @@ void Application::resizeGL() { auto renderConfig = _graphicsEngine.getRenderEngine()->getConfiguration(); assert(renderConfig); auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); - assert(mainView); - mainView->setProperty("resolutionScale", renderResolutionScale); + // mainView can be null if we're rendering in forward mode + if (mainView) { + mainView->setProperty("resolutionScale", renderResolutionScale); + } displayPlugin->setRenderResolutionScale(renderResolutionScale); } @@ -4100,6 +4102,19 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_G: + if (isShifted && isMeta && Menu::getInstance() && Menu::getInstance()->getMenu("Developer")->isVisible()) { + static const QString HIFI_FRAMES_FOLDER_VAR = "HIFI_FRAMES_FOLDER"; + static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR) + ? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR) + : "hifiFrames"; + static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}"; + QString fullPath = FileUtils::computeDocumentPath(FileUtils::replaceDateTimeTokens(GPU_FRAME_TEMPLATE)); + if (FileUtils::canCreateFile(fullPath)) { + getActiveDisplayPlugin()->captureFrame(fullPath.toStdString()); + } + } + break; case Qt::Key_X: if (isShifted && isMeta) { auto offscreenUi = getOffscreenUI(); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index dd99907b8e..5fb9c9a0ee 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -288,39 +288,37 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; _measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity; - if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) { - _measuredLinearVelocitiesIndex = 0; + _measuredLinearVelocitiesIndex %= AvatarActionHold::velocitySmoothFrames; + } + + if (_kinematicSetVelocity) { + glm::vec3 measuredLinearVelocity = _measuredLinearVelocities[0]; + for (int i = 1; i < AvatarActionHold::velocitySmoothFrames; i++) { + // there is a bit of lag between when someone releases the trigger and when the software reacts to + // the release. we calculate the velocity from previous frames but we don't include several + // of the most recent. + // + // if _measuredLinearVelocitiesIndex is + // 0 -- ignore i of 3 4 5 + // 1 -- ignore i of 4 5 0 + // 2 -- ignore i of 5 0 1 + // 3 -- ignore i of 0 1 2 + // 4 -- ignore i of 1 2 3 + // 5 -- ignore i of 2 3 4 + + // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back. + // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || + // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { + // continue; + // } + + measuredLinearVelocity += _measuredLinearVelocities[i]; } - } - - glm::vec3 measuredLinearVelocity; - for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) { - // there is a bit of lag between when someone releases the trigger and when the software reacts to - // the release. we calculate the velocity from previous frames but we don't include several - // of the most recent. - // - // if _measuredLinearVelocitiesIndex is - // 0 -- ignore i of 3 4 5 - // 1 -- ignore i of 4 5 0 - // 2 -- ignore i of 5 0 1 - // 3 -- ignore i of 0 1 2 - // 4 -- ignore i of 1 2 3 - // 5 -- ignore i of 2 3 4 - - // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back. - // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || - // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || - // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { - // continue; - // } - - measuredLinearVelocity += _measuredLinearVelocities[i]; - } - measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames + measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames // - 3 // 3 because of the 3 we skipped, above ); - if (_kinematicSetVelocity) { rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity)); rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget)); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d883aafe2c..1eb87c16f0 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -475,6 +475,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar _spaceProxiesToDelete.push_back(avatar->getSpaceIndex()); } AvatarHashMap::handleRemovedAvatar(avatar, removalReason); + avatar->tearDownGrabs(); avatar->die(); queuePhysicsChange(avatar); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6b89ba9687..92d9270d20 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -866,7 +866,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { // and all of its joints, now update our attachements. Avatar::simulateAttachments(deltaTime); relayJointDataToChildren(); - if (updateGrabs()) { + if (applyGrabChanges()) { _cauterizationNeedsUpdate = true; } @@ -5301,7 +5301,7 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabData.remove(grabID)) { - _deletedAvatarGrabs.insert(grabID); + _grabsToDelete.push_back(grabID); tellHandler = true; } }); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 7ff6a294d3..a3950c8e96 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -363,7 +363,7 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { { PROFILE_RANGE(simulation, "grabs"); - updateGrabs(); + applyGrabChanges(); } updateFadingStatus(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 0ecd9e9fce..07c1ca9a32 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -326,88 +326,79 @@ void Avatar::removeAvatarEntitiesFromTree() { } } -bool Avatar::updateGrabs() { +bool Avatar::applyGrabChanges() { + if (!_avatarGrabDataChanged && _grabsToChange.empty() && _grabsToDelete.empty()) { + // early exit for most common case: nothing to do + return false; + } + bool grabAddedOrRemoved = false; - // update the Grabs according to any changes in _avatarGrabData _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabDataChanged) { + // collect changes in _avatarGrabData foreach (auto grabID, _avatarGrabData.keys()) { - AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID); - if (grabItr == _avatarGrabs.end()) { + MapOfGrabs::iterator itr = _avatarGrabs.find(grabID); + if (itr == _avatarGrabs.end()) { GrabPointer grab = std::make_shared(); grab->fromByteArray(_avatarGrabData.value(grabID)); _avatarGrabs[grabID] = grab; - _changedAvatarGrabs.insert(grabID); + _grabsToChange.insert(grabID); } else { - GrabPointer grab = grabItr.value(); - bool changed = grab->fromByteArray(_avatarGrabData.value(grabID)); + bool changed = itr->second->fromByteArray(_avatarGrabData.value(grabID)); if (changed) { - _changedAvatarGrabs.insert(grabID); + _grabsToChange.insert(grabID); } } } _avatarGrabDataChanged = false; } - auto treeRenderer = DependencyManager::get(); - auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr; - auto sessionID = DependencyManager::get()->getSessionUUID(); - - QMutableSetIterator delItr(_deletedAvatarGrabs); - while (delItr.hasNext()) { - QUuid grabID = delItr.next(); - GrabPointer grab = _avatarGrabs[grabID]; - if (!grab) { - delItr.remove(); + // delete _avatarGrabs + VectorOfIDs undeleted; + for (const auto& id : _grabsToDelete) { + MapOfGrabs::iterator itr = _avatarGrabs.find(id); + if (itr == _avatarGrabs.end()) { continue; } bool success; + const GrabPointer& grab = itr->second; SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); - - // only clear this entry from the _deletedAvatarGrabs if we found the entity. if (success && target) { - bool iShouldTellServer = target->getEditSenderID() == sessionID; - - EntityItemPointer entity = std::dynamic_pointer_cast(target); - if (entity && entity->isAvatarEntity() && (entity->getOwningAvatarID() == sessionID || - entity->getOwningAvatarID() == AVATAR_SELF_ID)) { - // this is our own avatar-entity, so we always tell the server about the release - iShouldTellServer = true; - } - target->removeGrab(grab); - delItr.remove(); - // in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server - // about the final position. - if (entityTree) { - bool force = true; - entityTree->withWriteLock([&] { - entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer); - }); - } + _avatarGrabs.erase(itr); grabAddedOrRemoved = true; + } else { + undeleted.push_back(id); } - _avatarGrabs.remove(grabID); - _changedAvatarGrabs.remove(grabID); } + _grabsToDelete = std::move(undeleted); - QMutableSetIterator changeItr(_changedAvatarGrabs); - while (changeItr.hasNext()) { - QUuid grabID = changeItr.next(); - GrabPointer& grab = _avatarGrabs[grabID]; + // change _avatarGrabs and add Actions to target + SetOfIDs unchanged; + for (const auto& id : _grabsToChange) { + MapOfGrabs::iterator itr = _avatarGrabs.find(id); + if (itr == _avatarGrabs.end()) { + continue; + } bool success; + const GrabPointer& grab = itr->second; SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); - if (success && target) { target->addGrab(grab); - // only clear this entry from the _changedAvatarGrabs if we found the entity. - changeItr.remove(); + if (isMyAvatar()) { + EntityItemPointer entity = std::dynamic_pointer_cast(target); + if (entity) { + entity->upgradeScriptSimulationPriority(PERSONAL_SIMULATION_PRIORITY); + } + } grabAddedOrRemoved = true; + } else { + unchanged.insert(id); } } + _grabsToChange = std::move(unchanged); }); return grabAddedOrRemoved; } @@ -415,8 +406,8 @@ bool Avatar::updateGrabs() { void Avatar::accumulateGrabPositions(std::map& grabAccumulators) { // relay avatar's joint position to grabbed target in a way that allows for averaging _avatarGrabsLock.withReadLock([&] { - foreach (auto grabID, _avatarGrabs.keys()) { - const GrabPointer& grab = _avatarGrabs.value(grabID); + for (const auto& entry : _avatarGrabs) { + const GrabPointer& grab = entry.second; if (!grab || !grab->getActionID().isNull()) { continue; // the accumulated value isn't used, in this case. @@ -434,6 +425,20 @@ void Avatar::accumulateGrabPositions(std::map& g }); } +void Avatar::tearDownGrabs() { + _avatarGrabsLock.withWriteLock([&] { + for (const auto& entry : _avatarGrabs) { + _grabsToDelete.push_back(entry.first); + } + _grabsToChange.clear(); + }); + applyGrabChanges(); + if (!_grabsToDelete.empty()) { + // some grabs failed to delete, which is a possible "leak", so we log about it + qWarning() << "Failed to tearDown" << _grabsToDelete.size() << "grabs for Avatar" << getID(); + } +} + void Avatar::relayJointDataToChildren() { forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -2063,3 +2068,12 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() { } return result; } + +void Avatar::clearAvatarGrabData(const QUuid& id) { + AvatarData::clearAvatarGrabData(id); + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabs.find(id) != _avatarGrabs.end()) { + _grabsToDelete.push_back(id); + } + }); +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 7abd6ebae7..b43fe012b7 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include @@ -23,10 +26,12 @@ #include #include +#include +#include + #include "Head.h" #include "SkeletonModel.h" #include "Rig.h" -#include #include "MetaModelPayload.h" #include "MultiSphereShape.h" @@ -492,6 +497,7 @@ public: void accumulateGrabPositions(std::map& grabAccumulators); const std::vector& getMultiSphereShapes() const { return _multiSphereShapes; } + void tearDownGrabs(); signals: void targetScaleChanged(float targetScale); @@ -597,7 +603,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; - bool updateGrabs(); + bool applyGrabChanges(); void relayJointDataToChildren(); void fade(render::Transaction& transaction, render::Transition::Type type); @@ -687,8 +693,15 @@ protected: std::vector _multiSphereShapes; AABox _fitBoundingBox; + void clearAvatarGrabData(const QUuid& grabID) override; - AvatarGrabMap _avatarGrabs; + using SetOfIDs = std::set; + using VectorOfIDs = std::vector; + using MapOfGrabs = std::map; + + MapOfGrabs _avatarGrabs; + SetOfIDs _grabsToChange; // updated grab IDs -- changes needed to entities or physics + VectorOfIDs _grabsToDelete; // deleted grab IDs -- changes needed to entities or physics }; #endif // hifi_Avatar_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 87612f29ce..4e95774efb 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -3012,7 +3012,6 @@ void AvatarData::clearAvatarGrabData(const QUuid& grabID) { _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabData.remove(grabID)) { _avatarGrabDataChanged = true; - _deletedAvatarGrabs.insert(grabID); } }); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3a5545c637..d071b74d6e 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -54,7 +54,6 @@ #include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" -#include "Grab.h" #include @@ -67,8 +66,6 @@ using PackedAvatarEntityMap = QMap; // similar to AvatarEntit using AvatarEntityIDs = QSet; using AvatarGrabDataMap = QMap; -using AvatarGrabIDs = QSet; -using AvatarGrabMap = QMap; using AvatarDataSequenceNumber = uint16_t; @@ -1475,8 +1472,6 @@ protected: mutable ReadWriteLockable _avatarGrabsLock; AvatarGrabDataMap _avatarGrabData; bool _avatarGrabDataChanged { false }; // by network - AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics - AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() }; @@ -1539,7 +1534,7 @@ protected: } bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData); - void clearAvatarGrabData(const QUuid& grabID); + virtual void clearAvatarGrabData(const QUuid& grabID); private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 5835316efe..6c59dbd861 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -15,8 +15,10 @@ #include #include #include +#include #include +#include #include #include @@ -30,6 +32,7 @@ #include #include +#include #include #include #include @@ -465,11 +468,43 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) { }); } -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor) { - renderFromTexture(batch, texture, viewport, scissor, gpu::FramebufferPointer()); +void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const { + withOtherThreadContext([&] { + using namespace gpu; + auto glBackend = const_cast(*this).getGLBackend(); + FramebufferPointer framebuffer{ Framebuffer::create("captureFramebuffer") }; + TextureCapturer captureLambda = [&](const std::string& filename, const gpu::TexturePointer& texture, uint16 layer) { + QImage image; + if (texture->getUsageType() == TextureUsageType::STRICT_RESOURCE) { + image = QImage{ 1, 1, QImage::Format_ARGB32 }; + auto storedImage = texture->accessStoredMipFace(0, 0); + memcpy(image.bits(), storedImage->data(), image.sizeInBytes()); + //if (texture == textureCache->getWhiteTexture()) { + //} else if (texture == textureCache->getBlackTexture()) { + //} else if (texture == textureCache->getBlueTexture()) { + //} else if (texture == textureCache->getGrayTexture()) { + } else { + ivec4 rect = { 0, 0, texture->getWidth(), texture->getHeight() }; + framebuffer->setRenderBuffer(0, texture, layer); + glBackend->syncGPUObject(*framebuffer); + + image = QImage{ rect.z, rect.w, QImage::Format_ARGB32 }; + glBackend->downloadFramebuffer(framebuffer, rect, image); + } + QImageWriter(filename.c_str()).write(image); + }; + + if (_currentFrame) { + gpu::writeFrame(filename, _currentFrame, captureLambda); + } + }); } -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer copyFbo /*=gpu::FramebufferPointer()*/) { +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) { + renderFromTexture(batch, texture, viewport, scissor, nullptr); +} + +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { auto fbo = gpu::FramebufferPointer(); batch.enableStereo(false); batch.resetViewTransform(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 1cc060161b..5c653f8a0a 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -47,7 +47,7 @@ public: void endSession() override final; bool eventFilter(QObject* receiver, QEvent* event) override; bool isDisplayVisible() const override { return true; } - + void captureFrame(const std::string& outputName) const override; void submitFrame(const gpu::FramePointer& newFrame) override; glm::uvec2 getRecommendedRenderSize() const override { @@ -113,8 +113,8 @@ protected: // Plugin specific functionality to send the composed scene to the output window or device virtual void internalPresent(); - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer fbo); - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor); virtual void updateFrameData(); virtual glm::mat4 getViewCorrection() { return glm::mat4(); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index 40063652c8..9eafe381ab 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -69,7 +69,10 @@ bool DebugHmdDisplayPlugin::internalActivate() { _eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]); _eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, -0.0149999997, 1.0 }; _eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, -0.0149999997, 1.0 }; - _renderTargetSize = { 3024, 1680 }; + // Test HMD per-eye resolution + _renderTargetSize = uvec2{ 1214 * 2 , 1344 }; + // uncomment to capture a quarter size frame + //_renderTargetSize /= 2; _cullingProjection = _eyeProjections[0]; // This must come after the initialization, so that the values calculated // above are available during the customizeContext call (when not running diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 32c7eb29dd..41e4f43a5d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -3425,7 +3425,19 @@ void EntityItem::addGrab(GrabPointer grab) { enableNoBootstrap(); SpatiallyNestable::addGrab(grab); - if (getDynamic() && getParentID().isNull()) { + if (!getParentID().isNull()) { + return; + } + + int jointIndex = grab->getParentJointIndex(); + bool isFarGrab = jointIndex == FARGRAB_RIGHTHAND_INDEX + || jointIndex == FARGRAB_LEFTHAND_INDEX + || jointIndex == FARGRAB_MOUSE_INDEX; + + // GRAB HACK: FarGrab doesn't work on non-dynamic things yet, but we really really want NearGrab + // (aka Hold) to work for such ojects, hence we filter the useAction case like so: + bool useAction = getDynamic() || (_physicsInfo && !isFarGrab); + if (useAction) { EntityTreePointer entityTree = getTree(); assert(entityTree); EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; @@ -3436,13 +3448,11 @@ void EntityItem::addGrab(GrabPointer grab) { EntityDynamicType dynamicType; QVariantMap arguments; - int grabParentJointIndex =grab->getParentJointIndex(); - if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX || - grabParentJointIndex == FARGRAB_MOUSE_INDEX) { + if (isFarGrab) { // add a far-grab action dynamicType = DYNAMIC_TYPE_FAR_GRAB; arguments["otherID"] = grab->getOwnerID(); - arguments["otherJointIndex"] = grabParentJointIndex; + arguments["otherJointIndex"] = jointIndex; arguments["targetPosition"] = vec3ToQMap(grab->getPositionalOffset()); arguments["targetRotation"] = quatToQMap(grab->getRotationalOffset()); arguments["linearTimeScale"] = 0.05; @@ -3463,11 +3473,23 @@ void EntityItem::addGrab(GrabPointer grab) { grab->setActionID(actionID); _grabActions[actionID] = action; simulation->addDynamic(action); + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); + simulation->changeEntity(getThisPointer()); } } void EntityItem::removeGrab(GrabPointer grab) { + int oldNumGrabs = _grabs.size(); SpatiallyNestable::removeGrab(grab); + if (!getDynamic() && _grabs.size() != oldNumGrabs) { + // GRAB HACK: the expected behavior is for non-dynamic grabbed things to NOT be throwable + // so we slam the velocities to zero here whenever the number of grabs change. + // NOTE: if there is still another grab in effect this shouldn't interfere with the object's motion + // because that grab will slam the position+velocities next frame. + setLocalVelocity(glm::vec3(0.0f)); + setAngularVelocity(glm::vec3(0.0f)); + } + markDirtyFlags(Simulation::DIRTY_MOTION_TYPE); QUuid actionID = grab->getActionID(); if (!actionID.isNull()) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index bd444d28dd..f574234354 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -96,11 +96,11 @@ const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128; const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; -const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = 255; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = PERSONAL_SIMULATION_PRIORITY; class SimulationOwner { diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 07102253e4..38465d2809 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1480,9 +1480,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - extracted.mesh.createMeshTangents(generateTangents); - extracted.mesh.createBlendShapeTangents(generateTangents); - // find the clusters with which the mesh is associated QVector clusterIDs; foreach (const QString& childID, _connectionChildMap.values(it.key())) { diff --git a/libraries/gl/src/gl/Config.cpp b/libraries/gl/src/gl/Config.cpp index 0e3a174e91..94bb91a3e9 100644 --- a/libraries/gl/src/gl/Config.cpp +++ b/libraries/gl/src/gl/Config.cpp @@ -118,6 +118,8 @@ void gl::setSwapInterval(int interval) { wglSwapIntervalEXT(interval); #elif defined(Q_OS_MAC) CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#elif defined(Q_OS_ANDROID) + eglSwapInterval(eglGetCurrentDisplay(), interval); #else Q_UNUSED(interval); #endif diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 371e6454e6..7d27b42909 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -34,22 +34,9 @@ bool Context::USE_CUSTOM_CONTEXT { true }; #endif bool Context::enableDebugLogger() { -#if defined(Q_OS_MAC) - // OSX does not support GL_KHR_debug or GL_ARB_debug_output - return false; -#else -#if defined(DEBUG) || defined(USE_GLES) - static bool enableDebugLogger = true; -#else - static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); - static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -#endif - return enableDebugLogger; -#endif + return gl::debugContextEnabled(); } - - std::atomic Context::_totalSwapchainMemoryUsage { 0 }; size_t Context::getSwapchainMemoryUsage() { return _totalSwapchainMemoryUsage.load(); } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index b6b6ad3596..82724dfd62 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -52,6 +52,11 @@ void Context::moveToThread(QThread* thread) { } void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { + auto type = debugMessage.type(); + if (type == QOpenGLDebugMessage::PerformanceType) { + return; + } + auto severity = debugMessage.severity(); switch (severity) { case QOpenGLDebugMessage::NotificationSeverity: @@ -60,13 +65,13 @@ void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { default: break; } - qDebug(glLogging) << debugMessage; + qWarning(glLogging) << debugMessage; return; } void Context::setupDebugLogging(QOpenGLContext *context) { QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context); - QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){ + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, context, [](const QOpenGLDebugMessage& message){ Context::debugMessageHandler(message); }); if (logger->initialize()) { diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 15a41c3dc1..2c02fdca03 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -198,11 +198,48 @@ namespace gl { bool checkGLErrorDebug(const char* name) { -#ifdef DEBUG + // Disabling error checking macro on Android debug builds for now, + // as it throws off performance testing, which must be done on + // Debug builds +#if defined(DEBUG) && !defined(Q_OS_ANDROID) return checkGLError(name); #else Q_UNUSED(name); return false; #endif } + + // Enables annotation of captures made by tools like renderdoc + bool khrDebugEnabled() { + static std::once_flag once; + static bool khrDebug = false; + std::call_once(once, [&] { + khrDebug = nullptr != glPushDebugGroupKHR; + }); + return khrDebug; + } + + // Enables annotation of captures made by tools like renderdoc + bool extDebugMarkerEnabled() { + static std::once_flag once; + static bool extMarker = false; + std::call_once(once, [&] { + extMarker = nullptr != glPushGroupMarkerEXT; + }); + return extMarker; + } + + bool debugContextEnabled() { +#if defined(Q_OS_MAC) + // OSX does not support GL_KHR_debug or GL_ARB_debug_output + static bool enableDebugLogger = false; +#elif defined(DEBUG) || defined(USE_GLES) + //static bool enableDebugLogger = true; + static bool enableDebugLogger = false; +#else + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); + static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); +#endif + return enableDebugLogger; + } } diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 1865442ef6..2a798e6e98 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -37,7 +37,13 @@ bool isRenderThread(); namespace gl { void globalLock(); void globalRelease(bool finish = true); - + + bool debugContextEnabled(); + + bool khrDebugEnabled(); + + bool extDebugMarkerEnabled(); + void withSavedContext(const std::function& f); bool checkGLError(const char* name); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 295b9becb8..82f4f97e3b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -392,8 +392,38 @@ void GLBackend::renderPassDraw(const Batch& batch) { } } +// Support annotating captures in tools like Renderdoc +class GlDuration { +public: +#ifdef USE_GLES + GlDuration(const char* name) { + // We need to use strlen here instead of -1, because the Snapdragon profiler + // will crash otherwise + glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, strlen(name), name); + } + ~GlDuration() { + glPopDebugGroup(); + } +#else + GlDuration(const char* name) { + if (::gl::khrDebugEnabled()) { + glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 0, -1, name); + } + } + ~GlDuration() { + if (::gl::khrDebugEnabled()) { + glPopDebugGroupKHR(); + } + } +#endif +}; + +#define GL_PROFILE_RANGE(category, name) \ + PROFILE_RANGE(category, name); \ + GlDuration glProfileRangeThis(name); + void GLBackend::render(const Batch& batch) { - PROFILE_RANGE(render_gpu_gl, batch.getName()); + GL_PROFILE_RANGE(render_gpu_gl, batch.getName().c_str()); _transform._skybox = _stereo._skybox = batch.isSkyboxEnabled(); // Allow the batch to override the rendering stereo settings @@ -406,7 +436,7 @@ void GLBackend::render(const Batch& batch) { _transform._projectionJitter = Vec2(0.0f, 0.0f); { - PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); + GL_PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); renderPassTransfer(batch); } @@ -416,7 +446,7 @@ void GLBackend::render(const Batch& batch) { } #endif { - PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render"); + GL_PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render"); renderPassDraw(batch); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 07f7df9277..b5a279a54c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -54,153 +54,8 @@ #define GPU_STEREO_CAMERA_BUFFER #endif -// -// GL Backend pointer storage mechanism -// One of the following three defines must be defined. -// GPU_POINTER_STORAGE_SHARED - -// The platonic ideal, use references to smart pointers. -// However, this produces artifacts because there are too many places in the code right now that -// create temporary values (undesirable smart pointer duplications) and then those temp variables -// get passed on and have their reference taken, and then invalidated -// GPU_POINTER_STORAGE_REF - -// Raw pointer manipulation. Seems more dangerous than the reference wrappers, -// but in practice, the danger of grabbing a reference to a temporary variable -// is causing issues -// GPU_POINTER_STORAGE_RAW - -#if defined(USE_GLES) -#define GPU_POINTER_STORAGE_SHARED -#else -#define GPU_POINTER_STORAGE_RAW -#endif - namespace gpu { namespace gl { -#if defined(GPU_POINTER_STORAGE_SHARED) -template -static inline bool compare(const std::shared_ptr& a, const std::shared_ptr& b) { - return a == b; -} - -template -static inline T* acquire(const std::shared_ptr& pointer) { - return pointer.get(); -} - -template -static inline void reset(std::shared_ptr& pointer) { - return pointer.reset(); -} - -template -static inline bool valid(const std::shared_ptr& pointer) { - return pointer.operator bool(); -} - -template -static inline void assign(std::shared_ptr& pointer, const std::shared_ptr& source) { - pointer = source; -} - -using BufferReference = BufferPointer; -using TextureReference = TexturePointer; -using FramebufferReference = FramebufferPointer; -using FormatReference = Stream::FormatPointer; -using PipelineReference = PipelinePointer; - -#define GPU_REFERENCE_INIT_VALUE nullptr - -#elif defined(GPU_POINTER_STORAGE_REF) - -template -class PointerReferenceWrapper : public std::reference_wrapper> { - using Parent = std::reference_wrapper>; - -public: - using Pointer = std::shared_ptr; - PointerReferenceWrapper() : Parent(EMPTY()) {} - PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {} - void clear() { *this = EMPTY(); } - -private: - static const Pointer& EMPTY() { - static const Pointer EMPTY_VALUE; - return EMPTY_VALUE; - }; -}; - -template -static bool compare(const PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { - return reference.get() == pointer; -} - -template -static inline T* acquire(const PointerReferenceWrapper& reference) { - return reference.get().get(); -} - -template -static void assign(PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { - reference = pointer; -} - -template -static bool valid(const PointerReferenceWrapper& reference) { - return reference.get().operator bool(); -} - -template -static inline void reset(PointerReferenceWrapper& reference) { - return reference.clear(); -} - -using BufferReference = PointerReferenceWrapper; -using TextureReference = PointerReferenceWrapper; -using FramebufferReference = PointerReferenceWrapper; -using FormatReference = PointerReferenceWrapper; -using PipelineReference = PointerReferenceWrapper; - -#define GPU_REFERENCE_INIT_VALUE - -#elif defined(GPU_POINTER_STORAGE_RAW) - -template -static bool compare(const T* const& rawPointer, const std::shared_ptr& pointer) { - return rawPointer == pointer.get(); -} - -template -static inline T* acquire(T*& rawPointer) { - return rawPointer; -} - -template -static inline bool valid(const T* const& rawPointer) { - return rawPointer; -} - -template -static inline void reset(T*& rawPointer) { - rawPointer = nullptr; -} - -template -static inline void assign(T*& rawPointer, const std::shared_ptr& pointer) { - rawPointer = pointer.get(); -} - -using BufferReference = Buffer*; -using TextureReference = Texture*; -using FramebufferReference = Framebuffer*; -using FormatReference = Stream::Format*; -using PipelineReference = Pipeline*; - -#define GPU_REFERENCE_INIT_VALUE nullptr - -#endif - class GLBackend : public Backend, public std::enable_shared_from_this { // Context Backend static interface required friend class gpu::Context; @@ -583,13 +438,13 @@ protected: BufferState& operator=(const BufferState& other) = delete; void reset() { - gpu::gl::reset(buffer); + gpu::reset(buffer); offset = 0; size = 0; } bool compare(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) { const auto& self = *this; - return (self.offset == offset && self.size == size && gpu::gl::compare(self.buffer, buffer)); + return (self.offset == offset && self.size == size && gpu::compare(self.buffer, buffer)); } }; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index 411e16b18e..a10d6c96c4 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -73,7 +73,7 @@ void GLBackend::do_advance(const Batch& batch, size_t paramOffset) { } void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { - if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) { + if (_stereo.isStereo() && !_pipeline._stateCache.flags.scissorEnable) { qWarning("Clear without scissor in stereo mode"); } @@ -140,7 +140,7 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { } // Apply scissor if needed and if not already on - bool doEnableScissor = (useScissor && (!_pipeline._stateCache.scissorEnable)); + bool doEnableScissor = (useScissor && (!_pipeline._stateCache.flags.scissorEnable)); if (doEnableScissor) { glEnable(GL_SCISSOR_TEST); } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp index 8363af9b00..5da32f39df 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendState.cpp @@ -100,17 +100,17 @@ void GLBackend::do_setStateCullMode(int32 mode) { } void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { - if (_pipeline._stateCache.frontFaceClockwise != isClockwise) { + if (_pipeline._stateCache.flags.frontFaceClockwise != isClockwise) { static GLenum GL_FRONT_FACES[] = { GL_CCW, GL_CW }; glFrontFace(GL_FRONT_FACES[isClockwise]); (void)CHECK_GL_ERROR(); - _pipeline._stateCache.frontFaceClockwise = isClockwise; + _pipeline._stateCache.flags.frontFaceClockwise = isClockwise; } } void GLBackend::do_setStateDepthClampEnable(bool enable) { - if (_pipeline._stateCache.depthClampEnable != enable) { + if (_pipeline._stateCache.flags.depthClampEnable != enable) { #if !defined(USE_GLES) if (enable) { glEnable(GL_DEPTH_CLAMP); @@ -118,13 +118,13 @@ void GLBackend::do_setStateDepthClampEnable(bool enable) { glDisable(GL_DEPTH_CLAMP); } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.depthClampEnable = enable; + _pipeline._stateCache.flags.depthClampEnable = enable; #endif } } void GLBackend::do_setStateScissorEnable(bool enable) { - if (_pipeline._stateCache.scissorEnable != enable) { + if (_pipeline._stateCache.flags.scissorEnable != enable) { if (enable) { glEnable(GL_SCISSOR_TEST); } else { @@ -132,12 +132,12 @@ void GLBackend::do_setStateScissorEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.scissorEnable = enable; + _pipeline._stateCache.flags.scissorEnable = enable; } } void GLBackend::do_setStateMultisampleEnable(bool enable) { - if (_pipeline._stateCache.multisampleEnable != enable) { + if (_pipeline._stateCache.flags.multisampleEnable != enable) { #if !defined(USE_GLES) if (enable) { glEnable(GL_MULTISAMPLE); @@ -146,13 +146,13 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.multisampleEnable = enable; + _pipeline._stateCache.flags.multisampleEnable = enable; #endif } } void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { - if (_pipeline._stateCache.antialisedLineEnable != enable) { + if (_pipeline._stateCache.flags.antialisedLineEnable != enable) { #if !defined(USE_GLES) if (enable) { glEnable(GL_LINE_SMOOTH); @@ -161,7 +161,7 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.antialisedLineEnable = enable; + _pipeline._stateCache.flags.antialisedLineEnable = enable; #endif } } @@ -206,7 +206,7 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { if (CHECK_GL_ERROR()) { qCDebug(gpulogging) << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") - << "Func=" << test.getFunction() + << "Func=" << (uint16_t)test.getFunction() << "Raw=" << test.getRaw(); } _pipeline._stateCache.depthTest = test; @@ -264,7 +264,7 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S } void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { - if (_pipeline._stateCache.alphaToCoverageEnable != enable) { + if (_pipeline._stateCache.flags.alphaToCoverageEnable != enable) { if (enable) { glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); } else { @@ -272,7 +272,7 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { } (void)CHECK_GL_ERROR(); - _pipeline._stateCache.alphaToCoverageEnable = enable; + _pipeline._stateCache.flags.alphaToCoverageEnable = enable; } } @@ -317,7 +317,7 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { mask & State::ColorMask::WRITE_ALPHA); (void)CHECK_GL_ERROR(); - _pipeline._stateCache.colorWriteMask = mask; + _pipeline._stateCache.colorWriteMask = (State::ColorMask)mask; } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp index 9ba85b8418..995feaa7a4 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLShared.cpp @@ -174,17 +174,17 @@ void getCurrentGLState(State::Data& state) { { GLint winding; glGetIntegerv(GL_FRONT_FACE, &winding); - state.frontFaceClockwise = (winding == GL_CW); + state.flags.frontFaceClockwise = (winding == GL_CW); #if defined(USE_GLES) - state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE_EXT); - state.antialisedLineEnable = false; - state.depthClampEnable = false; + state.flags.multisampleEnable = glIsEnabled(GL_MULTISAMPLE_EXT); + state.flags.antialisedLineEnable = false; + state.flags.depthClampEnable = false; #else - state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); - state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); - state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); + state.flags.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); + state.flags.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); + state.flags.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); #endif - state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); + state.flags.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); } { if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { @@ -247,7 +247,7 @@ void getCurrentGLState(State::Data& state) { state.sampleMask = mask; } { - state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); + state.flags.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); } { GLboolean isEnabled = glIsEnabled(GL_BLEND); @@ -272,10 +272,10 @@ void getCurrentGLState(State::Data& state) { { GLboolean mask[4]; glGetBooleanv(GL_COLOR_WRITEMASK, mask); - state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) + state.colorWriteMask = (State::ColorMask)((mask[0] ? State::WRITE_RED : 0) | (mask[1] ? State::WRITE_GREEN : 0) | (mask[2] ? State::WRITE_BLUE : 0) - | (mask[3] ? State::WRITE_ALPHA : 0); + | (mask[3] ? State::WRITE_ALPHA : 0)); } (void)CHECK_GL_ERROR(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp index b6d917b928..89ba80031b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLState.cpp @@ -56,11 +56,11 @@ const GLState::Commands makeResetStateCommands() { return { std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), - std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), - std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), - std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), - std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), - std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), + std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.flags.frontFaceClockwise), + std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.flags.depthClampEnable), + std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.flags.scissorEnable), + std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.flags.multisampleEnable), + std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.flags.antialisedLineEnable), // Depth bias has 2 fields in State but really one call in GLBackend CommandPointer(depthBiasCommand), @@ -75,7 +75,7 @@ const GLState::Commands makeResetStateCommands() { std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), + std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.flags.alphaToCoverageEnable), std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 7573cc5473..86a307fe17 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -4,3 +4,4 @@ setup_hifi_library() link_hifi_libraries(shared ktx shaders) target_nsight() +target_json() diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index cd7c7c881e..e6217cc600 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -45,7 +45,7 @@ size_t Batch::_dataMax{ BATCH_PREALLOCATE_MIN }; size_t Batch::_objectsMax{ BATCH_PREALLOCATE_MIN }; size_t Batch::_drawCallInfosMax{ BATCH_PREALLOCATE_MIN }; -Batch::Batch(const char* name) { +Batch::Batch(const std::string& name) { _name = name; _commands.reserve(_commandsMax); _commandOffsets.reserve(_commandOffsetsMax); @@ -64,7 +64,7 @@ Batch::~Batch() { _drawCallInfosMax = std::max(_drawCallInfos.size(), _drawCallInfosMax); } -void Batch::setName(const char* name) { +void Batch::setName(const std::string& name) { _name = name; } @@ -96,7 +96,7 @@ void Batch::clear() { _textureTables.clear(); _transforms.clear(); - _name = nullptr; + _name.clear(); _invalidModel = true; _currentModel = Transform(); _drawcallUniform = 0; diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 96a45d3111..0a438ea148 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -89,14 +89,14 @@ public: void captureDrawCallInfo(); void captureNamedDrawCallInfo(std::string name); - Batch(const char* name = nullptr); + Batch(const std::string& name = ""); // Disallow copy construction and assignement of batches Batch(const Batch& batch) = delete; Batch& operator=(const Batch& batch) = delete; ~Batch(); - void setName(const char* name); - const char* getName() const { return _name; } + void setName(const std::string& name); + const std::string& getName() const { return _name; } void clear(); // Batches may need to override the context level stereo settings @@ -440,6 +440,18 @@ public: }; }; + using CommandHandler = std::function; + + void forEachCommand(const CommandHandler& handler) const { + size_t count = _commands.size(); + for (size_t i = 0; i < count; ++i) { + const auto command = _commands[i]; + const auto offset = _commandOffsets[i]; + const Param* params = _params.data() + offset; + handler(command, params); + } + } + typedef Cache::Vector BufferCaches; typedef Cache::Vector TextureCaches; typedef Cache::Vector TextureTableCaches; @@ -519,7 +531,7 @@ public: bool _enableSkybox { false }; protected: - const char* _name; + std::string _name; friend class Context; friend class Frame; diff --git a/libraries/gpu/src/gpu/Buffer.h b/libraries/gpu/src/gpu/Buffer.h index 01cc652fd1..49eed80b54 100644 --- a/libraries/gpu/src/gpu/Buffer.h +++ b/libraries/gpu/src/gpu/Buffer.h @@ -149,7 +149,8 @@ protected: Size _end{ 0 }; Sysmem _sysmem; - + friend class Serializer; + friend class Deserializer; friend class BufferView; friend class Frame; friend class Batch; diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 610c3d1308..45ee4263a3 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -47,10 +47,7 @@ Context::Context(const Context& context) { } Context::~Context() { - for (auto batch : _batchPool) { - delete batch; - } - _batchPool.clear(); + clearBatches(); _syncedPrograms.clear(); } @@ -97,6 +94,12 @@ FramePointer Context::endFrame() { return result; } +void Context::executeBatch(const char* name, std::function lambda) const { + auto batch = acquireBatch(name); + lambda(*batch); + executeBatch(*batch); +} + void Context::executeBatch(Batch& batch) const { PROFILE_RANGE(render_gpu, __FUNCTION__); batch.flush(); @@ -117,28 +120,27 @@ void Context::executeFrame(const FramePointer& frame) const { PROFILE_RANGE(render_gpu, __FUNCTION__); // Grab the stats at the around the frame and delta to have a consistent sampling - ContextStats beginStats; + static ContextStats beginStats; getStats(beginStats); // FIXME? probably not necessary, but safe consumeFrameUpdates(frame); _backend->setStereoState(frame->stereoState); - { - Batch beginBatch("Context::executeFrame::begin"); - _frameRangeTimer->begin(beginBatch); - _backend->render(beginBatch); - // Execute the frame rendering commands - for (auto& batch : frame->batches) { - _backend->render(*batch); - } - - Batch endBatch("Context::executeFrame::end"); - _frameRangeTimer->end(endBatch); - _backend->render(endBatch); + executeBatch("Context::executeFrame::begin", [&](Batch& batch){ + batch.pushProfileRange("Frame"); + _frameRangeTimer->begin(batch); + }); + // Execute the frame rendering commands + for (auto& batch : frame->batches) { + _backend->render(*batch); } + executeBatch("Context::executeFrame::end", [&](Batch& batch){ + batch.popProfileRange(); + _frameRangeTimer->end(batch); + }); - ContextStats endStats; + static ContextStats endStats; getStats(endStats); _frameStats.evalDelta(beginStats, endStats); } @@ -381,6 +383,16 @@ void Context::processProgramsToSync() { } } +std::mutex Context::_batchPoolMutex; +std::list Context::_batchPool; + +void Context::clearBatches() { + for (auto batch : _batchPool) { + delete batch; + } + _batchPool.clear(); +} + BatchPointer Context::acquireBatch(const char* name) { Batch* rawBatch = nullptr; { @@ -393,8 +405,10 @@ BatchPointer Context::acquireBatch(const char* name) { if (!rawBatch) { rawBatch = new Batch(); } - rawBatch->setName(name); - return BatchPointer(rawBatch, [this](Batch* batch) { releaseBatch(batch); }); + if (name) { + rawBatch->setName(name); + } + return BatchPointer(rawBatch, [](Batch* batch) { releaseBatch(batch); }); } void Context::releaseBatch(Batch* batch) { @@ -406,7 +420,7 @@ void Context::releaseBatch(Batch* batch) { void gpu::doInBatch(const char* name, const std::shared_ptr& context, const std::function& f) { - auto batch = context->acquireBatch(name); + auto batch = Context::acquireBatch(name); f(*batch); context->appendFrameBatch(batch); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 0626ecd8f3..b080b0ceac 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -24,6 +24,7 @@ #include "Pipeline.h" #include "Framebuffer.h" #include "Frame.h" +#include "PointerStorage.h" class QImage; @@ -162,8 +163,8 @@ public: void appendFrameBatch(const BatchPointer& batch); FramePointer endFrame(); - BatchPointer acquireBatch(const char* name = nullptr); - void releaseBatch(Batch* batch); + static BatchPointer acquireBatch(const char* name = nullptr); + static void releaseBatch(Batch* batch); // MUST only be called on the rendering thread // @@ -175,6 +176,11 @@ public: // Execute a batch immediately, rather than as part of a frame void executeBatch(Batch& batch) const; + // MUST only be called on the rendering thread + // + // Execute a batch immediately, rather than as part of a frame + void executeBatch(const char* name, std::function lambda) const; + // MUST only be called on the rendering thread // // Executes a frame, applying any updates contained in the frame batches to the rendering @@ -267,8 +273,6 @@ protected: Context(const Context& context); std::shared_ptr _backend; - std::mutex _batchPoolMutex; - std::list _batchPool; bool _frameActive{ false }; FramePointer _currentFrame; RangeTimerPointer _frameRangeTimer; @@ -285,6 +289,11 @@ protected: static CreateBackend _createBackendCallback; static std::once_flag _initialized; + // Should probably move this functionality to Batch + static void clearBatches(); + static std::mutex _batchPoolMutex; + static std::list _batchPool; + friend class Shader; friend class Backend; }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 7a3e0a2f82..705d746a5b 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -322,7 +322,7 @@ public: uint8 getLocationScalarCount() const { return DIMENSION_SCALAR_COUNT_PER_LOCATION[(Dimension)_dimension]; } uint32 getLocationSize() const { return DIMENSION_SCALAR_COUNT_PER_LOCATION[_dimension] * TYPE_SIZE[_type]; } - uint16 getRaw() const { return *((uint16*) (this)); } + uint16 getRaw() const { return *((const uint16*) (this)); } bool operator ==(const Element& right) const { @@ -376,7 +376,7 @@ public: }; -enum ComparisonFunction { +enum ComparisonFunction : uint16 { NEVER = 0, LESS, EQUAL, diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 0592b71889..7e03816116 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -96,6 +96,7 @@ namespace gpu { using TextureTablePointer = std::shared_ptr; struct StereoState { + StereoState() {} bool isStereo() const { return _enable && !_contextDisable; } @@ -108,6 +109,9 @@ namespace gpu { Mat4 _eyeProjections[2]; }; + class Serializer; + class Deserializer; + class GPUObject { public: virtual ~GPUObject() = default; diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index a3a5d3489c..3787ebfacd 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -42,6 +42,8 @@ namespace gpu { FramebufferRecycler framebufferRecycler; protected: + friend class Deserializer; + // Should be called once per frame, on the recording thred void finish(); void preRender(); diff --git a/libraries/gpu/src/gpu/FrameIO.h b/libraries/gpu/src/gpu/FrameIO.h new file mode 100644 index 0000000000..4de4cf5fe7 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameIO.h @@ -0,0 +1,29 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_gpu_FrameIO_h +#define hifi_gpu_FrameIO_h + +#include "Forward.h" +#include "Format.h" + +#include + +namespace gpu { + +using TextureCapturer = std::function; +using TextureLoader = std::function; +void writeFrame(const std::string& filename, const FramePointer& frame, const TextureCapturer& capturer = nullptr); +FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader = nullptr); + +using IndexOptimizer = std::function; +void optimizeFrame(const std::string& filename, const IndexOptimizer& optimizer); + +} // namespace gpu + +#endif diff --git a/libraries/gpu/src/gpu/FrameIOKeys.h b/libraries/gpu/src/gpu/FrameIOKeys.h new file mode 100644 index 0000000000..5d5f8a5bd9 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameIOKeys.h @@ -0,0 +1,214 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// Copyright 2013-2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_gpu_FrameIOKeys_h +#define hifi_gpu_FrameIOKeys_h + +namespace gpu { namespace keys { + +static const char* binary = "binary"; +static const char* L00 = "L00"; +static const char* L1m1 = "L1m1"; +static const char* L10 = "L10"; +static const char* L11 = "L11"; +static const char* L2m2 = "L2m2"; +static const char* L2m1 = "L2m1"; +static const char* L20 = "L20"; +static const char* L21 = "L21"; +static const char* L22 = "L22"; + +static const char* eyeProjections = "eyeProjections"; +static const char* eyeViews = "eyeViews"; +static const char* alphaToCoverageEnable = "alphaToCoverageEnable"; +static const char* antialisedLineEnable = "antialisedLineEnable"; +static const char* attributes = "attributes"; +static const char* batches = "batches"; +static const char* blendFunction = "blendFunction"; +static const char* borderColor = "borderColor"; +static const char* bufferMask = "bufferMask"; +static const char* buffers = "buffers"; +static const char* capturedTextures = "capturedTextures"; +static const char* channel = "channel"; +static const char* colorAttachments = "colorAttachments"; +static const char* colorWriteMask = "colorWriteMask"; +static const char* commands = "commands"; +static const char* comparisonFunction = "comparisonFunction"; +static const char* cullMode = "cullMode"; +static const char* data = "data"; +static const char* depth = "depth"; +static const char* depthBias = "depthBias"; +static const char* depthBiasSlopeScale = "depthBiasSlopeScale"; +static const char* depthClampEnable = "depthClampEnable"; +static const char* depthStencilAttachment = "depthStencilAttachment"; +static const char* depthTest = "depthTest"; +static const char* drawCallInfos = "drawCallInfos"; +static const char* drawcallUniform = "drawcallUniform"; +static const char* drawcallUniformReset = "drawcallUniformReset"; +static const char* element = "element"; +static const char* fillMode = "fillMode"; +static const char* filter = "filter"; +static const char* formats = "formats"; +static const char* frameIndex = "frameIndex"; +static const char* framebuffer = "framebuffer"; +static const char* framebuffers = "framebuffers"; +static const char* frequency = "frequency"; +static const char* frontFaceClockwise = "frontFaceClockwise"; +static const char* height = "height"; +static const char* id = "id"; +static const char* ktxFile = "ktxFile"; +static const char* layers = "layers"; +static const char* maxAnisotropy = "maxAnisotropy"; +static const char* maxMip = "maxMip"; +static const char* minMip = "minMip"; +static const char* mipOffset = "mipOffset"; +static const char* mips = "mips"; +static const char* multisampleEnable = "multisampleEnable"; +static const char* name = "name"; +static const char* namedData = "namedData"; +static const char* names = "names"; +static const char* objects = "objects"; +static const char* offset = "offset"; +static const char* pipelines = "pipelines"; +static const char* pose = "pose"; +static const char* profileRanges = "profileRanges"; +static const char* program = "program"; +static const char* programs = "programs"; +static const char* projectionJitter = "projectionJitter"; +static const char* queries = "queries"; +static const char* sampleCount = "sampleCount"; +static const char* sampleMask = "sampleMask"; +static const char* sampler = "sampler"; +static const char* samples = "samples"; +static const char* scissorEnable = "scissorEnable"; +static const char* shaders = "shaders"; +static const char* size = "size"; +static const char* skybox = "skybox"; +static const char* slot = "slot"; +static const char* source = "source"; +static const char* state = "state"; +static const char* stencilActivation = "stencilActivation"; +static const char* stencilTestBack = "stencilTestBack"; +static const char* stencilTestFront = "stencilTestFront"; +static const char* stereo = "stereo"; +static const char* subresource = "subresource"; +static const char* swapchains = "swapchains"; +static const char* texelFormat = "texelFormat"; +static const char* texture = "texture"; +static const char* textureTables = "textureTables"; +static const char* textures = "textures"; +static const char* transforms = "transforms"; +static const char* type = "type"; +static const char* usageType = "usageType"; +static const char* view = "view"; +static const char* width = "width"; +static const char* wrapModeU = "wrapModeU"; +static const char* wrapModeV = "wrapModeV"; +static const char* wrapModeW = "wrapModeW"; + + +static const char* backWriteMask = "backWriteMask"; +static const char* frontWriteMask = "frontWriteMask"; +static const char* reference = "reference"; +static const char* readMask = "readMask"; +static const char* failOp = "failOp"; +static const char* depthFailOp = "depthFailOp"; +static const char* passOp = "passOp"; +static const char* enabled = "enabled"; +static const char* blend = "blend"; +static const char* flags = "flags"; +static const char* writeMask = "writeMask"; +static const char* function = "function"; +static const char* sourceColor = "sourceColor"; +static const char* sourceAlpha = "sourceAlpha"; +static const char* destColor = "destColor"; +static const char* destAlpha = "destAlpha"; +static const char* opColor = "opColor"; +static const char* opAlpha = "opAlpha"; +static const char* enable = "enable"; +static const char* contextDisable = "contextDisable"; + +static const char* COMMAND_NAMES[] = { + "draw", + "drawIndexed", + "drawInstanced", + "drawIndexedInstanced", + "multiDrawIndirect", + "multiDrawIndexedIndirect", + + "setInputFormat", + "setInputBuffer", + "setIndexBuffer", + "setIndirectBuffer", + + "setModelTransform", + "setViewTransform", + "setProjectionTransform", + "setProjectionJitter", + "setViewportTransform", + "setDepthRangeTransform", + + "setPipeline", + "setStateBlendFactor", + "setStateScissorRect", + + "setUniformBuffer", + "setResourceBuffer", + "setResourceTexture", + "setResourceTextureTable", + "setResourceFramebufferSwapChainTexture", + + "setFramebuffer", + "setFramebufferSwapChain", + "clearFramebuffer", + "blit", + "generateTextureMips", + "generateTextureMipsWithPipeline", + + "advance", + + "beginQuery", + "endQuery", + "getQuery", + + "resetStages", + + "disableContextViewCorrection", + "restoreContextViewCorrection", + + "disableContextStereo", + "restoreContextStereo", + + "runLambda", + + "startNamedCall", + "stopNamedCall", + + "glUniform1i", + "glUniform1f", + "glUniform2f", + "glUniform3f", + "glUniform4f", + "glUniform3fv", + "glUniform4fv", + "glUniform4iv", + "glUniformMatrix3fv", + "glUniformMatrix4fv", + + "glColor4f", + + "pushProfileRange", + "popProfileRange", +}; + +template +constexpr size_t array_size(T (&)[N]) { return N; } + +static_assert(array_size(COMMAND_NAMES) == Batch::Command::NUM_COMMANDS, "Command array sizes must match"); +}} // namespace gpu::keys + +#endif diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp new file mode 100644 index 0000000000..c63eaf51c3 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -0,0 +1,906 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// 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 "FrameIO.h" + +#include +#include + +#include +#include + +#include +#include "Frame.h" +#include "Batch.h" +#include "TextureTable.h" + + +#include "FrameIOKeys.h" + +namespace gpu { +using json = nlohmann::json; + +class Deserializer { +public: + static std::string getBaseName(const std::string& filename) { + static const std::string ext{ ".json" }; + if (std::string::npos != filename.rfind(ext)) { + return filename.substr(0, filename.size() - ext.size()); + } + return filename; + } + + Deserializer(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) : + basename(getBaseName(filename)), externalTexture(externalTexture), textureLoader(loader) {} + + const std::string basename; + std::string basedir; + std::string binaryFile; + const uint32_t externalTexture; + TextureLoader textureLoader; + std::vector shaders; + std::vector programs; + std::vector textures; + std::vector textureTables; + std::vector buffers; + std::unordered_map bufferOffsets; + std::vector formats; + std::vector pipelines; + std::vector framebuffers; + std::vector swapchains; + std::vector queries; + json frameNode; + FramePointer readFrame(); + void optimizeFrame(const IndexOptimizer& optimizer); + + FramePointer deserializeFrame(); + + + void readBuffers(const json& node); + + template + static std::vector readArray(const json& node, const std::string& name, std::function parser) { + std::vector result; + if (node.count(name)) { + const auto& sourceArrayNode = node[name]; + result.reserve(sourceArrayNode.size()); + for (const auto& sourceNode : sourceArrayNode) { + if (sourceNode.is_null()) { + result.push_back(nullptr); + continue; + } + result.push_back(parser(sourceNode)); + } + } + return result; + } + + template + static std::vector readNumericVector(const json& node) { + auto count = node.size(); + std::vector result; + result.resize(count); + for (size_t i = 0; i < count; ++i) { + result[i] = node[i]; + } + return result; + } + + template + static void readFloatArray(const json& node, float* out) { + for (size_t i = 0; i < N; ++i) { + out[i] = node[i].operator float(); + } + } + + template + static bool readOptionalTransformed(T& dest, const json& node, const std::string& name, std::function f) { + if (node.count(name)) { + dest = f(node[name]); + return true; + } + return false; + } + + template + static bool readOptionalVectorTransformed(std::vector& dest, + const json& node, + const std::string& name, + std::function f) { + if (node.count(name)) { + const auto& arrayNode = node[name]; + const auto count = arrayNode.size(); + dest.reserve(count); + for (size_t i = 0; i < count; ++i) { + dest.emplace_back(f(arrayNode[i])); + } + return true; + } + return false; + } + + template + static bool readOptionalVector(std::vector& dest, const json& node, const std::string& name) { + return readOptionalVectorTransformed(dest, node, name, [](const json& node) { return node.get(); }); + } + + template + static T defaultNodeTransform(const json& node) { + return node.get(); + } + + template + static bool readBatchCacheTransformed(typename Batch::Cache::Vector& dest, + const json& node, + const std::string& name, + std::function f = [](const json& node) -> TT { + return node.get(); + }) { + if (node.count(name)) { + const auto& arrayNode = node[name]; + for (const auto& entry : arrayNode) { + dest.cache(f(entry)); + } + return true; + } + return false; + } + + template + static bool readPointerCache(typename Batch::Cache::Vector& dest, + const json& node, + const std::string& name, + std::vector& global) { + auto transform = [&](const json& node) -> const T& { return global[node.get()]; }; + return readBatchCacheTransformed(dest, node, name, transform); + } + + template + static bool readOptional(T& dest, const json& node, const std::string& name) { + return readOptionalTransformed(dest, node, name, [](const json& child) { + T result = child; + return result; + }); + } + + SwapChainPointer readSwapchain(const json& node); + ShaderPointer readProgram(const json& node); + PipelinePointer readPipeline(const json& node); + TextureTablePointer readTextureTable(const json& node); + TextureView readTextureView(const json& node); + FramebufferPointer readFramebuffer(const json& node); + BatchPointer readBatch(const json& node); + Batch::NamedBatchData readNamedBatchData(const json& node); + + //static StatePointer readState(const json& node); + static QueryPointer readQuery(const json& node); + TexturePointer readTexture(const json& node, uint32_t externalTexture); + static ShaderPointer readShader(const json& node); + static Stream::FormatPointer readFormat(const json& node); + static Element readElement(const json& node); + static Sampler readSampler(const json& node); + static glm::mat4 readMat4(const json& node) { + glm::mat4 m; + if (!node.is_null()) { + readFloatArray<16>(node, &m[0][0]); + } + return m; + } + static glm::vec4 readVec4(const json& node) { + glm::vec4 v; + if (!node.is_null()) { + readFloatArray<4>(node, &v[0]); + } + return v; + } + static glm::vec3 readVec3(const json& node) { + glm::vec3 v; + if (!node.is_null()) { + readFloatArray<3>(node, &v[0]); + } + return v; + } + static glm::vec2 readVec2(const json& node) { + glm::vec2 v; + if (!node.is_null()) { + readFloatArray<2>(node, &v[0]); + } + return v; + } + static Transform readTransform(const json& node) { return Transform{ readMat4(node) }; } + static std::vector fromBase64(const json& node); + static void readCommand(const json& node, Batch& batch); +}; + +FramePointer readFrame(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) { + return Deserializer(filename, externalTexture, loader).readFrame(); +} + +void optimizeFrame(const std::string& filename, const IndexOptimizer& optimizer) { + return Deserializer(filename, 0, {}).optimizeFrame(optimizer); +} + +} // namespace gpu + +using namespace gpu; + +void Deserializer::readBuffers(const json& buffersNode) { + storage::FileStorage mappedFile(binaryFile.c_str()); + const auto mappedSize = mappedFile.size(); + const auto* mapped = mappedFile.data(); + size_t bufferCount = buffersNode.size(); + buffers.reserve(buffersNode.size()); + size_t offset = 0; + for (size_t i = 0; i < bufferCount; ++i) { + const auto& bufferNode = buffersNode[i]; + if (bufferNode.is_null()) { + buffers.push_back(nullptr); + continue; + } + + size_t size = bufferNode; + if (offset + size > mappedSize) { + throw std::runtime_error("read buffer error"); + } + buffers.push_back(std::make_shared(size, mapped + offset)); + bufferOffsets[buffers.back()] = offset; + offset += size; + } +} + +Element Deserializer::readElement(const json& node) { + Element result; + if (!node.is_null()) { + *((uint16*)&result) = node; + } + return result; +} + +Sampler Deserializer::readSampler(const json& node) { + Sampler result; + if (!node.is_null()) { + if (node.count(keys::borderColor)) { + result._desc._borderColor = readVec4(node[keys::borderColor]); + } + if (node.count(keys::maxAnisotropy)) { + result._desc._maxAnisotropy = node[keys::maxAnisotropy]; + } + if (node.count(keys::wrapModeU)) { + result._desc._wrapModeU = node[keys::wrapModeU]; + } + if (node.count(keys::wrapModeV)) { + result._desc._wrapModeV = node[keys::wrapModeV]; + } + if (node.count(keys::wrapModeW)) { + result._desc._wrapModeW = node[keys::wrapModeW]; + } + if (node.count(keys::filter)) { + result._desc._filter = node[keys::filter]; + } + if (node.count(keys::comparisonFunction)) { + result._desc._comparisonFunc = node[keys::comparisonFunction]; + } + if (node.count(keys::minMip)) { + result._desc._minMip = node[keys::minMip]; + } + if (node.count(keys::maxMip)) { + result._desc._maxMip = node[keys::maxMip]; + } + if (node.count(keys::mipOffset)) { + result._desc._mipOffset = node[keys::mipOffset]; + } + } + return result; +} + +TexturePointer Deserializer::readTexture(const json& node, uint32_t external) { + if (node.is_null()) { + return nullptr; + } + + TextureUsageType usageType = node[keys::usageType]; + Texture::Type type = node[keys::type]; + glm::u16vec4 dims; + dims.x = node[keys::width]; + dims.y = node[keys::height]; + dims.z = node[keys::depth]; + dims.w = node[keys::layers]; + uint16 mips = node[keys::mips]; + uint16 samples = node[keys::samples]; + Element texelFormat = readElement(node[keys::texelFormat]); + Sampler sampler; + readOptionalTransformed(sampler, node, keys::sampler, [](const json& node) { return readSampler(node); }); + TexturePointer result; + if (usageType == TextureUsageType::EXTERNAL) { + result = Texture::createExternal([](uint32_t, void*) {}); + result->setExternalTexture(external, nullptr); + } else { + result = Texture::create(usageType, type, texelFormat, dims.x, dims.y, dims.z, samples, dims.w, mips, sampler); + } + + auto& texture = *result; + readOptional(texture._source, node, keys::source); + + std::string ktxFile; + readOptional(ktxFile, node, keys::ktxFile); + if (!ktxFile.empty()) { + if (QFileInfo(ktxFile.c_str()).isRelative()) { + ktxFile = basedir + "/" + ktxFile; + } + texture.setKtxBacking(ktxFile); + } + return result; +} + +SwapChainPointer Deserializer::readSwapchain(const json& node) { + if (node.is_null()) { + return nullptr; + } + + uint8_t swapChainSize = node[keys::size]; + std::vector swapChainFramebuffers; + const auto& framebuffersNode = node[keys::framebuffers]; + swapChainFramebuffers.resize(swapChainSize); + for (uint8_t i = 0; i < swapChainSize; ++i) { + auto index = framebuffersNode[i].get(); + swapChainFramebuffers[i] = framebuffers[index]; + } + return std::make_shared(swapChainFramebuffers); +} + +ShaderPointer Deserializer::readShader(const json& node) { + if (node.is_null()) { + return nullptr; + } + + // FIXME support procedural shaders + Shader::Type type = node[keys::type]; + uint32_t id = node[keys::id]; + ShaderPointer result; + switch (type) { + //case Shader::Type::GEOMETRY: + // result = Shader::createGeometry(id); + // break; + case Shader::Type::VERTEX: + result = Shader::createVertex(id); + break; + case Shader::Type::FRAGMENT: + result = Shader::createPixel(id); + break; + default: + throw std::runtime_error("not implemented"); + } + return result; +} + +ShaderPointer Deserializer::readProgram(const json& node) { + if (node.is_null()) { + return nullptr; + } + + std::vector programShaders; + programShaders.reserve(node.size()); + for (const auto& shaderRef : node) { + uint32_t shaderIndex = shaderRef; + programShaders.push_back(this->shaders[shaderIndex]); + } + + // FIXME account for geometry and compute shaders? + return Shader::createProgram(programShaders[0], programShaders[1]); +} + +static State::Flags readStateFlags(const json& node) { + State::Flags result; + // Hacky implementation because you can't pass boolean bitfields as references + bool value; + if (Deserializer::readOptional(value, node, keys::alphaToCoverageEnable)) { + result.alphaToCoverageEnable = value; + } + if (Deserializer::readOptional(value, node, keys::frontFaceClockwise)) { + result.frontFaceClockwise = value; + } + if (Deserializer::readOptional(value, node, keys::depthClampEnable)) { + result.depthClampEnable = value; + } + if (Deserializer::readOptional(value, node, keys::scissorEnable)) { + result.scissorEnable = value; + } + if (Deserializer::readOptional(value, node, keys::multisampleEnable)) { + result.multisampleEnable = value; + } + if (Deserializer::readOptional(value, node, keys::antialisedLineEnable)) { + result.antialisedLineEnable = value; + } + if (Deserializer::readOptional(value, node, keys::alphaToCoverageEnable)) { + result.alphaToCoverageEnable = value; + } + return result; +} + +static State::BlendFunction readBlendFunction(const json& node) { + State::BlendFunction result; + uint16 enabled; + State::BlendArg blendArg; + State::BlendOp blendOp; + if (Deserializer::readOptional(enabled, node, keys::enabled)) { + result.enabled = enabled; + } + if (Deserializer::readOptional(blendArg, node, keys::sourceColor)) { + result.sourceColor = blendArg; + } + if (Deserializer::readOptional(blendArg, node, keys::sourceAlpha)) { + result.sourceAlpha = blendArg; + } + if (Deserializer::readOptional(blendArg, node, keys::destColor)) { + result.destColor = blendArg; + } + if (Deserializer::readOptional(blendArg, node, keys::destAlpha)) { + result.destAlpha = blendArg; + } + if (Deserializer::readOptional(blendOp, node, keys::opAlpha)) { + result.opAlpha = blendOp; + } + if (Deserializer::readOptional(blendOp, node, keys::opColor)) { + result.opColor = blendOp; + } + return result; +} + +static State::DepthTest readDepthTest(const json& node) { + State::DepthTest result; + Deserializer::readOptional(result.writeMask, node, keys::writeMask); + Deserializer::readOptional(result.enabled, node, keys::enabled); + Deserializer::readOptional(result.function, node, keys::function); + return result; +} + +static State::StencilTest readStencilTest(const json& node) { + State::StencilTest result; + State::ComparisonFunction compareOp; + State::StencilOp stencilOp; + if (Deserializer::readOptional(compareOp, node, keys::function)) { + result.function = compareOp; + } + if (Deserializer::readOptional(stencilOp, node, keys::failOp)) { + result.failOp = stencilOp; + } + if (Deserializer::readOptional(stencilOp, node, keys::depthFailOp)) { + result.depthFailOp = stencilOp; + } + if (Deserializer::readOptional(stencilOp, node, keys::passOp)) { + result.passOp = stencilOp; + } + if (Deserializer::readOptional(compareOp, node, keys::function)) { + result.function = compareOp; + } + Deserializer::readOptional(result.reference, node, keys::reference); + Deserializer::readOptional(result.readMask, node, keys::readMask); + return result; +} + +static State::StencilActivation readStencilActivation(const json& node) { + auto jsonString = node.dump(2); + State::StencilActivation result; + bool enabled; + if (Deserializer::readOptional(enabled, node, keys::enabled)) { + result.enabled = enabled; + } + Deserializer::readOptional(result.frontWriteMask, node, keys::frontWriteMask); + Deserializer::readOptional(result.backWriteMask, node, keys::backWriteMask); + return result; +} + +StatePointer readState(const json& node) { + if (node.is_null()) { + return nullptr; + } + + State::Data data; + Deserializer::readOptionalTransformed(data.flags, node, keys::flags, &readStateFlags); + Deserializer::readOptionalTransformed(data.blendFunction, node, keys::blendFunction, &readBlendFunction); + Deserializer::readOptionalTransformed(data.depthTest, node, keys::depthTest, &readDepthTest); + Deserializer::readOptionalTransformed(data.stencilActivation, node, keys::stencilActivation, &readStencilActivation); + Deserializer::readOptionalTransformed(data.stencilTestFront, node, keys::stencilTestFront, &readStencilTest); + Deserializer::readOptionalTransformed(data.stencilTestBack, node, keys::stencilTestBack, &readStencilTest); + Deserializer::readOptional(data.colorWriteMask, node, keys::colorWriteMask); + Deserializer::readOptional(data.cullMode, node, keys::cullMode); + Deserializer::readOptional(data.depthBias, node, keys::depthBias); + Deserializer::readOptional(data.depthBiasSlopeScale, node, keys::depthBiasSlopeScale); + Deserializer::readOptional(data.fillMode, node, keys::fillMode); + Deserializer::readOptional(data.sampleMask, node, keys::sampleMask); + return std::make_shared(data); +} + +PipelinePointer Deserializer::readPipeline(const json& node) { + if (node.is_null()) { + return nullptr; + } + + auto state = readState(node[keys::state]); + uint32_t programIndex = node[keys::program]; + auto program = programs[programIndex]; + return Pipeline::create(program, state); +} + +Stream::FormatPointer Deserializer::readFormat(const json& node) { + if (node.is_null()) { + return nullptr; + } + + auto result = std::make_shared(); + auto& format = *result; + const auto& attributesNode = node[keys::attributes]; + for (const auto& attributeNode : attributesNode) { + uint8_t slot = attributeNode[keys::slot]; + auto& attribute = format._attributes[slot]; + attribute._slot = slot; + attribute._channel = attributeNode[keys::channel]; + readOptionalTransformed(attribute._element, attributeNode, keys::element, + [](const json& node) { return readElement(node); }); + readOptional(attribute._frequency, attributeNode, keys::frequency); + readOptional(attribute._offset, attributeNode, keys::offset); + } + format.evaluateCache(); + return result; +} + +TextureTablePointer Deserializer::readTextureTable(const json& node) { + if (node.is_null()) { + return nullptr; + } + TextureTablePointer result = std::make_shared(); + auto& table = *result; + auto count = node.size(); + for (size_t i = 0; i < count; ++i) { + uint32_t index = node[i]; + table.setTexture(i, textures[index]); + } + return result; +} + +TextureView Deserializer::readTextureView(const json& node) { + TextureView result; + auto texturePointerReader = [this](const json& node) { + uint32_t textureIndex = node; + return textures[textureIndex]; + }; + readOptionalTransformed(result._texture, node, keys::texture, texturePointerReader); + readOptionalTransformed(result._element, node, keys::element, &readElement); + readOptional(result._subresource, node, keys::subresource); + return result; +} + +FramebufferPointer Deserializer::readFramebuffer(const json& node) { + if (node.is_null()) { + return nullptr; + } + + FramebufferPointer result; + { + std::string name; + readOptional(name, node, keys::name); + result.reset(Framebuffer::create(name)); + } + auto& framebuffer = *result; + readOptional(framebuffer._bufferMask, node, keys::bufferMask); + readOptional(framebuffer._height, node, keys::height); + readOptional(framebuffer._width, node, keys::width); + readOptional(framebuffer._numSamples, node, keys::sampleCount); + auto textureViewReader = [this](const json& node) -> TextureView { return readTextureView(node); }; + readOptionalTransformed(framebuffer._depthStencilBuffer, node, keys::depthStencilAttachment, + textureViewReader); + if (framebuffer._depthStencilBuffer) { + framebuffer._depthStamp++; + } + if (node.count(keys::colorAttachments)) { + const auto& colorAttachmentsNode = node[keys::colorAttachments]; + size_t count = colorAttachmentsNode.size(); + for (size_t i = 0; i < count; ++i) { + const auto& colorAttachmentNode = colorAttachmentsNode[i]; + if (colorAttachmentNode.is_null()) { + continue; + } + framebuffer._renderBuffers[i] = readTextureView(colorAttachmentNode); + framebuffer._colorStamps[i]++; + } + } + + return result; +} + +QueryPointer Deserializer::readQuery(const json& node) { + if (node.is_null()) { + return nullptr; + } + std::string name = node[keys::name]; + return std::make_shared([](const Query&) {}, name); +} + +std::vector Deserializer::fromBase64(const json& node) { + std::vector result; + auto decoded = QByteArray::fromBase64(QByteArray{ node.get().c_str() }); + result.resize(decoded.size()); + memcpy(result.data(), decoded.data(), decoded.size()); + return result; +} + +static std::unordered_map getCommandNameMap() { + static std::unordered_map result; + if (result.empty()) { + for (Batch::Command i = Batch::COMMAND_draw; i < Batch::NUM_COMMANDS; i = (Batch::Command)(i + 1)) { + result[keys::COMMAND_NAMES[i]] = i; + } + } + return result; +} + +void Deserializer::readCommand(const json& commandNode, Batch& batch) { + size_t count = commandNode.size(); + std::string commandName = commandNode[0]; + Batch::Command command = getCommandNameMap()[commandName]; + batch._commands.push_back(command); + batch._commandOffsets.push_back(batch._params.size()); + for (size_t i = 1; i < count; ++i) { + batch._params.emplace_back(commandNode[i].get()); + } +} + +Batch::NamedBatchData Deserializer::readNamedBatchData(const json& node) { + Batch::NamedBatchData result; + readOptionalVectorTransformed(result.buffers, node, keys::buffers, [this](const json& node) { + uint32_t index = node; + return buffers[index]; + }); + readOptionalVectorTransformed(result.drawCallInfos, node, keys::drawCallInfos, + [](const json& node) -> Batch::DrawCallInfo { + Batch::DrawCallInfo result{ 0 }; + *((uint32_t*)&result) = node; + return result; + }); + return result; +} + +BatchPointer Deserializer::readBatch(const json& node) { + if (node.is_null()) { + return nullptr; + } + + std::string batchName; + if (node.count(keys::name)) { + batchName = node[keys::name]; + } + BatchPointer result = std::make_shared(batchName); + auto& batch = *result; + readOptional(batch._enableStereo, node, keys::stereo); + readOptional(batch._enableSkybox, node, keys::skybox); + readOptionalTransformed(batch._projectionJitter, node, keys::projectionJitter, &readVec2); + readOptional(batch._drawcallUniform, node, keys::drawcallUniform); + readOptional(batch._drawcallUniformReset, node, keys::drawcallUniformReset); + readPointerCache(batch._textures, node, keys::textures, textures); + readPointerCache(batch._textureTables, node, keys::textureTables, textureTables); + readPointerCache(batch._buffers, node, keys::buffers, buffers); + readPointerCache(batch._pipelines, node, keys::pipelines, pipelines); + readPointerCache(batch._streamFormats, node, keys::formats, formats); + readPointerCache(batch._framebuffers, node, keys::framebuffers, framebuffers); + readPointerCache(batch._swapChains, node, keys::swapchains, swapchains); + readPointerCache(batch._queries, node, keys::queries, queries); + + readOptionalVectorTransformed(batch._drawCallInfos, node, keys::drawCallInfos, + [](const json& node) -> Batch::DrawCallInfo { + Batch::DrawCallInfo result{ 0 }; + *((uint32_t*)&result) = node; + return result; + }); + + readOptionalTransformed>(batch._data, node, keys::data, + [](const json& node) { return fromBase64(node); }); + + for (const auto& commandNode : node[keys::commands]) { + readCommand(commandNode, batch); + } + readBatchCacheTransformed(batch._transforms, node, keys::transforms, &readTransform); + readBatchCacheTransformed(batch._profileRanges, node, keys::profileRanges); + readBatchCacheTransformed(batch._names, node, keys::names); + + auto objectTransformReader = [](const json& node) -> Batch::TransformObject { + Batch::TransformObject result; + result._model = readMat4(node); + result._modelInverse = glm::inverse(result._model); + return result; + }; + readOptionalVectorTransformed(batch._objects, node, keys::objects, objectTransformReader); + + if (node.count(keys::namedData)) { + const auto& namedDataNode = node[keys::namedData]; + for (auto itr = namedDataNode.begin(); itr != namedDataNode.end(); ++itr) { + auto name = itr.key(); + batch._namedData[name] = readNamedBatchData(itr.value()); + } + } + + return result; +} + +StereoState readStereoState(const json& node) { + StereoState result; + Deserializer::readOptional(result._enable, node, keys::enable); + Deserializer::readOptional(result._contextDisable, node, keys::contextDisable); + Deserializer::readOptional(result._skybox, node, keys::skybox); + if (node.count(keys::eyeProjections)) { + auto projections = node[keys::eyeProjections]; + result._eyeProjections[0] = Deserializer::readMat4(projections[0]); + result._eyeProjections[1] = Deserializer::readMat4(projections[1]); + } + if (node.count(keys::eyeViews)) { + auto views = node[keys::eyeViews]; + result._eyeViews[0] = Deserializer::readMat4(views[0]); + result._eyeViews[1] = Deserializer::readMat4(views[1]); + } + return result; +} + + +FramePointer Deserializer::deserializeFrame() { + { + std::string filename{ basename + ".json" }; + if (0 == basename.find("assets:")) { + auto lastSlash = basename.rfind('/'); + basedir = basename.substr(0, lastSlash); + } else { + basedir = QFileInfo(basename.c_str()).absolutePath().toStdString(); + } + storage::FileStorage mappedFile(filename.c_str()); + frameNode = json::parse(std::string((const char*)mappedFile.data(), mappedFile.size())); + } + + FramePointer result = std::make_shared(); + auto& frame = *result; + + if (frameNode[keys::binary].is_string()) { + binaryFile = frameNode[keys::binary]; + if (QFileInfo(binaryFile.c_str()).isRelative()) { + binaryFile = basedir + "/" + binaryFile; + } + } else { + binaryFile = basename + ".bin"; + } + + + if (frameNode.count(keys::buffers)) { + readBuffers(frameNode[keys::buffers]); + } + + shaders = readArray(frameNode, keys::shaders, [](const json& node) { return readShader(node); }); + // Must come after shaders + programs = readArray(frameNode, keys::programs, [this](const json& node) { return readProgram(node); }); + // Must come after programs + pipelines = readArray(frameNode, keys::pipelines, [this](const json& node) { return readPipeline(node); }); + + formats = readArray(frameNode, keys::formats, [](const json& node) { return readFormat(node); }); + + auto textureReader = [this](const json& node) { return readTexture(node, externalTexture); }; + textures = readArray(frameNode, keys::textures, textureReader); + if (textureLoader) { + std::vector capturedTextures = readNumericVector(frameNode[keys::capturedTextures]); + for (const auto& index : capturedTextures) { + const auto& texturePointer = textures[index]; + uint16 layers = std::max(texturePointer->getNumSlices(), 1); + for (uint16 layer = 0; layer < layers; ++layer) { + std::string filename = basename + "." + std::to_string(index) + "." + std::to_string(layer) + ".png"; + textureLoader(filename, texturePointer, layer); + } + } + } + + // Must come after textures + auto textureTableReader = [this](const json& node) { return readTextureTable(node); }; + textureTables = readArray(frameNode, keys::textureTables, textureTableReader); + + // Must come after textures + auto framebufferReader = [this](const json& node) { return readFramebuffer(node); }; + framebuffers = readArray(frameNode, keys::framebuffers, framebufferReader); + + // Must come after textures & framebuffers + swapchains = + readArray(frameNode, keys::swapchains, [this](const json& node) { return readSwapchain(node); }); + + queries = readArray(frameNode, keys::queries, [this](const json& node) { return readQuery(node); }); + frame.framebuffer = framebuffers[frameNode[keys::framebuffer].get()]; + frame.view = readMat4(frameNode[keys::view]); + frame.pose = readMat4(frameNode[keys::pose]); + frame.frameIndex = frameNode[keys::frameIndex]; + frame.stereoState = readStereoState(frameNode[keys::stereo]); + if (frameNode.count(keys::batches)) { + for (const auto& batchNode : frameNode[keys::batches]) { + frame.batches.push_back(readBatch(batchNode)); + } + } + + return result; +} + + + +FramePointer Deserializer::readFrame() { + auto result = deserializeFrame(); + result->finish(); + return result; +} + +void Deserializer::optimizeFrame(const IndexOptimizer& optimizer) { + auto result = deserializeFrame(); + auto& frame = *result; + + + // optimize the index buffers? + struct CurrentIndexBuffer { + Offset offset{ 0 }; + BufferPointer buffer; + Type type{ gpu::Type::INT32 }; + Primitive primitve{ Primitive::TRIANGLES }; + uint32_t numIndices{ 0 }; + uint32_t startIndex{ 0 }; + }; + + std::vector captured; + for (auto& batch : frame.batches) { + + CurrentIndexBuffer currentIndexBuffer; + batch->forEachCommand([&](Batch::Command cmd, const Batch::Param* params){ + switch(cmd) { + case Batch::Command::COMMAND_setIndexBuffer: + currentIndexBuffer.offset = params[0]._size; + currentIndexBuffer.buffer = batch->_buffers.get(params[1]._int); + currentIndexBuffer.type = (Type)params[2]._int; + break; + + case Batch::Command::COMMAND_drawIndexed: + currentIndexBuffer.startIndex = params[0]._int; + currentIndexBuffer.numIndices = params[1]._int; + currentIndexBuffer.primitve = (Primitive)params[2]._int; + captured.emplace_back(currentIndexBuffer); + break; + + case Batch::Command::COMMAND_drawIndexedInstanced: + currentIndexBuffer.startIndex = params[1]._int; + currentIndexBuffer.numIndices = params[2]._int; + currentIndexBuffer.primitve = (Primitive)params[3]._int; + captured.emplace_back(currentIndexBuffer); + break; + + default: + break; + } + }); + } + + + std::string optimizedBinaryFile = basename + "_optimized.bin"; + QFile(binaryFile.c_str()).copy(optimizedBinaryFile.c_str()); + { + storage::FileStorage mappedFile(optimizedBinaryFile.c_str()); + std::set uniqueBuffers; + for (const auto& capturedIndexData : captured) { + if (uniqueBuffers.count(capturedIndexData.buffer)) { + continue; + } + uniqueBuffers.insert(capturedIndexData.buffer); + auto bufferOffset = bufferOffsets[capturedIndexData.buffer]; + auto& buffer = *capturedIndexData.buffer; + const auto& count = capturedIndexData.numIndices; + auto indices = (uint32_t*)buffer.editData(); + optimizer(capturedIndexData.primitve, count / 3, count, indices); + memcpy(mappedFile.mutableData() + bufferOffset, indices, sizeof(uint32_t) * count); + } + } + frameNode[keys::binary] = optimizedBinaryFile; + { + std::string frameJson = frameNode.dump(); + std::string filename = basename + "_optimized.json"; + storage::FileStorage::create(filename.c_str(), frameJson.size(), (const uint8_t*)frameJson.data()); + } +} diff --git a/libraries/gpu/src/gpu/FrameWriter.cpp b/libraries/gpu/src/gpu/FrameWriter.cpp new file mode 100644 index 0000000000..f348827385 --- /dev/null +++ b/libraries/gpu/src/gpu/FrameWriter.cpp @@ -0,0 +1,844 @@ +// +// Created by Bradley Austin Davis on 2018/10/14 +// 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 "FrameIO.h" +#include "Frame.h" +#include "Batch.h" +#include "TextureTable.h" +#include +#include + +#include "FrameIOKeys.h" + +namespace gpu { + +using json = nlohmann::json; + +class Serializer { +public: + const std::string basename; + const TextureCapturer textureCapturer; + std::unordered_map shaderMap; + std::unordered_map programMap; + std::unordered_map textureMap; + std::unordered_map textureTableMap; + std::unordered_map bufferMap; + std::unordered_map formatMap; + std::unordered_map pipelineMap; + std::unordered_map framebufferMap; + std::unordered_map swapchainMap; + std::unordered_map queryMap; + + Serializer(const std::string& basename, const TextureCapturer& capturer) : basename(basename), textureCapturer(capturer) {} + + template + static uint32_t getGlobalIndex(const T& value, std::unordered_map& map) { + if (map.count(value) == 0) { + uint32_t result = (uint32_t)map.size(); + map[value] = result; + return result; + } + return map[value]; + } + + template + static json serializePointerCache(const typename Batch::Cache::Vector& cache, std::unordered_map& map) { + json result = json::array(); + const auto count = cache._items.size(); + for (uint32_t i = 0; i < count; ++i) { + const auto& cacheEntry = cache._items[i]; + const auto& val = cacheEntry._data; + result.push_back(getGlobalIndex(val, map)); + } + return result; + } + + template + static json serializeDataCache(const typename Batch::Cache::Vector& cache, + std::function f = [](const T& t) -> TT { return t; }) { + json result = json::array(); + const auto count = cache._items.size(); + for (uint32_t i = 0; i < count; ++i) { + const auto& cacheEntry = cache._items[i]; + const auto& val = cacheEntry._data; + result.push_back(f(val)); + } + return result; + } + + template + static json writeVector(const std::vector& v, + const std::function& f = [](const T& t) -> TT { return t; }) { + auto node = json::array(); + for (const auto& e : v) { + node.push_back(f(e)); + } + return node; + } + + template + static json writeNumericVector(const std::vector& v) { + return writeVector(v); + } + + template + static json writeUintVector(const std::vector& v) { + return writeVector(v, [](const T& t) -> const uint32_t& { + return reinterpret_cast(t); + }); + } + + template + static json writeFloatArray(const float* f) { + json result = json::array(); + for (size_t i = 0; i < N; ++i) { + result.push_back(f[i]); + } + return result; + } + + template + static std::vector mapToVector(const std::unordered_map& map) { + std::vector result; + result.resize(map.size()); + for (const auto& entry : map) { + if (result[entry.second]) { + throw std::runtime_error("Invalid map"); + } + result[entry.second] = entry.first; + } + return result; + } + + template + std::function memberWriter(F f) { + return std::bind(f, this, std::placeholders::_1); + } + + void writeFrame(const Frame& frame); + json writeBatch(const Batch& batch); + json writeTextureTable(const TextureTablePointer& textureTable); + json writeTextureView(const TextureView& textureView); + json writeFramebuffer(const FramebufferPointer& texture); + json writePipeline(const PipelinePointer& pipeline); + json writeSwapchain(const SwapChainPointer& swapchain); + json writeProgram(const ShaderPointer& program); + json writeNamedBatchData(const Batch::NamedBatchData& namedData); + + json writeCapturableTextures(const Frame& frame); + void writeBinaryBlob(); + static std::string toBase64(const std::vector& v); + static json writeIrradiance(const SHPointer& irradiance); + static json writeMat4(const glm::mat4& m) { + static const glm::mat4 IDENTITY; + if (m == IDENTITY) { + return json(); + } + return writeFloatArray<16>(&m[0][0]); + } + static json writeVec4(const glm::vec4& v) { return writeFloatArray<4>(&v[0]); } + static json writeVec3(const glm::vec3& v) { return writeFloatArray<3>(&v[0]); } + static json writeVec2(const glm::vec2& v) { return writeFloatArray<2>(&v[0]); } + static json writeTransform(const Transform& t) { return writeMat4(t.getMatrix()); } + static json writeCommand(size_t index, const Batch& batch); + static json writeSampler(const Sampler& sampler); + static json writeTexture(const TexturePointer& texture); + static json writeFormat(const Stream::FormatPointer& format); + static json writeQuery(const QueryPointer& query); + static json writeShader(const ShaderPointer& shader); + static json writeBuffer(const BufferPointer& bufferPointer); + + static const TextureView DEFAULT_TEXTURE_VIEW; + static const Sampler DEFAULT_SAMPLER; + + template + void serializeMap(json& frameNode, const char* key, const std::unordered_map& map, F f) { + auto& node = frameNode[key] = json::array(); + for (const auto& item : mapToVector(map)) { + node.push_back(f(item)); + } + } +}; + +void writeFrame(const std::string& filename, const FramePointer& frame, const TextureCapturer& capturer) { + Serializer(filename, capturer).writeFrame(*frame); +} + +} // namespace gpu + +using namespace gpu; + +const TextureView Serializer::DEFAULT_TEXTURE_VIEW = TextureView(); +const Sampler Serializer::DEFAULT_SAMPLER = Sampler(); + +std::string Serializer::toBase64(const std::vector& v) { + return QByteArray((const char*)v.data(), (int)v.size()).toBase64().toStdString(); +} + +json Serializer::writeCommand(size_t index, const Batch& batch) { + const auto& command = batch._commands[index]; + auto offset = batch._commandOffsets[index]; + auto endOffset = batch._params.size(); + if ((index + 1) < batch._commands.size()) { + endOffset = batch._commandOffsets[index + 1]; + } + + json result = json::array(); + result.push_back(keys::COMMAND_NAMES[command]); + while (offset < endOffset) { + result.push_back(batch._params[offset]._size); + ++offset; + } + return result; +} + +json Serializer::writeNamedBatchData(const Batch::NamedBatchData& namedData) { + json result = json::object(); + auto& buffersNode = result[keys::buffers] = json::array(); + for (const auto& buffer : namedData.buffers) { + buffersNode.push_back(getGlobalIndex(buffer, bufferMap)); + } + result[keys::drawCallInfos] = writeUintVector(namedData.drawCallInfos); + return result; +} + +json Serializer::writeBatch(const Batch& batch) { + json batchNode; + static const Batch DEFAULT_BATCH; + + batchNode[keys::name] = batch.getName(); + if (batch._enableSkybox != DEFAULT_BATCH._enableSkybox) { + batchNode[keys::skybox] = batch._enableSkybox; + } + if (batch._enableStereo != DEFAULT_BATCH._enableStereo) { + batchNode[keys::stereo] = batch._enableStereo; + } + if (batch._projectionJitter != DEFAULT_BATCH._projectionJitter) { + batchNode[keys::projectionJitter] = writeVec2(batch._projectionJitter); + } + if (batch._drawcallUniform != DEFAULT_BATCH._drawcallUniform) { + batchNode[keys::drawcallUniform] = batch._drawcallUniform; + } + if (batch._drawcallUniformReset != DEFAULT_BATCH._drawcallUniformReset) { + batchNode[keys::drawcallUniformReset] = batch._drawcallUniformReset; + } + if (0 != batch._textures.size()) { + batchNode[keys::textures] = serializePointerCache(batch._textures, textureMap); + } + if (0 != batch._textureTables.size()) { + batchNode[keys::textureTables] = serializePointerCache(batch._textureTables, textureTableMap); + } + if (0 != batch._buffers.size()) { + batchNode[keys::buffers] = serializePointerCache(batch._buffers, bufferMap); + } + if (0 != batch._pipelines.size()) { + batchNode[keys::pipelines] = serializePointerCache(batch._pipelines, pipelineMap); + } + if (0 != batch._streamFormats.size()) { + batchNode[keys::formats] = serializePointerCache(batch._streamFormats, formatMap); + } + if (0 != batch._framebuffers.size()) { + batchNode[keys::framebuffers] = serializePointerCache(batch._framebuffers, framebufferMap); + } + if (0 != batch._swapChains.size()) { + batchNode[keys::swapchains] = serializePointerCache(batch._swapChains, swapchainMap); + } + if (0 != batch._queries.size()) { + batchNode[keys::queries] = serializePointerCache(batch._queries, queryMap); + } + if (!batch._drawCallInfos.empty()) { + batchNode[keys::drawCallInfos] = writeUintVector(batch._drawCallInfos); + } + if (!batch._data.empty()) { + batchNode[keys::data] = toBase64(batch._data); + } + + { + auto& node = batchNode[keys::commands] = json::array(); + size_t commandCount = batch._commands.size(); + for (size_t i = 0; i < commandCount; ++i) { + node.push_back(writeCommand(i, batch)); + } + } + + if (0 != batch._transforms.size()) { + batchNode[keys::transforms] = + serializeDataCache(batch._transforms, [](const Transform& t) { return writeTransform(t); }); + } + if (0 != batch._profileRanges.size()) { + batchNode[keys::profileRanges] = serializeDataCache(batch._profileRanges); + } + if (0 != batch._names.size()) { + batchNode[keys::names] = serializeDataCache(batch._names); + } + if (0 != batch._objects.size()) { + auto transform = [](const Batch::TransformObject& object) -> json { return writeMat4(object._model); }; + batchNode[keys::objects] = writeVector(batch._objects, transform); + } + + if (!batch._namedData.empty()) { + auto& namedDataNode = batchNode[keys::namedData] = json::object(); + for (const auto& entry : batch._namedData) { + namedDataNode[entry.first] = writeNamedBatchData(entry.second); + } + } + + // LambdaCache _lambdas; + + return batchNode; +} + +json Serializer::writeSampler(const Sampler& sampler) { + json result = json::object(); + if (sampler.getBorderColor() != DEFAULT_SAMPLER.getBorderColor()) { + result[keys::borderColor] = writeVec4(sampler.getBorderColor()); + } + if (sampler.getMaxAnisotropy() != DEFAULT_SAMPLER.getMaxAnisotropy()) { + result[keys::maxAnisotropy] = sampler.getMaxAnisotropy(); + } + if (sampler.getWrapModeU() != DEFAULT_SAMPLER.getWrapModeU()) { + result[keys::wrapModeU] = sampler.getWrapModeU(); + } + if (sampler.getWrapModeV() != DEFAULT_SAMPLER.getWrapModeV()) { + result[keys::wrapModeV] = sampler.getWrapModeV(); + } + if (sampler.getWrapModeW() != DEFAULT_SAMPLER.getWrapModeW()) { + result[keys::wrapModeW] = sampler.getWrapModeW(); + } + if (sampler.getFilter() != DEFAULT_SAMPLER.getFilter()) { + result[keys::filter] = sampler.getFilter(); + } + if (sampler.getComparisonFunction() != DEFAULT_SAMPLER.getComparisonFunction()) { + result[keys::comparisonFunction] = sampler.getComparisonFunction(); + } + if (sampler.getMinMip() != DEFAULT_SAMPLER.getMinMip()) { + result[keys::minMip] = sampler.getMinMip(); + } + if (sampler.getMaxMip() != DEFAULT_SAMPLER.getMaxMip()) { + result[keys::maxMip] = sampler.getMaxMip(); + } + if (sampler.getMipOffset() != DEFAULT_SAMPLER.getMipOffset()) { + result[keys::mipOffset] = sampler.getMipOffset(); + } + return result; +} + +json Serializer::writeBuffer(const BufferPointer& bufferPointer) { + if (!bufferPointer) { + return json(); + } + + return json(bufferPointer->getSize()); +} + +json Serializer::writeIrradiance(const SHPointer& irradiancePointer) { + if (!irradiancePointer) { + return json(); + } + + json result = json::object(); + const auto& irradiance = *irradiancePointer; + result[keys::L00] = writeVec3(irradiance.L00); + result[keys::L1m1] = writeVec3(irradiance.L1m1); + result[keys::L10] = writeVec3(irradiance.L10); + result[keys::L11] = writeVec3(irradiance.L11); + result[keys::L2m2] = writeVec3(irradiance.L2m2); + result[keys::L2m1] = writeVec3(irradiance.L2m1); + result[keys::L20] = writeVec3(irradiance.L20); + result[keys::L21] = writeVec3(irradiance.L21); + result[keys::L22] = writeVec3(irradiance.L22); + return result; +} + +json Serializer::writeTexture(const TexturePointer& texturePointer) { + if (!texturePointer) { + return json(); + } + + const auto& texture = *texturePointer; + json result = json::object(); + if (!texture.source().empty()) { + result[keys::source] = texture.source(); + } + const auto usageType = texture.getUsageType(); + result[keys::usageType] = usageType; + result[keys::type] = texture.getType(); + result[keys::width] = texture._width; + result[keys::height] = texture._height; + result[keys::depth] = texture._depth; + result[keys::mips] = texture._maxMipLevel + 1; + result[keys::samples] = texture._numSamples; + result[keys::layers] = texture._numSlices; + result[keys::texelFormat] = texture.getTexelFormat().getRaw(); + if (texture.isIrradianceValid()) { + result["irradiance"] = writeIrradiance(texture._irradiance); + } + if (texture._sampler != DEFAULT_SAMPLER) { + result[keys::sampler] = writeSampler(texture._sampler); + } + if (usageType == TextureUsageType::RENDERBUFFER) { + // TODO figure out if the buffer contents need to be preserved (if it's used as an input before it's ever written to) + // This might be the case for things like the TAA output attachments from the previous frame + } else if (usageType == TextureUsageType::EXTERNAL) { + // TODO serialize the current GL contents (if any) to the JSON + } else { + const auto* storage = texture._storage.get(); + const auto* ktxStorage = dynamic_cast(storage); + if (ktxStorage) { + result[keys::ktxFile] = ktxStorage->_filename; + } else { + // TODO serialize the backing storage + } + } + return result; +} + +json Serializer::writeTextureView(const TextureView& textureView) { + static const auto DEFAULT_TEXTURE_VIEW = TextureView(); + json result = json::object(); + if (textureView._texture) { + result[keys::texture] = getGlobalIndex(textureView._texture, textureMap); + } + if (textureView._subresource != DEFAULT_TEXTURE_VIEW._subresource) { + result[keys::subresource] = textureView._subresource; + } + if (textureView._element != DEFAULT_TEXTURE_VIEW._element) { + result[keys::element] = textureView._element.getRaw(); + } + return result; +} + +json Serializer::writeFramebuffer(const FramebufferPointer& framebufferPointer) { + if (!framebufferPointer) { + return json(); + } + + auto result = json::object(); + const auto& framebuffer = *framebufferPointer; + if (!framebuffer._name.empty()) { + result[keys::name] = framebuffer._name; + } + if (framebuffer._bufferMask != 0) { + result[keys::bufferMask] = framebuffer._bufferMask; + } + if (framebuffer._height != 0) { + result[keys::height] = framebuffer._height; + } + if (framebuffer._width != 0) { + result[keys::width] = framebuffer._width; + } + if (framebuffer._numSamples != 0 && framebuffer._numSamples != 1) { + result[keys::sampleCount] = framebuffer._numSamples; + } + + if (framebuffer._depthStencilBuffer.isValid()) { + result[keys::depthStencilAttachment] = writeTextureView(framebuffer._depthStencilBuffer); + } + + if (!framebuffer._renderBuffers.empty()) { + size_t rendereBufferCount = 0; + for (size_t i = 0; i < framebuffer._renderBuffers.size(); ++i) { + if (framebuffer._renderBuffers[i].isValid()) { + rendereBufferCount = i + 1; + } + } + if (rendereBufferCount > 0) { + auto& node = result[keys::colorAttachments] = json::array(); + for (size_t i = 0; i < rendereBufferCount; ++i) { + node.push_back(writeTextureView(framebuffer._renderBuffers[i])); + } + } + } + + //SwapchainPointer _swapchain; + return result; +} + +json Serializer::writeTextureTable(const TextureTablePointer& textureTablePointer) { + auto tableNode = json::array(); + const auto& textureTable = *textureTablePointer; + for (const auto& texture : textureTable.getTextures()) { + tableNode.push_back(getGlobalIndex(texture, textureMap)); + } + return tableNode; +} + +json Serializer::writeFormat(const Stream::FormatPointer& formatPointer) { + if (!formatPointer) { + return json(); + } + + const auto& format = *formatPointer; + json result = json::object(); + auto& attributesNode = result[keys::attributes] = json::array(); + static const auto DEFAULT_ATTRIBUTE = Stream::Attribute(); + for (const auto& entry : format._attributes) { + const auto& attribute = entry.second; + auto attributeNode = json::object(); + attributeNode[keys::slot] = attribute._slot; + attributeNode[keys::channel] = attribute._channel; + if (DEFAULT_ATTRIBUTE._element.getRaw() != attribute._element.getRaw()) { + attributeNode[keys::element] = attribute._element.getRaw(); + } + if (DEFAULT_ATTRIBUTE._frequency != attribute._frequency) { + attributeNode[keys::frequency] = attribute._frequency; + } + if (DEFAULT_ATTRIBUTE._offset != attribute._offset) { + attributeNode[keys::offset] = attribute._offset; + } + attributesNode.push_back(attributeNode); + } + return result; +} + +#define SET_IF_NOT_DEFAULT(FIELD) \ + if (value.FIELD != DEFAULT.FIELD) { \ + result[keys::FIELD] = value.FIELD; \ + } + +#define SET_IF_NOT_DEFAULT_(FIELD) \ + if (value._##FIELD != DEFAULT._##FIELD) { \ + result[keys::FIELD] = value._##FIELD; \ + } + +#define SET_IF_NOT_DEFAULT_TRANSFORM(FIELD, TRANSFORM) \ + if (value.FIELD != DEFAULT.FIELD) { \ + result[keys::FIELD] = TRANSFORM(value.FIELD); \ + } + +static json writeBlendFunction(const State::BlendFunction& value) { + static const State::BlendFunction DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(enabled); + SET_IF_NOT_DEFAULT(sourceColor); + SET_IF_NOT_DEFAULT(sourceAlpha); + SET_IF_NOT_DEFAULT(destColor); + SET_IF_NOT_DEFAULT(destAlpha); + SET_IF_NOT_DEFAULT(destColor); + SET_IF_NOT_DEFAULT(opAlpha); + SET_IF_NOT_DEFAULT(opColor); + return result; +} + +static json writeStateFlags(const State::Flags& value) { + static const State::Flags DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(frontFaceClockwise); + SET_IF_NOT_DEFAULT(depthClampEnable); + SET_IF_NOT_DEFAULT(scissorEnable); + SET_IF_NOT_DEFAULT(multisampleEnable); + SET_IF_NOT_DEFAULT(antialisedLineEnable); + SET_IF_NOT_DEFAULT(alphaToCoverageEnable); + return result; +} + +static json writeDepthTest(const State::DepthTest& value) { + static const State::DepthTest DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(writeMask); + SET_IF_NOT_DEFAULT(enabled); + SET_IF_NOT_DEFAULT(function); + return result; +} + + +static json writeStereoState(const StereoState& value) { + static const StereoState DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT_(enable); + SET_IF_NOT_DEFAULT_(contextDisable); + SET_IF_NOT_DEFAULT_(skybox); + if ((value._eyeProjections[0] != DEFAULT._eyeProjections[0]) || (value._eyeProjections[1] != DEFAULT._eyeProjections[1])) { + json projections = json::array(); + projections.push_back(Serializer::writeMat4(value._eyeProjections[0])); + projections.push_back(Serializer::writeMat4(value._eyeProjections[1])); + result[keys::eyeProjections] = projections; + } + if ((value._eyeViews[0] != DEFAULT._eyeViews[0]) || (value._eyeViews[1] != DEFAULT._eyeViews[1])) { + json views = json::array(); + views.push_back(Serializer::writeMat4(value._eyeViews[0])); + views.push_back(Serializer::writeMat4(value._eyeViews[1])); + result[keys::eyeViews] = views; + } + return result; +} + +static json writeStencilTest(const State::StencilTest& value) { + static const State::StencilTest DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(function); + SET_IF_NOT_DEFAULT(failOp); + SET_IF_NOT_DEFAULT(depthFailOp); + SET_IF_NOT_DEFAULT(passOp); + SET_IF_NOT_DEFAULT(reference); + SET_IF_NOT_DEFAULT(readMask); + return result; +} + +static json writeStencilActivation(const State::StencilActivation& value) { + static const State::StencilActivation DEFAULT; + json result = json::object(); + SET_IF_NOT_DEFAULT(frontWriteMask); + SET_IF_NOT_DEFAULT(backWriteMask); + SET_IF_NOT_DEFAULT(enabled); + return result; +} + +json writeState(const StatePointer& statePointer) { + if (!statePointer) { + return json(); + } + const auto& state = *statePointer; + const auto& value = state.getValues(); + const auto& DEFAULT = State::DEFAULT; + auto result = json::object(); + SET_IF_NOT_DEFAULT(colorWriteMask); + SET_IF_NOT_DEFAULT(cullMode); + SET_IF_NOT_DEFAULT(depthBias); + SET_IF_NOT_DEFAULT(depthBiasSlopeScale); + SET_IF_NOT_DEFAULT(fillMode); + SET_IF_NOT_DEFAULT(sampleMask); + SET_IF_NOT_DEFAULT_TRANSFORM(blendFunction, writeBlendFunction); + SET_IF_NOT_DEFAULT_TRANSFORM(flags, writeStateFlags); + SET_IF_NOT_DEFAULT_TRANSFORM(depthTest, writeDepthTest); + SET_IF_NOT_DEFAULT_TRANSFORM(stencilActivation, writeStencilActivation); + SET_IF_NOT_DEFAULT_TRANSFORM(stencilTestFront, writeStencilTest); + SET_IF_NOT_DEFAULT_TRANSFORM(stencilTestBack, writeStencilTest); + return result; +} + +json Serializer::writePipeline(const PipelinePointer& pipelinePointer) { + if (!pipelinePointer) { + return json(); + } + const auto& pipeline = *pipelinePointer; + auto result = json::object(); + result[keys::state] = writeState(pipeline.getState()); + result[keys::program] = getGlobalIndex(pipeline.getProgram(), programMap); + return result; +} + +json Serializer::writeProgram(const ShaderPointer& programPointer) { + if (!programPointer) { + return json(); + } + const auto& program = *programPointer; + auto result = json::array(); + for (const auto& shader : program._shaders) { + result.push_back(getGlobalIndex(shader, shaderMap)); + } + return result; +} + +json Serializer::writeShader(const ShaderPointer& shaderPointer) { + if (!shaderPointer) { + return json(); + } + auto result = json::object(); + const auto& shader = *shaderPointer; + result[keys::id] = shader._source.id; + result[keys::name] = shader._source.name; + result[keys::type] = shader._type; + return result; +} + +json Serializer::writeSwapchain(const SwapChainPointer& swapchainPointer) { + auto framebufferSwapchainPointer = std::static_pointer_cast(swapchainPointer); + if (!framebufferSwapchainPointer) { + return json(); + } + const FramebufferSwapChain& swapchain = *framebufferSwapchainPointer; + auto result = json::object(); + result[keys::size] = swapchain.getSize(); + auto& framebuffersNode = result[keys::framebuffers] = json::array(); + for (uint32_t i = 0; i < swapchain.getSize(); ++i) { + uint32_t index = getGlobalIndex(swapchain.get(i), framebufferMap); + framebuffersNode.push_back(index); + } + return result; +} + +json Serializer::writeQuery(const QueryPointer& queryPointer) { + if (!queryPointer) { + return json(); + } + + const auto& query = *queryPointer; + auto result = json::object(); + result[keys::name] = query._name; + return result; +} + +json Serializer::writeCapturableTextures(const Frame& frame) { + if (!textureCapturer) { + return json::array(); + } + + std::unordered_set writtenRenderbuffers; + std::unordered_set captureTextures; + + auto maybeCaptureTexture = [&](const TexturePointer& texture) { + // Not a valid texture + if (!texture) { + return; + } + + // Not a renderbuffer + if (texture->getUsageType() != TextureUsageType::RENDERBUFFER) { + return; + } + + // Already used in a target framebuffer + if (writtenRenderbuffers.count(texture) > 0) { + return; + } + + captureTextures.insert(texture); + }; + + for (const auto& batchPtr : frame.batches) { + const auto& batch = *batchPtr; + batch.forEachCommand([&](Batch::Command command, const Batch::Param* params) { + switch (command) { + case Batch::COMMAND_setResourceTexture: { + const auto& texture = batch._textures.get(params[0]._uint); + maybeCaptureTexture(texture); + } break; + + case Batch::COMMAND_setResourceTextureTable: { + const auto& textureTablePointer = batch._textureTables.get(params[0]._uint); + if (textureTablePointer) { + for (const auto& texture : textureTablePointer->getTextures()) { + maybeCaptureTexture(texture); + } + } + } break; + + case Batch::COMMAND_setFramebuffer: { + const auto& framebufferPointer = batch._framebuffers.get(params[0]._uint); + if (framebufferPointer) { + const auto& framebuffer = *framebufferPointer; + for (const auto& colorAttachment : framebuffer._renderBuffers) { + if (colorAttachment._texture) { + writtenRenderbuffers.insert(colorAttachment._texture); + } + } + if (framebuffer._depthStencilBuffer._texture) { + writtenRenderbuffers.insert(framebuffer._depthStencilBuffer._texture); + } + } + } + + case Batch::COMMAND_setResourceFramebufferSwapChainTexture: + default: + break; + } + }); // for each command + } // for each batch + + for (const auto& entry : textureMap) { + const auto& texturePointer = entry.first; + if (!texturePointer) { + continue; + } + const auto& texture = *texturePointer; + auto usageType = texture.getUsageType(); + if (usageType == TextureUsageType::RESOURCE || usageType == TextureUsageType::STRICT_RESOURCE) { + const auto* storage = texture._storage.get(); + const auto* ktxStorage = dynamic_cast(storage); + if (!ktxStorage) { + captureTextures.insert(texturePointer); + } + } + } + + json result = json::array(); + for (const auto& texture : captureTextures) { + if (textureCapturer) { + auto index = textureMap[texture]; + auto layers = std::max(texture->getNumSlices(), 1); + for (uint16 layer = 0; layer < layers; ++layer) { + std::string textureFilename = basename + "." + std::to_string(index) + "." + std::to_string(layer) + ".png"; + textureCapturer(textureFilename, texture, layer); + } + result.push_back(index); + } + } + return result; +} + +void Serializer::writeFrame(const Frame& frame) { + json frameNode = json::object(); + + frameNode[keys::batches] = json::array(); + for (const auto& batchPointer : frame.batches) { + frameNode[keys::batches].push_back(writeBatch(*batchPointer)); + } + + frameNode[keys::stereo] = writeStereoState(frame.stereoState); + frameNode[keys::capturedTextures] = writeCapturableTextures(frame); + frameNode[keys::frameIndex] = frame.frameIndex; + frameNode[keys::view] = writeMat4(frame.view); + frameNode[keys::pose] = writeMat4(frame.pose); + frameNode[keys::framebuffer] = getGlobalIndex(frame.framebuffer, framebufferMap); + using namespace std::placeholders; + serializeMap(frameNode, keys::swapchains, swapchainMap, std::bind(&Serializer::writeSwapchain, this, _1)); + serializeMap(frameNode, keys::framebuffers, framebufferMap, [this](const auto& t) { return writeFramebuffer(t); }); + serializeMap(frameNode, keys::textureTables, textureTableMap, [this](const auto& t) { return writeTextureTable(t); }); + serializeMap(frameNode, keys::pipelines, pipelineMap, [this](const auto& t) { return writePipeline(t); }); + serializeMap(frameNode, keys::programs, programMap, [this](const auto& t) { return writeProgram(t); }); + serializeMap(frameNode, keys::shaders, shaderMap, writeShader); + serializeMap(frameNode, keys::queries, queryMap, writeQuery); + serializeMap(frameNode, keys::formats, formatMap, writeFormat); + + // Serialize textures and buffers last, since the maps they use can be populated by some of the above code + // Serialize textures + serializeMap(frameNode, keys::textures, textureMap, writeTexture); + // Serialize buffers + serializeMap(frameNode, keys::buffers, bufferMap, writeBuffer); + + { + std::string frameJson = frameNode.dump(); + std::string filename = basename + ".json"; + storage::FileStorage::create(filename.c_str(), frameJson.size(), (const uint8_t*)frameJson.data()); + } + + writeBinaryBlob(); + frameNode[keys::binary] = basename + ".bin"; +} + +void Serializer::writeBinaryBlob() { + const auto buffers = mapToVector(bufferMap); + auto accumulator = [](size_t total, const BufferPointer& buffer) { return total + (buffer ? buffer->getSize() : 0); }; + size_t totalSize = std::accumulate(buffers.begin(), buffers.end(), (size_t)0, accumulator); + + const auto blobFilename = basename + ".bin"; + QFile file(blobFilename.c_str()); + if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) { + throw std::runtime_error("Unable to open file for writing"); + } + if (!file.resize(totalSize)) { + throw std::runtime_error("Unable to resize file"); + } + + auto mapped = file.map(0, totalSize); + size_t offset = 0; + + for (const auto& bufferPointer : buffers) { + if (!bufferPointer) { + continue; + } + const auto& buffer = *bufferPointer; + const auto bufferSize = buffer.getSize(); + const auto& bufferData = buffer._renderSysmem.readData(); + memcpy(mapped + offset, bufferData, bufferSize); + offset += bufferSize; + } + if (!file.unmap(mapped)) { + throw std::runtime_error("Unable to unmap file"); + } +} diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index 44e945883f..47b2775d52 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -173,6 +173,8 @@ protected: void updateSize(const TexturePointer& texture); bool assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource); + friend class Serializer; + friend class Deserializer; // Non exposed Framebuffer(const Framebuffer& framebuffer) = delete; Framebuffer() {} diff --git a/libraries/gpu/src/gpu/PointerStorage.h b/libraries/gpu/src/gpu/PointerStorage.h new file mode 100644 index 0000000000..3854205efb --- /dev/null +++ b/libraries/gpu/src/gpu/PointerStorage.h @@ -0,0 +1,159 @@ +// +// Created by Bradley Austin Davis on 2019/01/25 +// Copyright 2013-2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_gpu_PointerStorage_h +#define hifi_gpu_PointerStorage_h + +namespace gpu { + +// +// GL Backend pointer storage mechanism +// One of the following three defines must be defined. +// GPU_POINTER_STORAGE_SHARED + +// The platonic ideal, use references to smart pointers. +// However, this produces artifacts because there are too many places in the code right now that +// create temporary values (undesirable smart pointer duplications) and then those temp variables +// get passed on and have their reference taken, and then invalidated +// GPU_POINTER_STORAGE_REF + +// Raw pointer manipulation. Seems more dangerous than the reference wrappers, +// but in practice, the danger of grabbing a reference to a temporary variable +// is causing issues +// GPU_POINTER_STORAGE_RAW + +#if defined(GPU_POINTER_STORAGE_SHARED) +template +static inline bool compare(const std::shared_ptr& a, const std::shared_ptr& b) { + return a == b; +} + +template +static inline T* acquire(const std::shared_ptr& pointer) { + return pointer.get(); +} + +template +static inline void reset(std::shared_ptr& pointer) { + return pointer.reset(); +} + +template +static inline bool valid(const std::shared_ptr& pointer) { + return pointer.operator bool(); +} + +template +static inline void assign(std::shared_ptr& pointer, const std::shared_ptr& source) { + pointer = source; +} + +using BufferReference = BufferPointer; +using TextureReference = TexturePointer; +using FramebufferReference = FramebufferPointer; +using FormatReference = Stream::FormatPointer; +using PipelineReference = PipelinePointer; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#elif defined(GPU_POINTER_STORAGE_REF) + +template +class PointerReferenceWrapper : public std::reference_wrapper> { + using Parent = std::reference_wrapper>; + +public: + using Pointer = std::shared_ptr; + PointerReferenceWrapper() : Parent(EMPTY()) {} + PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {} + void clear() { *this = EMPTY(); } + +private: + static const Pointer& EMPTY() { + static const Pointer EMPTY_VALUE; + return EMPTY_VALUE; + }; +}; + +template +static bool compare(const PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + return reference.get() == pointer; +} + +template +static inline T* acquire(const PointerReferenceWrapper& reference) { + return reference.get().get(); +} + +template +static void assign(PointerReferenceWrapper& reference, const std::shared_ptr& pointer) { + reference = pointer; +} + +template +static bool valid(const PointerReferenceWrapper& reference) { + return reference.get().operator bool(); +} + +template +static inline void reset(PointerReferenceWrapper& reference) { + return reference.clear(); +} + +using BufferReference = PointerReferenceWrapper; +using TextureReference = PointerReferenceWrapper; +using FramebufferReference = PointerReferenceWrapper; +using FormatReference = PointerReferenceWrapper; +using PipelineReference = PointerReferenceWrapper; + +#define GPU_REFERENCE_INIT_VALUE + +#elif defined(GPU_POINTER_STORAGE_RAW) + +template +static bool compare(const T* const& rawPointer, const std::shared_ptr& pointer) { + return rawPointer == pointer.get(); +} + +template +static inline T* acquire(T* const& rawPointer) { + return rawPointer; +} + +template +static inline bool valid(const T* const& rawPointer) { + return rawPointer; +} + +template +static inline void reset(T*& rawPointer) { + rawPointer = nullptr; +} + +template +static inline void assign(T*& rawPointer, const std::shared_ptr& pointer) { + rawPointer = pointer.get(); +} + +using BufferReference = Buffer*; +using TextureReference = Texture*; +using FramebufferReference = Framebuffer*; +using FormatReference = Stream::Format*; +using PipelineReference = Pipeline*; + +#define GPU_REFERENCE_INIT_VALUE nullptr + +#else + +#error "No GPU pointer storage scheme defined" + +#endif + +} + +#endif // hifi_gpu_PointerStorage_h \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index e053fdd507..912901951c 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -45,6 +45,9 @@ namespace gpu { const std::string _name; uint64_t _queryResult { 0 }; uint64_t _usecBatchElapsedTime { 0 }; + + friend class Serializer; + friend class Deserializer; }; typedef std::shared_ptr QueryPointer; diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index 35426bed20..987e632025 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -167,6 +167,8 @@ protected: const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader); + friend class Serializer; + friend class Deserializer; }; typedef Shader::Pointer ShaderPointer; diff --git a/libraries/gpu/src/gpu/State.cpp b/libraries/gpu/src/gpu/State.cpp index cb5ced2c15..fab0438f75 100755 --- a/libraries/gpu/src/gpu/State.cpp +++ b/libraries/gpu/src/gpu/State.cpp @@ -33,19 +33,19 @@ State::Signature State::evalSignature(const Data& state) { if (state.cullMode != State::DEFAULT.cullMode) { signature.set(State::CULL_MODE); } - if (state.frontFaceClockwise != State::DEFAULT.frontFaceClockwise) { + if (state.flags.frontFaceClockwise != State::DEFAULT.flags.frontFaceClockwise) { signature.set(State::FRONT_FACE_CLOCKWISE); } - if (state.depthClampEnable != State::DEFAULT.depthClampEnable) { + if (state.flags.depthClampEnable != State::DEFAULT.flags.depthClampEnable) { signature.set(State::DEPTH_CLAMP_ENABLE); } - if (state.scissorEnable != State::DEFAULT.scissorEnable) { + if (state.flags.scissorEnable != State::DEFAULT.flags.scissorEnable) { signature.set(State::SCISSOR_ENABLE); } - if (state.multisampleEnable != State::DEFAULT.multisampleEnable) { + if (state.flags.multisampleEnable != State::DEFAULT.flags.multisampleEnable) { signature.set(State::MULTISAMPLE_ENABLE); } - if (state.antialisedLineEnable != State::DEFAULT.antialisedLineEnable) { + if (state.flags.antialisedLineEnable != State::DEFAULT.flags.antialisedLineEnable) { signature.set(State::ANTIALISED_LINE_ENABLE); } if (state.depthBias != State::DEFAULT.depthBias) { @@ -69,7 +69,7 @@ State::Signature State::evalSignature(const Data& state) { if (state.sampleMask != State::DEFAULT.sampleMask) { signature.set(State::SAMPLE_MASK); } - if (state.alphaToCoverageEnable != State::DEFAULT.alphaToCoverageEnable) { + if (state.flags.alphaToCoverageEnable != State::DEFAULT.flags.alphaToCoverageEnable) { signature.set(State::ALPHA_TO_COVERAGE_ENABLE); } if (state.blendFunction != State::DEFAULT.blendFunction) { @@ -86,3 +86,28 @@ State::State(const Data& values) : _values(values) { _signature = evalSignature(_values); } + + +template +static std::string hex(T t) { + std::stringstream stream; + stream << std::hex << t; + return stream.str(); +} + +std::string State::getKey() const { + std::string key; + key = hex(*(int*)&_values.depthBias); + key += ":" + hex(*(int*)&_values.depthBiasSlopeScale); + key += ":" + hex(_values.depthTest.getRaw()); + key += ":" + hex(_values.stencilActivation.getRaw()); + key += ":" + hex(_values.stencilTestFront.getRaw()); + key += ":" + hex(_values.stencilTestBack.getRaw()); + key += ":" + hex(_values.blendFunction.getRaw()); + key += ":" + hex(_values.sampleMask); + // fillMode, cullMode, colorMaskWrite and the flags consume 32 bits alltogether + static_assert(0 == offsetof(State::Data, fillMode) % 4, "Validate fillMode offset"); + key += ":" + hex(*(int*)&_values.fillMode); + return key; +} + diff --git a/libraries/gpu/src/gpu/State.h b/libraries/gpu/src/gpu/State.h index e68e829fb5..abe0cd7731 100755 --- a/libraries/gpu/src/gpu/State.h +++ b/libraries/gpu/src/gpu/State.h @@ -14,6 +14,7 @@ #include "Format.h" #include +#include #include #include #include @@ -21,16 +22,16 @@ // Why a macro and not a fancy template you will ask me ? // Because some of the fields are bool packed tightly in the State::Cache class // and it s just not good anymore for template T& variable manipulation... -#define SET_FIELD(field, defaultValue, value, dest) {\ - dest = value;\ - if (value == defaultValue) {\ - _signature.reset(field);\ - } else {\ - _signature.set(field);\ - }\ - _stamp++;\ -}\ - +#define SET_FIELD(FIELD, PATH, value) \ + { \ + _values.PATH = value; \ + if (value == DEFAULT.PATH) { \ + _signature.reset(FIELD); \ + } else { \ + _signature.set(FIELD); \ + } \ + _stamp++; \ + } namespace gpu { @@ -45,7 +46,8 @@ public: typedef ::gpu::ComparisonFunction ComparisonFunction; - enum FillMode { + enum FillMode : uint8 + { FILL_POINT = 0, FILL_LINE, FILL_FACE, @@ -53,7 +55,8 @@ public: NUM_FILL_MODES, }; - enum CullMode { + enum CullMode : uint8 + { CULL_NONE = 0, CULL_FRONT, CULL_BACK, @@ -61,7 +64,8 @@ public: NUM_CULL_MODES, }; - enum StencilOp { + enum StencilOp : uint16 + { STENCIL_OP_KEEP = 0, STENCIL_OP_ZERO, STENCIL_OP_REPLACE, @@ -74,7 +78,8 @@ public: NUM_STENCIL_OPS, }; - enum BlendArg { + enum BlendArg : uint16 + { ZERO = 0, ONE, SRC_COLOR, @@ -94,7 +99,8 @@ public: NUM_BLEND_ARGS, }; - enum BlendOp { + enum BlendOp : uint16 + { BLEND_OP_ADD = 0, BLEND_OP_SUBTRACT, BLEND_OP_REV_SUBTRACT, @@ -104,132 +110,158 @@ public: NUM_BLEND_OPS, }; - enum ColorMask + enum ColorMask : uint8 { WRITE_NONE = 0, WRITE_RED = 1, WRITE_GREEN = 2, WRITE_BLUE = 4, WRITE_ALPHA = 8, - WRITE_ALL = (WRITE_RED | WRITE_GREEN | WRITE_BLUE | WRITE_ALPHA ), + WRITE_ALL = (WRITE_RED | WRITE_GREEN | WRITE_BLUE | WRITE_ALPHA), }; class DepthTest { - uint8 _function = LESS; - uint8 _writeMask = true; - uint8 _enabled = false; -#if defined(__clang__) - __attribute__((unused)) -#endif - uint8 _spare = 0; // Padding + public: + uint8 writeMask{ true }; + uint8 enabled{ false }; + ComparisonFunction function{ LESS }; + public: DepthTest(bool enabled = false, bool writeMask = true, ComparisonFunction func = LESS) : - _function(func), _writeMask(writeMask), _enabled(enabled) { - } + writeMask(writeMask), enabled(enabled), function(func) {} - bool isEnabled() const { return _enabled != 0; } - ComparisonFunction getFunction() const { return ComparisonFunction(_function); } - uint8 getWriteMask() const { return _writeMask; } + bool isEnabled() const { return enabled != 0; } + ComparisonFunction getFunction() const { return function; } + uint8 getWriteMask() const { return writeMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } DepthTest(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const DepthTest& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const DepthTest& right) const { return getRaw() != right.getRaw(); } - }; + bool operator==(const DepthTest& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const DepthTest& right) const { return getRaw() != right.getRaw(); } + }; - class StencilTest { - static const int FUNC_MASK = 0x000f; - static const int FAIL_OP_MASK = 0x00f0; - static const int DEPTH_FAIL_OP_MASK = 0x0f00; - static const int PASS_OP_MASK = 0xf000; - static const int FAIL_OP_OFFSET = 4; - static const int DEPTH_FAIL_OP_OFFSET = 8; - static const int PASS_OP_OFFSET = 12; + static_assert(sizeof(DepthTest) == sizeof(uint32_t), "DepthTest size check"); - uint16 _functionAndOperations; - int8 _reference = 0; - uint8 _readMask = 0xff; - public: + struct StencilTest { + ComparisonFunction function : 4; + StencilOp failOp : 4; + StencilOp depthFailOp : 4; + StencilOp passOp : 4; + int8 reference{ 0 }; + uint8 readMask{ 0xff }; - StencilTest(int8 reference = 0, uint8 readMask =0xFF, ComparisonFunction func = ALWAYS, StencilOp failOp = STENCIL_OP_KEEP, StencilOp depthFailOp = STENCIL_OP_KEEP, StencilOp passOp = STENCIL_OP_KEEP) : - _functionAndOperations(func | (failOp << FAIL_OP_OFFSET) | (depthFailOp << DEPTH_FAIL_OP_OFFSET) | (passOp << PASS_OP_OFFSET)), - _reference(reference), _readMask(readMask) - {} + public: + StencilTest(int8 reference = 0, + uint8 readMask = 0xFF, + ComparisonFunction func = ALWAYS, + StencilOp failOp = STENCIL_OP_KEEP, + StencilOp depthFailOp = STENCIL_OP_KEEP, + StencilOp passOp = STENCIL_OP_KEEP) : + function(func), + failOp(failOp), depthFailOp(depthFailOp), passOp(passOp), reference(reference), readMask(readMask) {} - ComparisonFunction getFunction() const { return ComparisonFunction(_functionAndOperations & FUNC_MASK); } - StencilOp getFailOp() const { return StencilOp((_functionAndOperations & FAIL_OP_MASK) >> FAIL_OP_OFFSET); } - StencilOp getDepthFailOp() const { return StencilOp((_functionAndOperations & DEPTH_FAIL_OP_MASK) >> DEPTH_FAIL_OP_OFFSET); } - StencilOp getPassOp() const { return StencilOp((_functionAndOperations & PASS_OP_MASK) >> PASS_OP_OFFSET); } + ComparisonFunction getFunction() const { return function; } + StencilOp getFailOp() const { return failOp; } + StencilOp getDepthFailOp() const { return depthFailOp; } + StencilOp getPassOp() const { return passOp; } - int8 getReference() const { return _reference; } - uint8 getReadMask() const { return _readMask; } + int8 getReference() const { return reference; } + uint8 getReadMask() const { return readMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } StencilTest(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const StencilTest& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const StencilTest& right) const { return getRaw() != right.getRaw(); } - }; + bool operator==(const StencilTest& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const StencilTest& right) const { return getRaw() != right.getRaw(); } + }; + static_assert(sizeof(StencilTest) == sizeof(uint32_t), "StencilTest size check"); - class StencilActivation { - uint8 _frontWriteMask = 0xFF; - uint8 _backWriteMask = 0xFF; - uint16 _enabled = 0; - public: + StencilTest stencilTestFront; - StencilActivation(bool enabled, uint8 frontWriteMask = 0xFF, uint8 backWriteMask = 0xFF) : - _frontWriteMask(frontWriteMask), _backWriteMask(backWriteMask), _enabled(enabled) {} + struct StencilActivation { + uint8 frontWriteMask = 0xFF; + uint8 backWriteMask = 0xFF; + bool enabled : 1; + uint8 _spare1 : 7; + uint8 _spare2{ 0 }; - bool isEnabled() const { return (_enabled != 0); } - uint8 getWriteMaskFront() const { return _frontWriteMask; } - uint8 getWriteMaskBack() const { return _backWriteMask; } + public: + StencilActivation(bool enabled = false, uint8 frontWriteMask = 0xFF, uint8 backWriteMask = 0xFF) : + frontWriteMask(frontWriteMask), backWriteMask(backWriteMask), enabled(enabled), _spare1{ 0 } {} + + bool isEnabled() const { return enabled; } + uint8 getWriteMaskFront() const { return frontWriteMask; } + uint8 getWriteMaskBack() const { return backWriteMask; } int32 getRaw() const { return *(reinterpret_cast(this)); } StencilActivation(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const StencilActivation& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const StencilActivation& right) const { return getRaw() != right.getRaw(); } + bool operator==(const StencilActivation& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const StencilActivation& right) const { return getRaw() != right.getRaw(); } }; - class BlendFunction { - static const int COLOR_MASK = 0x0f; - static const int ALPHA_MASK = 0xf0; - static const int ALPHA_OFFSET = 4; + static_assert(sizeof(StencilActivation) == sizeof(uint32_t), "StencilActivation size check"); + + struct BlendFunction { + // Using uint8 here will make the structure as a whole not align to 32 bits + uint16 enabled : 8; + BlendArg sourceColor : 4; + BlendArg sourceAlpha : 4; + BlendArg destColor : 4; + BlendArg destAlpha : 4; + BlendOp opColor : 4; + BlendOp opAlpha : 4; - uint8 _enabled; - uint8 _source; - uint8 _destination; - uint8 _operation; public: - BlendFunction(bool enabled, - BlendArg sourceColor, BlendOp operationColor, BlendArg destinationColor, - BlendArg sourceAlpha, BlendOp operationAlpha, BlendArg destinationAlpha) : - _enabled(enabled), - _source(sourceColor | (sourceAlpha << ALPHA_OFFSET)), - _destination(destinationColor | (destinationAlpha << ALPHA_OFFSET)), - _operation(operationColor | (operationAlpha << ALPHA_OFFSET)) {} + BlendArg sourceColor, + BlendOp operationColor, + BlendArg destinationColor, + BlendArg sourceAlpha, + BlendOp operationAlpha, + BlendArg destinationAlpha) : + enabled(enabled), + sourceColor(sourceColor), sourceAlpha(sourceAlpha), + destColor(destinationColor), destAlpha(destinationAlpha), + opColor(operationColor), opAlpha(operationAlpha) {} - BlendFunction(bool enabled, BlendArg source = ONE, BlendOp operation = BLEND_OP_ADD, BlendArg destination = ZERO) : - _enabled(enabled), - _source(source | (source << ALPHA_OFFSET)), - _destination(destination | (destination << ALPHA_OFFSET)), - _operation(operation | (operation << ALPHA_OFFSET)) {} + BlendFunction(bool enabled = false, BlendArg source = ONE, BlendOp operation = BLEND_OP_ADD, BlendArg destination = ZERO) : + BlendFunction(enabled, source, operation, destination, source, operation, destination) {} - bool isEnabled() const { return (_enabled != 0); } + bool isEnabled() const { return (enabled != 0); } - BlendArg getSourceColor() const { return BlendArg(_source & COLOR_MASK); } - BlendArg getDestinationColor() const { return BlendArg(_destination & COLOR_MASK); } - BlendOp getOperationColor() const { return BlendOp(_operation & COLOR_MASK); } + BlendArg getSourceColor() const { return sourceColor; } + BlendArg getDestinationColor() const { return destColor; } + BlendOp getOperationColor() const { return opColor; } - BlendArg getSourceAlpha() const { return BlendArg((_source & ALPHA_MASK) >> ALPHA_OFFSET); } - BlendArg getDestinationAlpha() const { return BlendArg((_destination & ALPHA_MASK) >> ALPHA_OFFSET); } - BlendOp getOperationAlpha() const { return BlendOp((_operation & ALPHA_MASK) >> ALPHA_OFFSET); } + BlendArg getSourceAlpha() const { return sourceAlpha; } + BlendArg getDestinationAlpha() const { return destAlpha; } + BlendOp getOperationAlpha() const { return opAlpha; } int32 getRaw() const { return *(reinterpret_cast(this)); } BlendFunction(int32 raw) { *(reinterpret_cast(this)) = raw; } - bool operator== (const BlendFunction& right) const { return getRaw() == right.getRaw(); } - bool operator!= (const BlendFunction& right) const { return getRaw() != right.getRaw(); } + bool operator==(const BlendFunction& right) const { return getRaw() == right.getRaw(); } + bool operator!=(const BlendFunction& right) const { return getRaw() != right.getRaw(); } }; + static_assert(sizeof(BlendFunction) == sizeof(uint32_t), "BlendFunction size check"); + + struct Flags { + Flags() : + frontFaceClockwise(false), depthClampEnable(false), scissorEnable(false), multisampleEnable(false), + antialisedLineEnable(true), alphaToCoverageEnable(false), _spare1(0) {} + bool frontFaceClockwise : 1; + bool depthClampEnable : 1; + bool scissorEnable : 1; + bool multisampleEnable : 1; + bool antialisedLineEnable : 1; + bool alphaToCoverageEnable : 1; + uint8 _spare1 : 2; + + bool operator==(const Flags& right) const { return *(uint8*)this == *(uint8*)&right; } + bool operator!=(const Flags& right) const { return *(uint8*)this != *(uint8*)&right; } + }; + + static_assert(sizeof(Flags) == sizeof(uint8), "Flags size check"); + // The Data class is the full explicit description of the State class fields value. // Useful for having one const static called Default for reference or for the gpu::Backend to keep track of the current value class Data { @@ -237,71 +269,72 @@ public: float depthBias = 0.0f; float depthBiasSlopeScale = 0.0f; - DepthTest depthTest = DepthTest(false, true, LESS); - - StencilActivation stencilActivation = StencilActivation(false); - StencilTest stencilTestFront = StencilTest(0, 0xff, ALWAYS, STENCIL_OP_KEEP, STENCIL_OP_KEEP, STENCIL_OP_KEEP); - StencilTest stencilTestBack = StencilTest(0, 0xff, ALWAYS, STENCIL_OP_KEEP, STENCIL_OP_KEEP, STENCIL_OP_KEEP); - + DepthTest depthTest; + StencilActivation stencilActivation; + StencilTest stencilTestFront; + StencilTest stencilTestBack; uint32 sampleMask = 0xFFFFFFFF; + BlendFunction blendFunction; + FillMode fillMode{ FILL_FACE }; + CullMode cullMode{ CULL_NONE }; + ColorMask colorWriteMask{ WRITE_ALL }; - BlendFunction blendFunction = BlendFunction(false); - - uint8 fillMode = FILL_FACE; - uint8 cullMode = CULL_NONE; - - uint8 colorWriteMask = WRITE_ALL; - - bool frontFaceClockwise : 1; - bool depthClampEnable : 1; - bool scissorEnable : 1; - bool multisampleEnable : 1; - bool antialisedLineEnable : 1; - bool alphaToCoverageEnable : 1; - - Data() : - frontFaceClockwise(false), - depthClampEnable(false), - scissorEnable(false), - multisampleEnable(false), - antialisedLineEnable(true), - alphaToCoverageEnable(false) - {} + Flags flags; }; + static_assert(offsetof(Data, depthBias) == 0, "Data offsets"); + static_assert(offsetof(Data, depthBiasSlopeScale) == 4, "Data offsets"); + static_assert(offsetof(Data, depthTest) == 8, "Data offsets"); + static_assert(offsetof(Data, stencilActivation) == 12, "Data offsets"); + static_assert(offsetof(Data, stencilTestFront) == 16, "Data offsets"); + static_assert(offsetof(Data, stencilTestBack) == 20, "Data offsets"); + static_assert(offsetof(Data, sampleMask) == 24, "Data offsets"); + static_assert(offsetof(Data, blendFunction) == 28, "Data offsets"); + static_assert(offsetof(Data, fillMode) == 32, "Data offsets"); + static_assert(offsetof(Data, cullMode) == 33, "Data offsets"); + static_assert(offsetof(Data, colorWriteMask) == 34, "Data offsets"); + static_assert(offsetof(Data, flags) == 35, "Data offsets"); + static_assert(sizeof(Data) == 36, "Data Size Check"); + + std::string getKey() const; + // The unique default values for all the fields static const Data DEFAULT; - void setFillMode(FillMode fill) { SET_FIELD(FILL_MODE, DEFAULT.fillMode, fill, _values.fillMode); } - FillMode getFillMode() const { return FillMode(_values.fillMode); } + void setFillMode(FillMode fill) { SET_FIELD(FILL_MODE, fillMode, fill); } + FillMode getFillMode() const { return _values.fillMode; } - void setCullMode(CullMode cull) { SET_FIELD(CULL_MODE, DEFAULT.cullMode, cull, _values.cullMode); } - CullMode getCullMode() const { return CullMode(_values.cullMode); } + void setCullMode(CullMode cull) { SET_FIELD(CULL_MODE, cullMode, cull); } + CullMode getCullMode() const { return _values.cullMode; } - void setFrontFaceClockwise(bool isClockwise) { SET_FIELD(FRONT_FACE_CLOCKWISE, DEFAULT.frontFaceClockwise, isClockwise, _values.frontFaceClockwise); } - bool isFrontFaceClockwise() const { return _values.frontFaceClockwise; } + const Flags& getFlags() const { return _values.flags; } - void setDepthClampEnable(bool enable) { SET_FIELD(DEPTH_CLAMP_ENABLE, DEFAULT.depthClampEnable, enable, _values.depthClampEnable); } - bool isDepthClampEnable() const { return _values.depthClampEnable; } + void setFrontFaceClockwise(bool isClockwise) { SET_FIELD(FRONT_FACE_CLOCKWISE, flags.frontFaceClockwise, isClockwise); } + bool isFrontFaceClockwise() const { return _values.flags.frontFaceClockwise; } - void setScissorEnable(bool enable) { SET_FIELD(SCISSOR_ENABLE, DEFAULT.scissorEnable, enable, _values.scissorEnable); } - bool isScissorEnable() const { return _values.scissorEnable; } + void setDepthClampEnable(bool enable) { SET_FIELD(DEPTH_CLAMP_ENABLE, flags.depthClampEnable, enable); } + bool isDepthClampEnable() const { return _values.flags.depthClampEnable; } - void setMultisampleEnable(bool enable) { SET_FIELD(MULTISAMPLE_ENABLE, DEFAULT.multisampleEnable, enable, _values.multisampleEnable); } - bool isMultisampleEnable() const { return _values.multisampleEnable; } + void setScissorEnable(bool enable) { SET_FIELD(SCISSOR_ENABLE, flags.scissorEnable, enable); } + bool isScissorEnable() const { return _values.flags.scissorEnable; } - void setAntialiasedLineEnable(bool enable) { SET_FIELD(ANTIALISED_LINE_ENABLE, DEFAULT.antialisedLineEnable, enable, _values.antialisedLineEnable); } - bool isAntialiasedLineEnable() const { return _values.antialisedLineEnable; } + void setMultisampleEnable(bool enable) { SET_FIELD(MULTISAMPLE_ENABLE, flags.multisampleEnable, enable); } + bool isMultisampleEnable() const { return _values.flags.multisampleEnable; } + + void setAntialiasedLineEnable(bool enable) { SET_FIELD(ANTIALISED_LINE_ENABLE, flags.antialisedLineEnable, enable); } + bool isAntialiasedLineEnable() const { return _values.flags.antialisedLineEnable; } // Depth Bias - void setDepthBias(float bias) { SET_FIELD(DEPTH_BIAS, DEFAULT.depthBias, bias, _values.depthBias); } + void setDepthBias(float bias) { SET_FIELD(DEPTH_BIAS, depthBias, bias); } float getDepthBias() const { return _values.depthBias; } - void setDepthBiasSlopeScale(float scale) { SET_FIELD(DEPTH_BIAS_SLOPE_SCALE, DEFAULT.depthBiasSlopeScale, scale, _values.depthBiasSlopeScale); } + void setDepthBiasSlopeScale(float scale) { SET_FIELD(DEPTH_BIAS_SLOPE_SCALE, depthBiasSlopeScale, scale); } float getDepthBiasSlopeScale() const { return _values.depthBiasSlopeScale; } // Depth Test - void setDepthTest(DepthTest depthTest) { SET_FIELD(DEPTH_TEST, DEFAULT.depthTest, depthTest, _values.depthTest); } - void setDepthTest(bool enable, bool writeMask, ComparisonFunction func) { setDepthTest(DepthTest(enable, writeMask, func)); } + void setDepthTest(DepthTest newDepthTest) { SET_FIELD(DEPTH_TEST, depthTest, newDepthTest); } + void setDepthTest(bool enable, bool writeMask, ComparisonFunction func) { + setDepthTest(DepthTest(enable, writeMask, func)); + } DepthTest getDepthTest() const { return _values.depthTest; } bool isDepthTestEnabled() const { return getDepthTest().isEnabled(); } @@ -310,11 +343,14 @@ public: // Stencil test void setStencilTest(bool enabled, uint8 frontWriteMask, StencilTest frontTest, uint8 backWriteMask, StencilTest backTest) { - SET_FIELD(STENCIL_ACTIVATION, DEFAULT.stencilActivation, StencilActivation(enabled, frontWriteMask, backWriteMask), _values.stencilActivation); - SET_FIELD(STENCIL_TEST_FRONT, DEFAULT.stencilTestFront, frontTest, _values.stencilTestFront); - SET_FIELD(STENCIL_TEST_BACK, DEFAULT.stencilTestBack, backTest, _values.stencilTestBack); } + SET_FIELD(STENCIL_ACTIVATION, stencilActivation, StencilActivation(enabled, frontWriteMask, backWriteMask)); + SET_FIELD(STENCIL_TEST_FRONT, stencilTestFront, frontTest); + SET_FIELD(STENCIL_TEST_BACK, stencilTestBack, backTest); + } + void setStencilTest(bool enabled, uint8 frontWriteMask, StencilTest frontTest) { - setStencilTest(enabled, frontWriteMask, frontTest, frontWriteMask, frontTest); } + setStencilTest(enabled, frontWriteMask, frontTest, frontWriteMask, frontTest); + } StencilActivation getStencilActivation() const { return _values.stencilActivation; } StencilTest getStencilTestFront() const { return _values.stencilTestFront; } @@ -325,32 +361,45 @@ public: uint8 getStencilWriteMaskBack() const { return getStencilActivation().getWriteMaskBack(); } // Alpha to coverage - void setAlphaToCoverageEnable(bool enable) { SET_FIELD(ALPHA_TO_COVERAGE_ENABLE, DEFAULT.alphaToCoverageEnable, enable, _values.alphaToCoverageEnable); } - bool isAlphaToCoverageEnabled() const { return _values.alphaToCoverageEnable; } + void setAlphaToCoverageEnable(bool enable) { SET_FIELD(ALPHA_TO_COVERAGE_ENABLE, flags.alphaToCoverageEnable, enable); } + bool isAlphaToCoverageEnabled() const { return _values.flags.alphaToCoverageEnable; } // Sample mask - void setSampleMask(uint32 mask) { SET_FIELD(SAMPLE_MASK, DEFAULT.sampleMask, mask, _values.sampleMask); } + void setSampleMask(uint32 mask) { SET_FIELD(SAMPLE_MASK, sampleMask, mask); } uint32 getSampleMask() const { return _values.sampleMask; } // Blend Function - void setBlendFunction(BlendFunction function) { SET_FIELD(BLEND_FUNCTION, DEFAULT.blendFunction, function, _values.blendFunction); } - BlendFunction getBlendFunction() const { return _values.blendFunction; } + void setBlendFunction(BlendFunction function) { SET_FIELD(BLEND_FUNCTION, blendFunction, function); } + const BlendFunction& getBlendFunction() const { return _values.blendFunction; } - void setBlendFunction(bool enabled, BlendArg sourceColor, BlendOp operationColor, BlendArg destinationColor, BlendArg sourceAlpha, BlendOp operationAlpha, BlendArg destinationAlpha) { - setBlendFunction(BlendFunction(enabled, sourceColor, operationColor, destinationColor, sourceAlpha, operationAlpha, destinationAlpha)); } + void setBlendFunction(bool enabled, + BlendArg sourceColor, + BlendOp operationColor, + BlendArg destinationColor, + BlendArg sourceAlpha, + BlendOp operationAlpha, + BlendArg destinationAlpha) { + setBlendFunction(BlendFunction(enabled, sourceColor, operationColor, destinationColor, sourceAlpha, operationAlpha, + destinationAlpha)); + } void setBlendFunction(bool enabled, BlendArg source, BlendOp operation, BlendArg destination) { - setBlendFunction(BlendFunction(enabled, source, operation, destination)); } + setBlendFunction(BlendFunction(enabled, source, operation, destination)); + } bool isBlendEnabled() const { return getBlendFunction().isEnabled(); } // Color write mask - void setColorWriteMask(uint8 mask) { SET_FIELD(COLOR_WRITE_MASK, DEFAULT.colorWriteMask, mask, _values.colorWriteMask); } - void setColorWriteMask(bool red, bool green, bool blue, bool alpha) { uint32 value = ((WRITE_RED * red) | (WRITE_GREEN * green) | (WRITE_BLUE * blue) | (WRITE_ALPHA * alpha)); SET_FIELD(COLOR_WRITE_MASK, DEFAULT.colorWriteMask, value, _values.colorWriteMask); } - uint8 getColorWriteMask() const { return _values.colorWriteMask; } + void setColorWriteMask(ColorMask mask) { SET_FIELD(COLOR_WRITE_MASK, colorWriteMask, mask); } + void setColorWriteMask(bool red, bool green, bool blue, bool alpha) { + ColorMask value = (ColorMask)((WRITE_RED * red) | (WRITE_GREEN * green) | (WRITE_BLUE * blue) | (WRITE_ALPHA * alpha)); + SET_FIELD(COLOR_WRITE_MASK, colorWriteMask, value); + } + ColorMask getColorWriteMask() const { return _values.colorWriteMask; } // All the possible fields // NOTE: If you change this, you must update GLBackend::GLState::_resetStateCommands - enum Field { + enum Field + { FILL_MODE, CULL_MODE, FRONT_FACE_CLOCKWISE, @@ -376,7 +425,7 @@ public: COLOR_WRITE_MASK, - NUM_FIELDS, // not a valid field, just the count + NUM_FIELDS, // not a valid field, just the count }; // The signature of the state tells which fields of the state are not default @@ -391,20 +440,20 @@ public: State(const Data& values); const Data& getValues() const { return _values; } - const GPUObjectPointer gpuObject {}; + const GPUObjectPointer gpuObject{}; protected: State(const State& state); State& operator=(const State& state); Data _values; - Signature _signature{0}; - Stamp _stamp{0}; + Signature _signature{ 0 }; + Stamp _stamp{ 0 }; }; -typedef std::shared_ptr< State > StatePointer; -typedef std::vector< StatePointer > States; +typedef std::shared_ptr StatePointer; +typedef std::vector States; -}; +}; // namespace gpu #endif diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 2e1ed1d83c..42a0de66ca 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -128,6 +128,8 @@ public: uint32 _elementTotalSize { 0 }; std::string _key; + friend class Serializer; + friend class Deserializer; void evaluateCache(); }; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 73ed1b15dc..23bfff6873 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -189,6 +189,8 @@ public: } protected: Desc _desc; + + friend class Deserializer; }; enum class TextureUsageType : uint8 { @@ -371,6 +373,8 @@ public: ktx::KTXDescriptorPointer _ktxDescriptor; friend class Texture; + friend class Serializer; + friend class Deserializer; }; uint16 minAvailableMipLevel() const { return _storage->minAvailableMipLevel(); }; @@ -628,6 +632,9 @@ protected: static TexturePointer create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler); Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips); + + friend class Serializer; + friend class Deserializer; }; typedef std::shared_ptr TexturePointer; diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index e247984bbf..f0fc97c5c7 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -67,108 +67,6 @@ bool HFMMaterial::needTangentSpace() const { return !normalTexture.isNull(); } -static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape); - -void HFMMesh::createBlendShapeTangents(bool generateTangents) { - for (auto& blendShape : blendshapes) { - _createBlendShapeTangents(*this, generateTangents, blendShape); - } -} - -using IndexAccessor = std::function; - -static void setTangents(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, - const QVector& vertices, const QVector& normals, QVector& tangents) { - glm::vec3 vertex[2]; - glm::vec3 normal; - glm::vec3* tangent = vertexAccessor(mesh, firstIndex, secondIndex, vertex, normal); - if (tangent) { - glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); - if (glm::length(bitangent) < EPSILON) { - return; - } - glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); - glm::vec3 normalizedNormal = glm::normalize(normal); - *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * - glm::normalize(bitangent), normalizedNormal); - } -} - -static void createTangents(const HFMMesh& mesh, bool generateFromTexCoords, - const QVector& vertices, const QVector& normals, QVector& tangents, - IndexAccessor accessor) { - // if we have a normal map (and texture coordinates), we must compute tangents - if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { - tangents.resize(vertices.size()); - - foreach(const HFMMeshPart& part, mesh.parts) { - for (int i = 0; i < part.quadIndices.size(); i += 4) { - setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i), vertices, normals, tangents); - } - // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 - // This is most likely evidence of a further problem in extractMesh() - for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { - setTangents(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1), vertices, normals, tangents); - setTangents(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2), vertices, normals, tangents); - setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); - } - if ((part.triangleIndices.size() % 3) != 0) { - qCDebug(modelformat) << "Error in extractHFMModel part.triangleIndices.size() is not divisible by three "; - } - } - } -} - -void HFMMesh::createMeshTangents(bool generateFromTexCoords) { - HFMMesh& mesh = *this; - // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't - // const in the lambda function. - auto& tangents = mesh.tangents; - createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, - [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { - outVertices[0] = mesh.vertices[firstIndex]; - outVertices[1] = mesh.vertices[secondIndex]; - outNormal = mesh.normals[firstIndex]; - return &(tangents[firstIndex]); - }); -} - -static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape) { - // Create lookup to get index in blend shape from vertex index in mesh - std::vector reverseIndices; - reverseIndices.resize(mesh.vertices.size()); - std::iota(reverseIndices.begin(), reverseIndices.end(), 0); - - for (int indexInBlendShape = 0; indexInBlendShape < blendShape.indices.size(); ++indexInBlendShape) { - auto indexInMesh = blendShape.indices[indexInBlendShape]; - reverseIndices[indexInMesh] = indexInBlendShape; - } - - createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, - [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { - const auto index1 = reverseIndices[firstIndex]; - const auto index2 = reverseIndices[secondIndex]; - - if (index1 < blendShape.vertices.size()) { - outVertices[0] = blendShape.vertices[index1]; - if (index2 < blendShape.vertices.size()) { - outVertices[1] = blendShape.vertices[index2]; - } else { - // Index isn't in the blend shape so return vertex from mesh - outVertices[1] = mesh.vertices[secondIndex]; - } - outNormal = blendShape.normals[index1]; - return &blendShape.tangents[index1]; - } else { - // Index isn't in blend shape so return nullptr - return (glm::vec3*)nullptr; - } - }); -} - QStringList HFMModel::getJointNames() const { QStringList names; foreach (const HFMJoint& joint, joints) { diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 78f608d72e..1bd87332a1 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -239,9 +239,6 @@ public: graphics::MeshPointer _mesh; bool wasCompressed { false }; - - void createMeshTangents(bool generateFromTexCoords); - void createBlendShapeTangents(bool generateTangents); }; /**jsdoc diff --git a/libraries/model-baker/CMakeLists.txt b/libraries/model-baker/CMakeLists.txt index 50b113976f..6fa7c1815a 100644 --- a/libraries/model-baker/CMakeLists.txt +++ b/libraries/model-baker/CMakeLists.txt @@ -1,6 +1,4 @@ set(TARGET_NAME model-baker) setup_hifi_library() -link_hifi_libraries(shared task gpu graphics) - -include_hifi_library_headers(hfm) +link_hifi_libraries(shared task gpu graphics hfm) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index bf2b21993c..8d1a82518d 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -15,26 +15,65 @@ #include "BakerTypes.h" #include "BuildGraphicsMeshTask.h" +#include "CalculateMeshNormalsTask.h" +#include "CalculateMeshTangentsTask.h" +#include "CalculateBlendshapeNormalsTask.h" +#include "CalculateBlendshapeTangentsTask.h" namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet3, hifi::URL, MeshIndicesToModelNames>; + using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { - auto& hfmModelIn = input; + const auto& hfmModelIn = input; output.edit0() = hfmModelIn->meshes.toStdVector(); output.edit1() = hfmModelIn->originalURL; output.edit2() = hfmModelIn->meshIndicesToModelNames; + auto& blendshapesPerMesh = output.edit3(); + blendshapesPerMesh.reserve(hfmModelIn->meshes.size()); + for (int i = 0; i < hfmModelIn->meshes.size(); i++) { + blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); + } + output.edit4() = hfmModelIn->materials; + } + }; + + class BuildBlendshapesTask { + public: + using Input = VaryingSet3, std::vector>; + using Output = BlendshapesPerMesh; + using JobModel = Job::ModelIO; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMeshIn = input.get0(); + const auto& normalsPerBlendshapePerMesh = input.get1(); + const auto& tangentsPerBlendshapePerMesh = input.get2(); + auto& blendshapesPerMeshOut = output; + + blendshapesPerMeshOut = blendshapesPerMeshIn; + + for (int i = 0; i < (int)blendshapesPerMeshOut.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i]; + auto& blendshapesOut = blendshapesPerMeshOut[i]; + for (int j = 0; j < (int)blendshapesOut.size(); j++) { + const auto& normals = normalsPerBlendshape[j]; + const auto& tangents = tangentsPerBlendshape[j]; + auto& blendshape = blendshapesOut[j]; + blendshape.normals = QVector::fromStdVector(normals); + blendshape.tangents = QVector::fromStdVector(tangents); + } + } } }; class BuildMeshesTask { public: - using Input = VaryingSet4, std::vector, TangentsPerMesh, BlendshapesPerMesh>; + using Input = VaryingSet5, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; using Output = std::vector; using JobModel = Job::ModelIO; @@ -42,13 +81,15 @@ namespace baker { auto& meshesIn = input.get0(); int numMeshes = (int)meshesIn.size(); auto& graphicsMeshesIn = input.get1(); - auto& tangentsPerMeshIn = input.get2(); - auto& blendshapesPerMeshIn = input.get3(); + auto& normalsPerMeshIn = input.get2(); + auto& tangentsPerMeshIn = input.get3(); + auto& blendshapesPerMeshIn = input.get4(); auto meshesOut = meshesIn; for (int i = 0; i < numMeshes; i++) { auto& meshOut = meshesOut[i]; meshOut._mesh = graphicsMeshesIn[i]; + meshOut.normals = QVector::fromStdVector(normalsPerMeshIn[i]); meshOut.tangents = QVector::fromStdVector(tangentsPerMeshIn[i]); meshOut.blendshapes = QVector::fromStdVector(blendshapesPerMeshIn[i]); } @@ -80,17 +121,27 @@ namespace baker { const auto meshesIn = modelPartsIn.getN(0); const auto url = modelPartsIn.getN(1); const auto meshIndicesToModelNames = modelPartsIn.getN(2); + const auto blendshapesPerMeshIn = modelPartsIn.getN(3); + const auto materials = modelPartsIn.getN(4); + + // Calculate normals and tangents for meshes and blendshapes if they do not exist + // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. + const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); + const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, materials).asVarying(); + const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); + const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying(); + const auto normalsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs); + const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, materials).asVarying(); + const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); // Build the graphics::MeshPointer for each hfm::Mesh - const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames).asVarying(); - const auto buildGraphicsMeshOutputs = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); - const auto graphicsMeshes = buildGraphicsMeshOutputs.getN(0); - // TODO: Move tangent/blendshape validation/calculation to an earlier step - const auto tangentsPerMesh = buildGraphicsMeshOutputs.getN(1); - const auto blendshapesPerMesh = buildGraphicsMeshOutputs.getN(2); + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); // Combine the outputs into a new hfm::Model - const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, tangentsPerMesh, blendshapesPerMesh).asVarying(); + const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); + const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); + const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying(); hfmModelOut = model.addJob("BuildModel", buildModelInputs); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 8615c1b17c..5d14ee5420 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -15,10 +15,25 @@ #include namespace baker { + using MeshIndices = std::vector; + using IndicesPerMesh = std::vector>; + using VerticesPerMesh = std::vector>; + using MeshNormals = std::vector; + using NormalsPerMesh = std::vector>; using MeshTangents = std::vector; using TangentsPerMesh = std::vector>; + using Blendshapes = std::vector; using BlendshapesPerMesh = std::vector>; + using BlendshapeVertices = std::vector; + using BlendshapeNormals = std::vector; + using BlendshapeIndices = std::vector; + using VerticesPerBlendshape = std::vector>; + using NormalsPerBlendshape = std::vector>; + using IndicesPerBlendshape = std::vector>; + using BlendshapeTangents = std::vector; + using TangentsPerBlendshape = std::vector>; + using MeshIndicesToModelNames = QHash; }; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 6d351a99e9..370add2c2e 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -26,9 +26,18 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, baker::MeshTangents& meshTangents, baker::Blendshapes& blendshapes) { +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn) { auto graphicsMesh = std::make_shared(); + // Fill tangents with a dummy value to force tangents to be present if there are normals + baker::MeshTangents meshTangents; + if (!meshTangentsIn.empty()) { + meshTangents = meshTangentsIn; + } else { + meshTangents.reserve(meshNormals.size()); + std::fill_n(std::back_inserter(meshTangents), meshNormals.size(), Vectors::UNIT_X); + } + unsigned int totalSourceIndices = 0; foreach(const HFMMeshPart& part, hfmMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); @@ -48,23 +57,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics int numVerts = hfmMesh.vertices.size(); - if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - meshTangents.reserve(hfmMesh.normals.size()); - std::fill_n(std::back_inserter(meshTangents), hfmMesh.normals.size(), Vectors::UNIT_X); - } else { - meshTangents = hfmMesh.tangents.toStdVector(); - } - // Same thing with blend shapes - blendshapes = hfmMesh.blendshapes.toStdVector(); - for (auto& blendShape : blendshapes) { - 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(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); - } - } - // evaluate all attribute elements and data sizes // Position is a vec3 @@ -73,12 +65,12 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) const auto normalElement = HFM_NORMAL_ELEMENT; - const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int normalsSize = (int)meshNormals.size() * normalElement.getSize(); const int tangentsSize = (int)meshTangents.size() * normalElement.getSize(); // If there are normals then there should be tangents assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { - HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in file"); + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in mesh"); } const auto normalsAndTangentsSize = normalsSize + tangentsSize; @@ -124,11 +116,11 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics if (normalsSize > 0) { std::vector normalsAndTangents; - normalsAndTangents.reserve(hfmMesh.normals.size() + (int)meshTangents.size()); - auto normalIt = hfmMesh.normals.constBegin(); + normalsAndTangents.reserve(meshNormals.size() + (int)meshTangents.size()); + auto normalIt = meshNormals.cbegin(); auto tangentIt = meshTangents.cbegin(); for (; - normalIt != hfmMesh.normals.constEnd(); + normalIt != meshNormals.cend(); ++normalIt, ++tangentIt) { #if HFM_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); @@ -212,11 +204,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics auto vertexFormat = std::make_shared(); auto vertexBufferStream = std::make_shared(); - // Decision time: - // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes - // else everything is interleaved in one buffer - - // Default case is no blend shapes gpu::BufferPointer attribBuffer; int totalAttribBufferSize = totalVertsSize; gpu::uint8 posChannel = 0; @@ -244,7 +231,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } } - // Pack normal and Tangent with the rest of atributes if no blend shapes + // Pack normal and Tangent with the rest of atributes if (colorsSize) { vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); bufOffset += colorElement.getSize(); @@ -384,22 +371,21 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - auto& meshes = input.get0(); - auto& url = input.get1(); - auto& meshIndicesToModelNames = input.get2(); + const auto& meshes = input.get0(); + const auto& url = input.get1(); + const auto& meshIndicesToModelNames = input.get2(); + const auto& normalsPerMesh = input.get3(); + const auto& tangentsPerMesh = input.get4(); + + auto& graphicsMeshes = output; - auto& graphicsMeshes = output.edit0(); - auto& tangentsPerMesh = output.edit1(); - auto& blendshapesPerMesh = output.edit2(); int n = (int)meshes.size(); for (int i = 0; i < n; i++) { graphicsMeshes.emplace_back(); auto& graphicsMesh = graphicsMeshes[i]; - tangentsPerMesh.emplace_back(); - blendshapesPerMesh.emplace_back(); // Try to create the graphics::Mesh - buildGraphicsMesh(meshes[i], graphicsMesh, tangentsPerMesh[i], blendshapesPerMesh[i]); + buildGraphicsMesh(meshes[i], graphicsMesh, normalsPerMesh[i], tangentsPerMesh[i]); // Choose a name for the mesh if (graphicsMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h index 3c8985ef9a..bb4136c086 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -20,8 +20,8 @@ class BuildGraphicsMeshTask { public: - using Input = baker::VaryingSet3, hifi::URL, baker::MeshIndicesToModelNames>; - using Output = baker::VaryingSet3, std::vector, std::vector>; + using Input = baker::VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Output = std::vector; using JobModel = baker::Job::ModelIO; void run(const baker::BakeContextPointer& context, const Input& input, Output& output); diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp new file mode 100644 index 0000000000..b908fa9ced --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp @@ -0,0 +1,70 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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 "CalculateBlendshapeNormalsTask.h" + +#include "ModelMath.h" + +void CalculateBlendshapeNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMesh = input.get0(); + const auto& meshes = input.get1(); + auto& normalsPerBlendshapePerMeshOut = output; + + normalsPerBlendshapePerMeshOut.reserve(blendshapesPerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& mesh = meshes[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + normalsPerBlendshapePerMeshOut.emplace_back(); + auto& normalsPerBlendshapeOut = normalsPerBlendshapePerMeshOut[normalsPerBlendshapePerMeshOut.size()-1]; + + normalsPerBlendshapeOut.reserve(blendshapes.size()); + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& normalsIn = blendshape.normals; + // Check if normals are already defined. Otherwise, calculate them from existing blendshape vertices. + if (!normalsIn.empty()) { + normalsPerBlendshapeOut.push_back(normalsIn.toStdVector()); + } else { + // Create lookup to get index in blendshape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + normalsPerBlendshapeOut.emplace_back(); + auto& normals = normalsPerBlendshapeOut[normalsPerBlendshapeOut.size()-1]; + normals.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&reverseIndices, &blendshape, &normals](int normalIndex) /* NormalAccessor */ { + const auto lookupIndex = reverseIndices[normalIndex]; + if (lookupIndex < blendshape.vertices.size()) { + return &normals[lookupIndex]; + } else { + // Index isn't in the blendshape. Request that the normal not be calculated. + return (glm::vec3*)nullptr; + } + }, + [&mesh, &reverseIndices, &blendshape](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + const auto lookupIndex = reverseIndices[vertexIndex]; + if (lookupIndex < blendshape.vertices.size()) { + outVertex = blendshape.vertices[lookupIndex]; + } else { + // Index isn't in the blendshape, so return vertex from mesh + outVertex = mesh.vertices[lookupIndex]; + } + }); + } + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h new file mode 100644 index 0000000000..0eb7d3c4e7 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateBlendshapeNormalsTask_h +#define hifi_CalculateBlendshapeNormalsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape normals if not already present in the blendshape +class CalculateBlendshapeNormalsTask { +public: + using Input = baker::VaryingSet2>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp new file mode 100644 index 0000000000..04e05f0378 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp @@ -0,0 +1,95 @@ +// +// CalculateBlendshapeTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/08. +// Copyright 2019 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 "CalculateBlendshapeTangentsTask.h" + +#include + +#include "ModelMath.h" + +void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerBlendshapePerMesh = input.get0(); + const auto& blendshapesPerMesh = input.get1(); + const auto& meshes = input.get2(); + const auto& materials = input.get3(); + auto& tangentsPerBlendshapePerMeshOut = output; + + tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + const auto& mesh = meshes[i]; + tangentsPerBlendshapePerMeshOut.emplace_back(); + auto& tangentsPerBlendshapeOut = tangentsPerBlendshapePerMeshOut[tangentsPerBlendshapePerMeshOut.size()-1]; + + // Check if we actually need to calculate the tangents, or just append empty arrays + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& tangentsIn = blendshape.tangents; + const auto& normals = normalsPerBlendshape[j]; + tangentsPerBlendshapeOut.emplace_back(); + auto& tangentsOut = tangentsPerBlendshapeOut[tangentsPerBlendshapeOut.size()-1]; + + // Check if we already have tangents + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we can and should calculate tangents (we need normals to calculate the tangents) + if (normals.empty() || !needTangents) { + continue; + } + tangentsOut.resize(normals.size()); + + // Create lookup to get index in blend shape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + baker::calculateTangents(mesh, + [&mesh, &blendshape, &normals, &tangentsOut, &reverseIndices](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + const auto index1 = reverseIndices[firstIndex]; + const auto index2 = reverseIndices[secondIndex]; + + if (index1 < blendshape.vertices.size()) { + outVertices[0] = blendshape.vertices[index1]; + outTexCoords[0] = mesh.texCoords[index1]; + outTexCoords[1] = mesh.texCoords[index2]; + if (index2 < blendshape.vertices.size()) { + outVertices[1] = blendshape.vertices[index2]; + } else { + // Index isn't in the blend shape so return vertex from mesh + outVertices[1] = mesh.vertices[secondIndex]; + } + outNormal = normals[index1]; + return &tangentsOut[index1]; + } else { + // Index isn't in blend shape so return nullptr + return (glm::vec3*)nullptr; + } + }); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h new file mode 100644 index 0000000000..c24b41d2d9 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateBlendshapeTangentsTask_h +#define hifi_CalculateBlendshapeTangentsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape tangents if not already present in the blendshape +class CalculateBlendshapeTangentsTask { +public: + using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, QHash>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp new file mode 100644 index 0000000000..a6884e104d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp @@ -0,0 +1,40 @@ +// +// CalculateMeshNormalsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// Copyright 2019 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 "CalculateMeshNormalsTask.h" + +#include "ModelMath.h" + +void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& meshes = input; + auto& normalsPerMeshOut = output; + + normalsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < (int)meshes.size(); i++) { + const auto& mesh = meshes[i]; + normalsPerMeshOut.emplace_back(); + auto& normalsOut = normalsPerMeshOut[normalsPerMeshOut.size()-1]; + // Only calculate normals if this mesh doesn't already have them + if (!mesh.normals.empty()) { + normalsOut = mesh.normals.toStdVector(); + } else { + normalsOut.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&normalsOut](int normalIndex) /* NormalAccessor */ { + return &normalsOut[normalIndex]; + }, + [&mesh](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + outVertex = mesh.vertices[vertexIndex]; + } + ); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h new file mode 100644 index 0000000000..b32780c68d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h @@ -0,0 +1,30 @@ +// +// CalculateMeshNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateMeshNormalsTask_h +#define hifi_CalculateMeshNormalsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh normals if not already present in the mesh +class CalculateMeshNormalsTask { +public: + using Input = std::vector; + using Output = baker::NormalsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp new file mode 100644 index 0000000000..e94e15507e --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -0,0 +1,65 @@ +// +// CalculateMeshTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// Copyright 2019 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 "CalculateMeshTangentsTask.h" + +#include "ModelMath.h" + +void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerMesh = input.get0(); + const std::vector& meshes = input.get1(); + const auto& materials = input.get2(); + auto& tangentsPerMeshOut = output; + + tangentsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < (int)meshes.size(); i++) { + const auto& mesh = meshes[i]; + const auto& tangentsIn = mesh.tangents; + const auto& normals = normalsPerMesh[i]; + tangentsPerMeshOut.emplace_back(); + auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; + + // Check if we already have tangents and therefore do not need to do any calculation + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we have normals, and if not then tangents can't be calculated + if (normals.empty()) { + continue; + } + + // Check if we actually need to calculate the tangents + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + if (needTangents) { + continue; + } + + tangentsOut.resize(normals.size()); + baker::calculateTangents(mesh, + [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = normals[firstIndex]; + outTexCoords[0] = mesh.texCoords[firstIndex]; + outTexCoords[1] = mesh.texCoords[secondIndex]; + return &(tangentsOut[firstIndex]); + }); + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h new file mode 100644 index 0000000000..b8fdb7d5f4 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h @@ -0,0 +1,32 @@ +// +// CalculateMeshTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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_CalculateMeshTangentsTask_h +#define hifi_CalculateMeshTangentsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh tangents if not already present in the mesh +class CalculateMeshTangentsTask { +public: + using NormalsPerMesh = std::vector>; + + using Input = baker::VaryingSet3, QHash>; + using Output = baker::TangentsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/ModelMath.cpp b/libraries/model-baker/src/model-baker/ModelMath.cpp new file mode 100644 index 0000000000..50e7e89cf4 --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.cpp @@ -0,0 +1,121 @@ +// +// ModelMath.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/18. +// Copyright 2019 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 "ModelMath.h" + +#include +#include "ModelBakerLogging.h" + +namespace baker { + template + const T& checkedAt(const QVector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + const T& checkedAt(const std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + T& checkedAt(std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + void setTangent(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex) { + glm::vec3 vertex[2]; + glm::vec2 texCoords[2]; + glm::vec3 normal; + glm::vec3* tangent = vertexAccessor(firstIndex, secondIndex, vertex, texCoords, normal); + if (tangent) { + glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); + if (glm::length(bitangent) < EPSILON) { + return; + } + glm::vec2 texCoordDelta = texCoords[1] - texCoords[0]; + glm::vec3 normalizedNormal = glm::normalize(normal); + *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); + } + } + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexSetter) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + glm::vec3* n0 = normalAccessor(part.quadIndices[i]); + glm::vec3* n1 = normalAccessor(part.quadIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.quadIndices[i + 2]); + glm::vec3* n3 = normalAccessor(part.quadIndices[i + 3]); + if (!n0 || !n1 || !n2 || !n3) { + // Quad is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; // Assume all vertices in this quad are in the same plane, so only the first three are needed to calculate the normal + vertexSetter(part.quadIndices[i], vertices[0]); + vertexSetter(part.quadIndices[i + 1], vertices[1]); + vertexSetter(part.quadIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = *n3 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + glm::vec3* n0 = normalAccessor(part.triangleIndices[i]); + glm::vec3* n1 = normalAccessor(part.triangleIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.triangleIndices[i + 2]); + if (!n0 || !n1 || !n2) { + // Tri is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; + vertexSetter(part.triangleIndices[i], vertices[0]); + vertexSetter(part.triangleIndices[i + 1], vertices[1]); + vertexSetter(part.triangleIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateNormals: part.triangleIndices.size() is not divisible by three"); + } + } + } + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + setTangent(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1)); + setTangent(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); + setTangent(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); + setTangent(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i)); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + setTangent(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateTangents: part.triangleIndices.size() is not divisible by three"); + } + } + } +} + diff --git a/libraries/model-baker/src/model-baker/ModelMath.h b/libraries/model-baker/src/model-baker/ModelMath.h new file mode 100644 index 0000000000..2a909e6eed --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.h @@ -0,0 +1,34 @@ +// +// ModelMath.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// Copyright 2019 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 "BakerTypes.h" + +namespace baker { + // Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed + using NormalAccessor = std::function; + + // Assigns a vertex to outVertex given the lookup index + using VertexSetter = std::function; + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexAccessor); + + // firstIndex, secondIndex: the vertex indices to be used for calculation + // outVertices: should be assigned a 2 element array containing the vertices at firstIndex and secondIndex + // outTexCoords: same as outVertices but for texture coordinates + // outNormal: reference to the normal of this triangle + // + // Return value: pointer to the tangent you want to be calculated + using IndexAccessor = std::function; + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor); +}; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 9814e358c3..a82931064a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -81,7 +81,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer setShape(shape); if (_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { - // avatar entities entities are always thus, so we cache this fact in _ownershipState + // avatar entities are always thus, so we cache this fact in _ownershipState _ownershipState = EntityMotionState::OwnershipState::Unownable; } @@ -211,6 +211,7 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } if (_entity->isMovingRelativeToParent() || _entity->hasActions() || + _entity->hasGrabs() || _entity->hasAncestorOfType(NestableType::Avatar)) { return MOTION_TYPE_KINEMATIC; } @@ -235,11 +236,19 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { assert(entityTreeIsLocked()); if (_motionType == MOTION_TYPE_KINEMATIC) { BT_PROFILE("kinematicIntegration"); + uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); + if (hasInternalKinematicChanges()) { + // ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: This kinematic body was moved by an Action + // and doesn't require transform update because the body is authoritative and its transform + // has already been copied out --> do no kinematic integration. + clearInternalKinematicChanges(); + _lastKinematicStep = thisStep; + return; + } // This is physical kinematic motion which steps strictly by the subframe count // of the physics simulation and uses full gravity for acceleration. _entity->setAcceleration(_entity->getGravity()); - uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _lastKinematicStep = thisStep; _entity->stepKinematicMotion(dt); @@ -767,6 +776,7 @@ bool EntityMotionState::shouldSendBid() const { // NOTE: this method is only ever called when the entity's simulation is NOT locally owned return _body->isActive() && (_region == workload::Region::R1) + && _ownershipState != EntityMotionState::OwnershipState::Unownable && glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority() && !_entity->getLocked(); } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 653e3f4252..ddf384dc77 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -88,7 +88,6 @@ public: virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; bool shouldSendBid() const; - uint8_t computeFinalBidPriority() const; bool isLocallyOwned() const override; bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() @@ -100,6 +99,7 @@ public: void saveKinematicState(btScalar timeStep) override; protected: + uint8_t computeFinalBidPriority() const; void updateSendVelocities(); uint64_t getNextBidExpiry() const { return _nextBidExpiry; } void initForBid(); diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index a1dc0f8368..fe175a2c7d 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -161,8 +161,9 @@ public: bool hasInternalKinematicChanges() const { return _hasInternalKinematicChanges; } - void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; } - void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } + // these methods are declared const so they can be called inside other const methods + void dirtyInternalKinematicChanges() const { _hasInternalKinematicChanges = true; } + void clearInternalKinematicChanges() const { _hasInternalKinematicChanges = false; } virtual bool isLocallyOwned() const { return false; } virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() @@ -186,8 +187,11 @@ protected: btRigidBody* _body { nullptr }; float _density { 1.0f }; + // ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: These date members allow an Action + // to operate on a kinematic object without screwing up our default kinematic integration + // which is done in the MotionState::getWorldTransform(). mutable uint32_t _lastKinematicStep; - bool _hasInternalKinematicChanges { false }; + mutable bool _hasInternalKinematicChanges { false }; }; using SetOfMotionStates = QSet; diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 76cb4e6e38..f6189121a9 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -100,9 +100,11 @@ void ThreadSafeDynamicsWorld::synchronizeMotionState(btRigidBody* body) { if (body->isKinematicObject()) { ObjectMotionState* objectMotionState = static_cast(body->getMotionState()); if (objectMotionState->hasInternalKinematicChanges()) { - // this is a special case where the kinematic motion has been updated by an Action - // so we supply the body's current transform to the MotionState - objectMotionState->clearInternalKinematicChanges(); + // ACTION_CAN_CONTROL_KINEMATIC_OBJECT_HACK: + // This is a special case where the kinematic motion has been updated by an Action + // so we supply the body's current transform to the MotionState, + // but we DON'T clear the internalKinematicChanges bit here because + // objectMotionState.getWorldTransform() will use and clear it later body->getMotionState()->setWorldTransform(body->getWorldTransform()); } return; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index fde43e7a5b..aa52e57c3f 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -142,6 +142,7 @@ public: // Rendering support virtual void setContext(const gpu::ContextPointer& context) final { _gpuContext = context; } virtual void submitFrame(const gpu::FramePointer& newFrame) = 0; + virtual void captureFrame(const std::string& outputName) const { } virtual float getRenderResolutionScale() const { return _renderResolutionScale; diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 556e305fac..7217a3e5eb 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -42,7 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { auto program = gpu::Shader::createProgram(shader::gpu::program::drawNothing); auto state = std::make_shared(); drawMask(*state); - state->setColorWriteMask(0); + state->setColorWriteMask(gpu::State::WRITE_NONE); _meshStencilPipeline = gpu::Pipeline::create(program, state); } @@ -54,7 +54,7 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { auto program = gpu::Shader::createProgram(shader::render_utils::program::stencil_drawMask); auto state = std::make_shared(); drawMask(*state); - state->setColorWriteMask(0); + state->setColorWriteMask(gpu::State::WRITE_NONE); _paintStencilPipeline = gpu::Pipeline::create(program, state); } diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index b983213e4a..90d7c71c9f 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -70,12 +70,12 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u } FileStorage::FileStorage(const QString& filename) : _file(filename) { - bool opened = _file.open(QFile::ReadWrite); + bool opened = _file.open(QFile::ReadWrite | QFile::Unbuffered); if (opened) { _hasWriteAccess = true; } else { _hasWriteAccess = false; - opened = _file.open(QFile::ReadOnly); + opened = _file.open(QFile::ReadOnly| QFile::Unbuffered); } if (opened) { diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index bf3ff3f324..4c78da7306 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -370,6 +370,9 @@ "priority": { "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." }, + "materialMappingMode": { + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + }, "materialMappingPos": { "tooltip": "The offset position of the bottom left of the material within the parent's UV space." }, @@ -379,6 +382,9 @@ "materialMappingRot": { "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." }, + "materialRepeat": { + "tooltip": "If enabled, the material will repeat, otherwise it will clamp." + }, "followCamera": { "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." }, diff --git a/scripts/system/assets/images/icon-zone.svg b/scripts/system/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/system/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/system/assets/images/materials/GridPattern.json b/scripts/system/assets/images/materials/GridPattern.json new file mode 100644 index 0000000000..468b709ea4 --- /dev/null +++ b/scripts/system/assets/images/materials/GridPattern.json @@ -0,0 +1,13 @@ +{ + "materialVersion": 1, + "materials": { + "albedo": [ + 0.0, + 0.0, + 7.0 + ], + "unlit": true, + "opacity": 0.4, + "albedoMap": "GridPattern.png" + } +} diff --git a/scripts/system/assets/images/materials/GridPattern.png b/scripts/system/assets/images/materials/GridPattern.png new file mode 100644 index 0000000000..2ecc7f8570 Binary files /dev/null and b/scripts/system/assets/images/materials/GridPattern.png differ diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 84143f4c25..2e175d72a4 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -82,13 +82,18 @@ var selectionManager = SelectionManager; var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); -var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); if (properties.type === 'Light') { return { url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; } else { return { url: PARTICLE_SYSTEM_URL, @@ -106,11 +111,15 @@ var gridTool = new GridTool({ }); gridTool.setVisible(false); +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); }); var DEGREES_TO_RADIANS = Math.PI / 180.0; @@ -836,7 +845,7 @@ var toolBar = (function () { dialogWindow.fromQml.connect(fromQml); } }; - }; + } addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); @@ -1492,6 +1501,7 @@ Script.scriptEnding.connect(function () { cleanupModelMenus(); tooltip.cleanup(); selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); Entities.setLightsArePickable(originalLightsArePickable); Overlays.deleteOverlay(importingSVOImageOverlay); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 0bec77aa41..ee95312fa4 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3798,6 +3798,11 @@ function loaded() { if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { return; } + + if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) { + return; + } + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js new file mode 100644 index 0000000000..fe950c2e2b --- /dev/null +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -0,0 +1,255 @@ +"use strict"; + +// entityShapeVisualizer.js +// +// Created by Thijs Wenker on 1/11/19 +// +// Copyright 2019 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 SHAPETYPE_TO_SHAPE = { + "box": "Cube", + "ellipsoid": "Sphere", + "cylinder-y": "Cylinder", +}; + +var REQUESTED_ENTITY_SHAPE_PROPERTIES = [ + 'type', 'shapeType', 'compoundShapeURL', 'localDimensions' +]; + +function getEntityShapePropertiesForType(properties) { + switch (properties.type) { + case "Zone": + if (SHAPETYPE_TO_SHAPE[properties.shapeType]) { + return { + type: "Shape", + shape: SHAPETYPE_TO_SHAPE[properties.shapeType], + localDimensions: properties.localDimensions + }; + } else if (properties.shapeType === "compound") { + return { + type: "Model", + modelURL: properties.compoundShapeURL, + localDimensions: properties.localDimensions + }; + } else if (properties.shapeType === "sphere") { + var sphereDiameter = Math.max(properties.localDimensions.x, properties.localDimensions.y, + properties.localDimensions.z); + return { + type: "Sphere", + modelURL: properties.compoundShapeURL, + localDimensions: {x: sphereDiameter, y: sphereDiameter, z: sphereDiameter} + }; + } + break; + } + + // Default properties + return { + type: "Shape", + shape: "Cube", + localDimensions: properties.localDimensions + }; +} + +function deepEqual(a, b) { + if (a === b) { + return true; + } + + if (typeof(a) !== "object" || typeof(b) !== "object") { + return false; + } + + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + + for (var property in a) { + if (!a.hasOwnProperty(property)) { + continue; + } + if (!b.hasOwnProperty(property)) { + return false; + } + if (!deepEqual(a[property], b[property])) { + return false; + } + } + return true; +} + +/** + * Returns an array of property names which are different in comparison. + * @param propertiesA + * @param propertiesB + * @returns {Array} - array of different property names + */ +function compareEntityProperties(propertiesA, propertiesB) { + var differentProperties = [], + property; + + for (property in propertiesA) { + if (!propertiesA.hasOwnProperty(property)) { + continue; + } + if (!propertiesB.hasOwnProperty(property) || !deepEqual(propertiesA[property], propertiesB[property])) { + differentProperties.push(property); + } + } + for (property in propertiesB) { + if (!propertiesB.hasOwnProperty(property)) { + continue; + } + if (!propertiesA.hasOwnProperty(property)) { + differentProperties.push(property); + } + } + + return differentProperties; +} + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} + +function EntityShape(entityID) { + this.entityID = entityID; + var propertiesForType = getEntityShapePropertiesForType(Entities.getEntityProperties(entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES)); + + this.previousPropertiesForType = propertiesForType; + + this.initialize(propertiesForType); +} + +EntityShape.prototype = { + initialize: function(properties) { + // Create new instance of JS object: + var overlayProperties = deepCopy(properties); + + overlayProperties.localPosition = Vec3.ZERO; + overlayProperties.localRotation = Quat.IDENTITY; + overlayProperties.canCastShadows = false; + overlayProperties.parentID = this.entityID; + overlayProperties.collisionless = true; + this.entity = Entities.addEntity(overlayProperties, "local"); + var PROJECTED_MATERIALS = false; + this.materialEntity = Entities.addEntity({ + type: "Material", + localPosition: Vec3.ZERO, + localRotation: Quat.IDENTITY, + localDimensions: properties.localDimensions, + parentID: this.entity, + priority: 1, + materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", + materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), + }, "local"); + }, + update: function() { + var propertiesForType = getEntityShapePropertiesForType(Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES)); + + var difference = compareEntityProperties(propertiesForType, this.previousPropertiesForType); + + if (deepEqual(difference, ['localDimensions'])) { + this.previousPropertiesForType = propertiesForType; + Entities.editEntity(this.entity, { + localDimensions: propertiesForType.localDimensions, + }); + } else if (difference.length > 0) { + this.previousPropertiesForType = propertiesForType; + this.clear(); + this.initialize(propertiesForType); + } + }, + clear: function() { + Entities.deleteEntity(this.materialEntity); + Entities.deleteEntity(this.entity); + } +}; + +function EntityShapeVisualizer(visualizedTypes) { + this.acceptedEntities = []; + this.ignoredEntities = []; + this.entityShapes = {}; + + this.visualizedTypes = visualizedTypes; +} + +EntityShapeVisualizer.prototype = { + addEntity: function(entityID) { + if (this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID] = new EntityShape(entityID); + + }, + updateEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].update(); + }, + removeEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].clear(); + delete this.entityShapes[entityID]; + }, + cleanup: function() { + Object.keys(this.entityShapes).forEach(function(entityID) { + this.entityShapes[entityID].clear(); + }, this); + this.entityShapes = {}; + }, + setEntities: function(entities) { + var qualifiedEntities = entities.filter(function(entityID) { + if (this.acceptedEntities.indexOf(entityID) !== -1) { + return true; + } + if (this.ignoredEntities.indexOf(entityID) !== -1) { + return false; + } + if (this.visualizedTypes.indexOf(Entities.getEntityProperties(entityID, "type").type) !== -1) { + this.acceptedEntities.push(entityID); + return true; + } + this.ignoredEntities.push(entityID); + return false; + }, this); + + + var newEntries = []; + var updateEntries = []; + + var currentEntries = Object.keys(this.entityShapes); + qualifiedEntities.forEach(function(entityID) { + if (currentEntries.indexOf(entityID) !== -1) { + updateEntries.push(entityID); + } else { + newEntries.push(entityID); + } + }); + + var deleteEntries = currentEntries.filter(function(entityID) { + return updateEntries.indexOf(entityID) === -1; + }); + + deleteEntries.forEach(function(entityID) { + this.removeEntity(entityID); + }, this); + + updateEntries.forEach(function(entityID) { + this.updateEntity(entityID); + }, this); + + newEntries.forEach(function(entityID) { + this.addEntity(entityID); + }, this); + } +}; + +module.exports = EntityShapeVisualizer; diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ddf9a7b373..886f15ded4 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -6,31 +6,38 @@ if (NPM_EXECUTABLE) set_target_properties(jsdoc PROPERTIES FOLDER "Tools") endif() +function(check_test name) + set(RESULT TRUE) + if (BUILD_TOOLS_INCLUDE) + unset(RESULT) + list(FIND BUILD_TOOLS_INCLUDE ${name} BUILD_TOOL_FIND) + if (NOT (${BUILD_TOOL_FIND} EQUAL -1)) + set(RESULT TRUE) + endif() + endif() + set(BUILD_TOOL_RESULT ${RESULT} PARENT_SCOPE) +endfunction() + if (BUILD_TOOLS) - add_subdirectory(udt-test) - set_target_properties(udt-test PROPERTIES FOLDER "Tools") + set(ALL_TOOLS + udt-test + vhacd-util + frame-optimizer + gpu-frame-player + ice-client + ktx-tool + ac-client + skeleton-dump + atp-client + oven + nitpick + ) - add_subdirectory(vhacd-util) - set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") - - add_subdirectory(ice-client) - set_target_properties(ice-client PROPERTIES FOLDER "Tools") - - add_subdirectory(ktx-tool) - set_target_properties(ktx-tool PROPERTIES FOLDER "Tools") - - add_subdirectory(ac-client) - set_target_properties(ac-client PROPERTIES FOLDER "Tools") - - add_subdirectory(skeleton-dump) - set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") - - add_subdirectory(atp-client) - set_target_properties(atp-client PROPERTIES FOLDER "Tools") - - add_subdirectory(oven) - set_target_properties(oven PROPERTIES FOLDER "Tools") - - add_subdirectory(nitpick) - set_target_properties(nitpick PROPERTIES FOLDER "Tools") + foreach(TOOL ${ALL_TOOLS}) + check_test(${TOOL}) + if (${BUILD_TOOL_RESULT}) + add_subdirectory(${TOOL}) + set_target_properties(${TOOL} PROPERTIES FOLDER "Tools") + endif() + endforeach() endif() diff --git a/tools/frame-optimizer/CMakeLists.txt b/tools/frame-optimizer/CMakeLists.txt new file mode 100644 index 0000000000..cc268c5baf --- /dev/null +++ b/tools/frame-optimizer/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME frame-optimizer) + +setup_memory_debugger() +setup_hifi_project(Gui Widgets) +link_hifi_libraries(shared ktx shaders gpu ) +package_libraries_for_deployment() diff --git a/tools/frame-optimizer/src/main.cpp b/tools/frame-optimizer/src/main.cpp new file mode 100644 index 0000000000..a4200c3d97 --- /dev/null +++ b/tools/frame-optimizer/src/main.cpp @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis on 2018/10/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 + +#ifdef Q_OS_WIN +#include +#endif + +#include +#include +#include + + +gpu::IndexOptimizer optimizer= [](gpu::Primitive primitive, uint32_t faceCount, uint32_t indexCount, uint32_t* indices ) { + // FIXME add a triangle index optimizer here +}; + + +void messageHandler(QtMsgType type, const QMessageLogContext &, const QString & message) { + auto messageStr = message.toStdString(); +#ifdef Q_OS_WIN + OutputDebugStringA(messageStr.c_str()); + OutputDebugStringA("\n"); +#endif + std::cerr << messageStr << std::endl; +} + +int main(int argc, char** argv) { + QCoreApplication app(argc, argv); + qInstallMessageHandler(messageHandler); + gpu::optimizeFrame("D:/Frames/20190112_1647.json", optimizer); + return 0; +} diff --git a/tools/gpu-frame-player/CMakeLists.txt b/tools/gpu-frame-player/CMakeLists.txt new file mode 100644 index 0000000000..bd50839f9c --- /dev/null +++ b/tools/gpu-frame-player/CMakeLists.txt @@ -0,0 +1,19 @@ + +set(TARGET_NAME gpu-frame-player) + +setup_memory_debugger() +setup_hifi_project(Gui Widgets) + + +# link in the shared libraries +link_hifi_libraries( + shared ktx shaders gpu +# vk gpu-vk + gl ${PLATFORM_GL_BACKEND} +) + +target_compile_definitions(${TARGET_NAME} PRIVATE USE_GL) +target_opengl() +#target_vulkan() + +package_libraries_for_deployment() diff --git a/tools/gpu-frame-player/src/PlayerWindow.cpp b/tools/gpu-frame-player/src/PlayerWindow.cpp new file mode 100644 index 0000000000..7fbf43139f --- /dev/null +++ b/tools/gpu-frame-player/src/PlayerWindow.cpp @@ -0,0 +1,110 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "PlayerWindow.h" + +#include +#include +#include +#include + +#include + +PlayerWindow::PlayerWindow() { + installEventFilter(this); + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + +#ifdef USE_GL + setSurfaceType(QSurface::OpenGLSurface); +#else + setSurfaceType(QSurface::VulkanSurface); +#endif + + setGeometry(QRect(QPoint(), QSize(800, 600))); + create(); + show(); + // Ensure the window is visible and the GL context is valid + QCoreApplication::processEvents(); + _renderThread.initialize(this); +} + +PlayerWindow::~PlayerWindow() { +} + +bool PlayerWindow::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::Close) { + _renderThread.terminate(); + } + + return QWindow::eventFilter(obj, event); +} + +void PlayerWindow::loadFrame() { + static const QString LAST_FILE_KEY{ "lastFile" }; + auto lastScene = _settings.value(LAST_FILE_KEY); + QString openDir; + if (lastScene.isValid()) { + QFileInfo lastSceneInfo(lastScene.toString()); + if (lastSceneInfo.absoluteDir().exists()) { + openDir = lastSceneInfo.absolutePath(); + } + } + + QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), openDir, tr("GPU Frames (*.json)")); + if (fileName.isNull()) { + return; + } + _settings.setValue(LAST_FILE_KEY, fileName); + loadFrame(fileName); +} + +void PlayerWindow::keyPressEvent(QKeyEvent* event) { + switch (event->key()) { + case Qt::Key_F1: + loadFrame(); + return; + + default: + break; + } +} + +void PlayerWindow::resizeEvent(QResizeEvent* ev) { + _renderThread.resize(ev->size()); +} + +void PlayerWindow::textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) { + QImage image; + QImageReader(filename.c_str()).read(&image); + if (layer > 0) { + return; + } + texture->assignStoredMip(0, image.byteCount(), image.constBits()); +} + +void PlayerWindow::loadFrame(const QString& path) { + auto frame = gpu::readFrame(path.toStdString(), _renderThread._externalTexture, &PlayerWindow::textureLoader); + if (frame) { + _renderThread.submitFrame(frame); + if (!_renderThread.isThreaded()) { + _renderThread.process(); + } + } + if (frame->framebuffer) { + const auto& fbo = *frame->framebuffer; + glm::uvec2 size{ fbo.getWidth(), fbo.getHeight() }; + + auto screenSize = screen()->size(); + static const glm::uvec2 maxSize{ screenSize.width() - 100, screenSize.height() - 100 }; + while (glm::any(glm::greaterThan(size, maxSize))) { + size /= 2; + } + resize(size.x, size.y); + } + _renderThread.submitFrame(frame); +} diff --git a/tools/gpu-frame-player/src/PlayerWindow.h b/tools/gpu-frame-player/src/PlayerWindow.h new file mode 100644 index 0000000000..4dfbca0855 --- /dev/null +++ b/tools/gpu-frame-player/src/PlayerWindow.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once +#include +#include + +#include +#include "RenderThread.h" + +// Create a simple OpenGL window that renders text in various ways +class PlayerWindow : public QWindow { +public: + + PlayerWindow(); + virtual ~PlayerWindow(); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void resizeEvent(QResizeEvent* ev) override; + void loadFrame(); + void loadFrame(const QString& path); + +private: + static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer); + QSettings _settings; + RenderThread _renderThread; +}; diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp new file mode 100644 index 0000000000..608e8f250f --- /dev/null +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -0,0 +1,271 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 "RenderThread.h" +#include +#include + +void RenderThread::submitFrame(const gpu::FramePointer& frame) { + std::unique_lock lock(_frameLock); + _pendingFrames.push(frame); +} + +void RenderThread::resize(const QSize& newSize) { + std::unique_lock lock(_frameLock); + _pendingSize.push(newSize); +} + +void RenderThread::initialize(QWindow* window) { + std::unique_lock lock(_frameLock); + setObjectName("RenderThread"); + Parent::initialize(); + + _window = window; +#ifdef USE_GL + _context.setWindow(window); + _context.create(); + _context.makeCurrent(); + QOpenGLContextWrapper(_context.qglContext()).makeCurrent(_window); + glGenTextures(1, &_externalTexture); + glBindTexture(GL_TEXTURE_2D, _externalTexture); + static const glm::u8vec4 color{ 0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color); + gl::setSwapInterval(0); + // GPU library init + gpu::Context::init(); + _context.makeCurrent(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _context.doneCurrent(); + _context.moveToThread(_thread); +#else + auto size = window->size(); + _extent = vk::Extent2D{ (uint32_t)size.width(), (uint32_t)size.height() }; + + _context.setValidationEnabled(true); + _context.requireExtensions({ + std::string{ VK_KHR_SURFACE_EXTENSION_NAME }, + std::string{ VK_KHR_WIN32_SURFACE_EXTENSION_NAME }, + }); + _context.requireDeviceExtensions({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }); + _context.createInstance(); + _surface = _context.instance.createWin32SurfaceKHR({ {}, GetModuleHandle(NULL), (HWND)window->winId() }); + _context.createDevice(_surface); + _swapchain.setSurface(_surface); + _swapchain.create(_extent, true); + + setupRenderPass(); + setupFramebuffers(); + + acquireComplete = _context.device.createSemaphore(vk::SemaphoreCreateInfo{}); + renderComplete = _context.device.createSemaphore(vk::SemaphoreCreateInfo{}); + + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); +#endif +} + +void RenderThread::setup() { + // Wait until the context has been moved to this thread + { std::unique_lock lock(_frameLock); } + _gpuContext->beginFrame(); + _gpuContext->endFrame(); + +#ifdef USE_GL + _context.makeCurrent(); + glViewport(0, 0, 800, 600); + (void)CHECK_GL_ERROR(); +#endif + _elapsed.start(); +} + +void RenderThread::shutdown() { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _gpuContext->shutdown(); + _gpuContext.reset(); +} + +#ifndef USE_GL +extern vk::CommandBuffer currentCommandBuffer; +#endif + +void RenderThread::renderFrame(gpu::FramePointer& frame) { + ++_presentCount; +#ifdef USE_GL + _context.makeCurrent(); +#endif + _backend->recycle(); + _backend->syncCache(); + + auto windowSize = _window->size(); + +#ifndef USE_GL + auto windowExtent = vk::Extent2D{ (uint32_t)windowSize.width(), (uint32_t)windowSize.height() }; + if (windowExtent != _extent) { + return; + } + + if (_extent != _swapchain.extent) { + _swapchain.create(_extent); + setupFramebuffers(); + return; + } + + static const vk::Offset2D offset; + static const std::array clearValues{ + vk::ClearColorValue(std::array{ { 0.2f, 0.2f, 0.2f, 0.2f } }), + vk::ClearDepthStencilValue({ 1.0f, 0 }), + }; + + auto swapchainIndex = _swapchain.acquireNextImage(acquireComplete).value; + auto framebuffer = _framebuffers[swapchainIndex]; + const auto& commandBuffer = currentCommandBuffer = _context.createCommandBuffer(); + + auto rect = vk::Rect2D{ offset, _extent }; + vk::RenderPassBeginInfo beginInfo{ _renderPass, framebuffer, rect, (uint32_t)clearValues.size(), clearValues.data() }; + commandBuffer.begin(vk::CommandBufferBeginInfo{ vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); + + using namespace vks::debug::marker; + beginRegion(commandBuffer, "executeFrame", glm::vec4{ 1, 1, 1, 1 }); +#endif + + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + } + +#ifdef USE_GL + auto& glbackend = (gpu::gl::GLBackend&)(*_backend); + glm::uvec2 fboSize{ frame->framebuffer->getWidth(), frame->framebuffer->getHeight() }; + auto fbo = glbackend.getFramebufferID(frame->framebuffer); + glDisable(GL_FRAMEBUFFER_SRGB); + glBlitNamedFramebuffer(fbo, 0, 0, 0, fboSize.x, fboSize.y, 0, 0, windowSize.width(), windowSize.height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + (void)CHECK_GL_ERROR(); + _context.swapBuffers(); + _context.doneCurrent(); +#else + endRegion(commandBuffer); + beginRegion(commandBuffer, "renderpass:testClear", glm::vec4{ 0, 1, 1, 1 }); + commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eInline); + commandBuffer.endRenderPass(); + endRegion(commandBuffer); + commandBuffer.end(); + + static const vk::PipelineStageFlags waitFlags{ vk::PipelineStageFlagBits::eBottomOfPipe }; + vk::SubmitInfo submitInfo{ 1, &acquireComplete, &waitFlags, 1, &commandBuffer, 1, &renderComplete }; + vk::Fence frameFence = _context.device.createFence(vk::FenceCreateInfo{}); + _context.queue.submit(submitInfo, frameFence); + _swapchain.queuePresent(renderComplete); + _context.trashCommandBuffers({ commandBuffer }); + _context.emptyDumpster(frameFence); + _context.recycle(); +#endif +} + +bool RenderThread::process() { + std::queue pendingFrames; + std::queue pendingSize; + + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + pendingSize.swap(_pendingSize); + } + + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + _gpuContext->consumeFrameUpdates(_activeFrame); + pendingFrames.pop(); + } + + while (!pendingSize.empty()) { +#ifndef USE_GL + const auto& size = pendingSize.front(); + _extent = { (uint32_t)size.width(), (uint32_t)size.height() }; +#endif + pendingSize.pop(); + } + + if (!_activeFrame) { + QThread::msleep(1); + return true; + } + + renderFrame(_activeFrame); + return true; +} + +#ifndef USE_GL + +void RenderThread::setupFramebuffers() { + // Recreate the frame buffers + _context.trashAll(_framebuffers, [this](const std::vector& framebuffers) { + for (const auto& framebuffer : framebuffers) { + _device.destroy(framebuffer); + } + }); + + vk::ImageView attachment; + vk::FramebufferCreateInfo framebufferCreateInfo; + framebufferCreateInfo.renderPass = _renderPass; + framebufferCreateInfo.attachmentCount = 1; + framebufferCreateInfo.pAttachments = &attachment; + framebufferCreateInfo.width = _extent.width; + framebufferCreateInfo.height = _extent.height; + framebufferCreateInfo.layers = 1; + + // Create frame buffers for every swap chain image + _framebuffers = _swapchain.createFramebuffers(framebufferCreateInfo); +} + +void RenderThread::setupRenderPass() { + if (_renderPass) { + _device.destroy(_renderPass); + } + + vk::AttachmentDescription attachment; + // Color attachment + attachment.format = _swapchain.colorFormat; + attachment.loadOp = vk::AttachmentLoadOp::eClear; + attachment.storeOp = vk::AttachmentStoreOp::eStore; + attachment.initialLayout = vk::ImageLayout::eUndefined; + attachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; + + vk::AttachmentReference colorAttachmentReference; + colorAttachmentReference.attachment = 0; + colorAttachmentReference.layout = vk::ImageLayout::eColorAttachmentOptimal; + + vk::SubpassDescription subpass; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentReference; + vk::SubpassDependency subpassDependency; + subpassDependency.srcSubpass = 0; + subpassDependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + subpassDependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + subpassDependency.dstSubpass = VK_SUBPASS_EXTERNAL; + subpassDependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead; + subpassDependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + + vk::RenderPassCreateInfo renderPassInfo; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &attachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &subpassDependency; + _renderPass = _device.createRenderPass(renderPassInfo); +} +#endif diff --git a/tools/gpu-frame-player/src/RenderThread.h b/tools/gpu-frame-player/src/RenderThread.h new file mode 100644 index 0000000000..7312fece6c --- /dev/null +++ b/tools/gpu-frame-player/src/RenderThread.h @@ -0,0 +1,68 @@ +// +// Created by Bradley Austin Davis on 2018/10/21 +// 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 +// + +#pragma once + +#include + +#include +#include + +#ifdef USE_GL +#include +#include +#include +#else +#include +#include +#endif + +class RenderThread : public GenericThread { + using Parent = GenericThread; +public: + QWindow* _window{ nullptr }; + +#ifdef USE_GL + gl::Context _context; +#else + vks::Context& _context{ vks::Context::get() }; + const vk::Device& _device{ _context.device }; + + vk::SurfaceKHR _surface; + vk::RenderPass _renderPass; + vks::Swapchain _swapchain; + vk::Semaphore acquireComplete, renderComplete; + std::vector _framebuffers; + vk::Extent2D _extent; + + void setupFramebuffers(); + void setupRenderPass(); +#endif + + std::mutex _mutex; + gpu::ContextPointer _gpuContext; // initialized during window creation + std::shared_ptr _backend; + std::atomic _presentCount{ 0 }; + QElapsedTimer _elapsed; + size_t _frameIndex{ 0 }; + std::mutex _frameLock; + std::queue _pendingFrames; + std::queue _pendingSize; + gpu::FramePointer _activeFrame; + uint32_t _externalTexture{ 0 }; + + + void resize(const QSize& newSize); + void setup() override; + bool process() override; + void shutdown() override; + + void submitFrame(const gpu::FramePointer& frame); + void initialize(QWindow* window); + void renderFrame(gpu::FramePointer& frame); +}; diff --git a/tools/gpu-frame-player/src/main.cpp b/tools/gpu-frame-player/src/main.cpp new file mode 100644 index 0000000000..ac48ee2e1c --- /dev/null +++ b/tools/gpu-frame-player/src/main.cpp @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// 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 "PlayerWindow.h" + +Q_DECLARE_LOGGING_CATEGORY(gpu_player_logging) +Q_LOGGING_CATEGORY(gpu_player_logging, "hifi.gpu.player") + +QSharedPointer logger; + +static const QString LAST_FRAME_FILE = "lastFrameFile"; + +static void setup() { + DependencyManager::set(); +} + +int main(int argc, char** argv) { + setupHifiApplication("gpuFramePlayer"); + + QApplication app(argc, argv); + logger.reset(new FileLogger()); + setup(); + PlayerWindow window; + app.exec(); + return 0; +} diff --git a/tools/normalizeFrame.py b/tools/normalizeFrame.py new file mode 100644 index 0000000000..36b2038d5c --- /dev/null +++ b/tools/normalizeFrame.py @@ -0,0 +1,59 @@ +import os +import json +import shutil +import sys + +def scriptRelative(*paths): + scriptdir = os.path.dirname(os.path.realpath(sys.argv[0])) + result = os.path.join(scriptdir, *paths) + result = os.path.realpath(result) + result = os.path.normcase(result) + return result + + + +class FrameProcessor: + def __init__(self, filename): + self.filename = filename + dir, name = os.path.split(self.filename) + self.dir = dir + self.ktxDir = os.path.join(self.dir, 'ktx') + os.makedirs(self.ktxDir, exist_ok=True) + self.resDir = scriptRelative("../interface/resources") + + if (name.endswith(".json")): + self.name = name[0:-5] + else: + self.name = name + self.filename = self.filename + '.json' + + with open(self.filename, 'r') as f: + self.json = json.load(f) + + + def processKtx(self, texture): + if texture is None: return + if not 'ktxFile' in texture: return + sourceKtx = texture['ktxFile'] + if sourceKtx.startswith(':'): + sourceKtx = os.path.join(self.resDir, sourceKtx[3:]) + sourceKtxDir, sourceKtxName = os.path.split(sourceKtx) + destKtx = os.path.join(self.ktxDir, sourceKtxName) + if not os.path.isfile(destKtx): + shutil.copy(sourceKtx, destKtx) + newValue = 'ktx/' + sourceKtxName + texture['ktxFile'] = newValue + + + def process(self): + for texture in self.json['textures']: + self.processKtx(texture) + + with open(self.filename, 'w') as f: + json.dump(self.json, f, indent=2) + +fp = FrameProcessor("D:/Frames/20190110_1635.json") +fp.process() + + +#C:\Users\bdavi\git\hifi\interface\resources\meshes \ No newline at end of file