// // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Camera.hpp" Q_DECLARE_LOGGING_CATEGORY(renderperflogging) Q_LOGGING_CATEGORY(renderperflogging, "hifi.render_perf") static const QString LAST_SCENE_KEY = "lastSceneFile"; static const QString LAST_LOCATION_KEY = "lastLocation"; class ParentFinder : public SpatialParentFinder { public: EntityTreePointer _tree; ParentFinder(EntityTreePointer tree) : _tree(tree) {} SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const override { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { success = true; return parent; } // search entities if (entityTree) { parent = entityTree->findByID(parentID); } else { parent = _tree ? _tree->findEntityByEntityItemID(parentID) : nullptr; } if (!parent.expired()) { success = true; return parent; } success = false; return parent; } }; class QWindowCamera : public SimpleCamera { Key forKey(int key) { switch (key) { case Qt::Key_W: return FORWARD; case Qt::Key_S: return BACK; case Qt::Key_A: return LEFT; case Qt::Key_D: return RIGHT; case Qt::Key_E: return UP; case Qt::Key_C: return DOWN; default: break; } return INVALID; } vec2 _lastMouse; public: void onKeyPress(QKeyEvent* event) { Key k = forKey(event->key()); if (k == INVALID) { return; } keys.set(k); } void onKeyRelease(QKeyEvent* event) { Key k = forKey(event->key()); if (k == INVALID) { return; } keys.reset(k); } void onMouseMove(QMouseEvent* event) { vec2 mouse = toGlm(event->localPos()); vec2 delta = mouse - _lastMouse; auto buttons = event->buttons(); if (buttons & Qt::RightButton) { dolly(delta.y * 0.01f); } else if (buttons & Qt::LeftButton) { //rotate(delta.x * -0.01f); rotate(delta * -0.01f); } else if (buttons & Qt::MiddleButton) { delta.y *= -1.0f; translate(delta * -0.01f); } _lastMouse = mouse; } }; static QString toHumanSize(size_t size, size_t maxUnit = std::numeric_limits::max()) { static const std::vector SUFFIXES{ { "B", "KB", "MB", "GB", "TB", "PB" } }; const size_t maxIndex = std::min(maxUnit, SUFFIXES.size() - 1); size_t suffixIndex = 0; while (suffixIndex < maxIndex && size > 1024) { size >>= 10; ++suffixIndex; } return QString("%1 %2").arg(size).arg(SUFFIXES[suffixIndex]); } extern QThread* RENDER_THREAD; class RenderThread : public GenericThread { using Parent = GenericThread; public: gl::Context _context; gpu::PipelinePointer _presentPipeline; gpu::ContextPointer _gpuContext; // initialized during window creation std::atomic _presentCount; QElapsedTimer _elapsed; std::atomic _fps{ 1 }; RateCounter<200> _fpsCounter; std::mutex _mutex; std::shared_ptr _backend; std::vector _frameTimes; size_t _frameIndex{ 0 }; std::mutex _frameLock; std::queue _pendingFrames; gpu::FramePointer _activeFrame; QSize _size; static const size_t FRAME_TIME_BUFFER_SIZE{ 8192 }; void submitFrame(const gpu::FramePointer& frame) { std::unique_lock lock(_frameLock); _pendingFrames.push(frame); } void initialize(QWindow* window, gl::Context& initContext) { setObjectName("RenderThread"); _context.setWindow(window); _context.create(); _context.makeCurrent(); window->setSurfaceType(QSurface::OpenGLSurface); _context.makeCurrent(_context.qglContext(), window); gl::setSwapInterval(0); // GPU library init gpu::Context::init(); _gpuContext = std::make_shared(); _backend = _gpuContext->getBackend(); _context.makeCurrent(); DependencyManager::get()->init(); _context.makeCurrent(); initContext.create(); _context.doneCurrent(); std::unique_lock lock(_mutex); Parent::initialize(); _context.moveToThread(_thread); } void setup() override { RENDER_THREAD = QThread::currentThread(); // Wait until the context has been moved to this thread { std::unique_lock lock(_mutex); } _context.makeCurrent(); _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::display_plugins::program::SrgbToLinear); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); _presentPipeline = gpu::Pipeline::create(program, state); } //_textOverlay = new TextOverlay(glm::uvec2(800, 600)); glViewport(0, 0, 800, 600); (void)CHECK_GL_ERROR(); _elapsed.start(); } void shutdown() override { _activeFrame.reset(); while (!_pendingFrames.empty()) { _gpuContext->consumeFrameUpdates(_pendingFrames.front()); _pendingFrames.pop(); } _presentPipeline.reset(); _gpuContext.reset(); } void renderFrame(gpu::FramePointer& frame) { ++_presentCount; _context.makeCurrent(); _backend->recycle(); _backend->syncCache(); if (frame && !frame->batches.empty()) { _gpuContext->executeFrame(frame); { auto geometryCache = DependencyManager::get(); gpu::Batch presentBatch; presentBatch.setViewportTransform({ 0, 0, _size.width(), _size.height() }); presentBatch.enableStereo(false); presentBatch.resetViewTransform(); presentBatch.setFramebuffer(gpu::FramebufferPointer()); presentBatch.setResourceTexture(0, frame->framebuffer->getRenderBuffer(0)); presentBatch.setPipeline(_presentPipeline); presentBatch.draw(gpu::TRIANGLE_STRIP, 4); _gpuContext->executeBatch(presentBatch); } (void)CHECK_GL_ERROR(); } _context.makeCurrent(); _context.swapBuffers(); _fpsCounter.increment(); static size_t _frameCount{ 0 }; ++_frameCount; if (_elapsed.elapsed() >= 500) { _fps = _fpsCounter.rate(); _frameCount = 0; _elapsed.restart(); } (void)CHECK_GL_ERROR(); _context.doneCurrent(); } void report() { uint64_t total = 0; for (const auto& t : _frameTimes) { total += t; } auto averageFrameTime = total / FRAME_TIME_BUFFER_SIZE; qDebug() << "Average frame " << averageFrameTime; std::list sortedHighFrames; for (const auto& t : _frameTimes) { if (t > averageFrameTime * 6) { sortedHighFrames.push_back(t); } } sortedHighFrames.sort(); for (const auto& t : sortedHighFrames) { qDebug() << "Long frame " << t; } } bool process() override { std::queue pendingFrames; { std::unique_lock lock(_frameLock); pendingFrames.swap(_pendingFrames); } while (!pendingFrames.empty()) { _activeFrame = pendingFrames.front(); if (_activeFrame) { _gpuContext->consumeFrameUpdates(_activeFrame); } pendingFrames.pop(); } if (!_activeFrame) { QThread::msleep(1); return true; } { auto start = usecTimestampNow(); renderFrame(_activeFrame); auto duration = usecTimestampNow() - start; auto frameBufferIndex = _frameIndex % FRAME_TIME_BUFFER_SIZE; _frameTimes[frameBufferIndex] = duration; ++_frameIndex; if (0 == _frameIndex % FRAME_TIME_BUFFER_SIZE) { report(); } } return true; } }; class TestActionFactory : public EntityDynamicFactoryInterface { public: virtual EntityDynamicPointer factory(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity, QVariantMap arguments) override { return EntityDynamicPointer(); } virtual EntityDynamicPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override { return nullptr; } }; QSharedPointer logger; OffscreenGLCanvas* _chromiumShareContext{ nullptr }; Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext* context); // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow, public AbstractViewStateInterface { protected: void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { viewOut = _viewFrustum; } const ConicalViewFrustums& getConicalViews() const override { return _view; } QThread* getMainThread() override { return QThread::currentThread(); } PickRay computePickRay(float x, float y) const override { return PickRay(); } glm::vec3 getAvatarPosition() const override { return vec3(); } void postLambdaEvent(const std::function& f) override {} void sendLambdaEvent(const std::function& f) override {} qreal getDevicePixelRatio() override { return 1.0f; } render::ScenePointer getMain3DScene() override { return _main3DScene; } render::EnginePointer getRenderEngine() override { return _renderEngine; } std::map> _postUpdateLambdas; void pushPostUpdateLambda(void* key, const std::function& func) override { _postUpdateLambdas[key] = func; } bool isHMDMode() const override { return false; } public: //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" static void setup() { DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(NodeType::Agent); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); } QTestWindow() { installEventFilter(this); _camera.movementSpeed = 50.0f; QThreadPool::globalInstance()->setMaxThreadCount(2); QThread::currentThread()->setPriority(QThread::HighestPriority); AbstractViewStateInterface::setInstance(this); _octree = DependencyManager::set(false, this, nullptr); _octree->init(); DependencyManager::set(_octree->getTree()); auto nodeList = DependencyManager::get(); NodePermissions permissions; permissions.setAll(true); nodeList->setPermissions(permissions); { SimpleEntitySimulationPointer simpleSimulation{ new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(_octree->getTree()); _octree->getTree()->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } DependencyManager::set(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); _renderThread._size = _size; setGeometry(QRect(QPoint(), _size)); create(); show(); QCoreApplication::processEvents(); // Create the initial context _renderThread.initialize(this, _initContext); _initContext.makeCurrent(); if (nsightActive()) { // Prevent web entities from rendering REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, WebEntityItem::factory); } else { _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->setObjectName("ChromiumShareContext"); _chromiumShareContext->create(_initContext.qglContext()); _chromiumShareContext->makeCurrent(); qt_gl_set_global_share_context(_chromiumShareContext->getContext()); // Make sure all QML surfaces share the main thread GL context OffscreenQmlSurface::setSharedContext(_initContext.qglContext()); _initContext.makeCurrent(); } // FIXME use a wait condition QThread::msleep(1000); _renderThread.submitFrame(gpu::FramePointer()); _initContext.makeCurrent(); DependencyManager::get()->initializeShapePipelines(); // Render engine init static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); _renderEngine->addJob("UpdateScene"); _renderEngine->addJob("RenderMainView", _cullFunctor, isDeferred); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); // Render engine library init reloadScene(); restorePosition(); QTimer* timer = new QTimer(this); timer->setInterval(0); // Qt::CoarseTimer acceptable connect(timer, &QTimer::timeout, this, [this] { draw(); }); timer->start(); _ready = true; } virtual ~QTestWindow() { getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts _renderEngine.reset(); _main3DScene.reset(); EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(nullptr); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::get()->cleanup(); // remove the NodeList from the DependencyManager DependencyManager::destroy(); } void loadCommands(const QString& filename) { QFileInfo fileInfo(filename); if (!fileInfo.exists()) { return; } _commandPath = fileInfo.absolutePath(); _commands = FileUtils::readLines(filename); _commandIndex = 0; } protected: bool eventFilter(QObject* obj, QEvent* event) override { if (event->type() == QEvent::Close) { _renderThread.terminate(); } return QWindow::eventFilter(obj, event); } void keyPressEvent(QKeyEvent* event) override { switch (event->key()) { case Qt::Key_F1: importScene(); return; case Qt::Key_F2: reloadScene(); return; case Qt::Key_F4: cycleMode(); return; case Qt::Key_F5: goTo(); return; case Qt::Key_F6: savePosition(); return; case Qt::Key_F7: restorePosition(); return; case Qt::Key_F8: resetPosition(); return; case Qt::Key_F9: toggleCulling(); return; case Qt::Key_Home: gpu::Texture::setAllowedGPUMemoryUsage(0); return; case Qt::Key_End: gpu::Texture::setAllowedGPUMemoryUsage(MB_TO_BYTES(64)); return; default: break; } _camera.onKeyPress(event); } void keyReleaseEvent(QKeyEvent* event) override { _camera.onKeyRelease(event); } void mouseMoveEvent(QMouseEvent* event) override { _camera.onMouseMove(event); } void resizeEvent(QResizeEvent* ev) override { resizeWindow(ev->size()); } private: static bool cull(const RenderArgs* args, const AABox& bounds) { float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); return (renderAccuracy > 0.0f); } uint16_t _fps; void draw() { if (!_ready) { return; } if (!isVisible()) { return; } if (_renderCount.load() != 0 && _renderCount.load() >= _renderThread._presentCount.load()) { return; } _renderCount = _renderThread._presentCount.load(); update(); _initContext.makeCurrent(); RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, getPerspectiveAccuracyAngleTan(DEFAULT_OCTREE_SIZE_SCALE, 0), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); QSize windowSize = _size; if (_renderMode == NORMAL) { renderArgs._context->enableStereo(false); } else { renderArgs._context->enableStereo(true); mat4 eyeOffsets[2]; mat4 eyeProjections[2]; if (_renderMode == STEREO) { for (size_t i = 0; i < 2; ++i) { eyeProjections[i] = _viewFrustum.getProjection(); } } else if (_renderMode == HMD) { eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, 0.0149999997, 1.0 }; eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, 0.0149999997, 1.0 }; eyeProjections[0][0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; eyeProjections[0][1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; eyeProjections[0][2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; eyeProjections[0][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; eyeProjections[1][0] = vec4{ 0.752847493, 0.000000000, 0.000000000, 0.000000000 }; eyeProjections[1][1] = vec4{ 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; eyeProjections[1][2] = vec4{ 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; eyeProjections[1][3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; windowSize = { 2048, 2048 }; } renderArgs._context->setStereoProjections(eyeProjections); renderArgs._context->setStereoViews(eyeOffsets); } auto framebufferCache = DependencyManager::get(); framebufferCache->setFrameBufferSize(windowSize); renderArgs._blitFramebuffer = framebufferCache->getFramebuffer(); // Viewport is assigned to the size of the framebuffer renderArgs._viewport = ivec4(0, 0, windowSize.width(), windowSize.height()); renderArgs.setViewFrustum(_viewFrustum); renderArgs._scene = _main3DScene; // Final framebuffer that will be handled to the display-plugin render(&renderArgs); if (_fps != _renderThread._fps) { _fps = _renderThread._fps; updateText(); } } private: class EntityUpdateOperator : public RecurseOctreeOperator { public: EntityUpdateOperator(const qint64& now) : now(now) {} bool preRecursion(const OctreeElementPointer& element) override { return true; } bool postRecursion(const OctreeElementPointer& element) override { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { if (!entityItem->isParentIDValid()) { return; // we weren't able to resolve a parent from _parentID, so don't save this entity. } entityItem->update(now); }); return true; } const qint64& now; }; void updateText() { QString title = QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4 Max GPU %5") .arg(_fps) .arg(_cullingEnabled) .arg(toHumanSize(gpu::Context::getTextureGPUMemSize(), 2)) .arg(toHumanSize(gpu::Texture::getTextureCPUMemSize(), 2)) .arg(toHumanSize(gpu::Texture::getAllowedGPUMemoryUsage(), 2)); setTitle(title); #if 0 { _textBlocks.erase(TextBlock::Info); auto& infoTextBlock = _textBlocks[TextBlock::Info]; infoTextBlock.push_back({ vec2(98, 10), "FPS: ", TextOverlay::alignRight }); infoTextBlock.push_back({ vec2(100, 10), std::to_string((uint32_t)_fps), TextOverlay::alignLeft }); infoTextBlock.push_back({ vec2(98, 30), "Culling: ", TextOverlay::alignRight }); infoTextBlock.push_back({ vec2(100, 30), _cullingEnabled ? "Enabled" : "Disabled", TextOverlay::alignLeft }); } #endif #if 0 _textOverlay->beginTextUpdate(); for (const auto& e : _textBlocks) { for (const auto& b : e.second) { _textOverlay->addText(b.text, b.position, b.alignment); } } _textOverlay->endTextUpdate(); #endif } void runCommand(const QString& command) { qDebug() << "Running command: " << command; QStringList commandParams = command.split(QRegularExpression(QString("\\s"))); QString verb = commandParams[0].toLower(); if (verb == "loop") { if (commandParams.length() > 1) { int maxLoops = commandParams[1].toInt(); if (maxLoops < ++_commandLoops) { qDebug() << "Exceeded loop count"; return; } } _commandIndex = 0; } else if (verb == "wait") { if (commandParams.length() < 2) { qDebug() << "No wait time specified"; return; } int seconds = commandParams[1].toInt(); _nextCommandTime = usecTimestampNow() + seconds * USECS_PER_SECOND; } else if (verb == "load") { if (commandParams.length() < 2) { qDebug() << "No load file specified"; return; } QString file = commandParams[1]; if (QFileInfo(file).isRelative()) { file = _commandPath + "/" + file; } if (!QFileInfo(file).exists()) { qDebug() << "Cannot find scene file " + file; return; } importScene(file); } else if (verb == "go") { if (commandParams.length() < 2) { qDebug() << "No destination specified for go command"; return; } parsePath(commandParams[1]); } else { qDebug() << "Unknown command " << command; } } void runNextCommand(quint64 now) { if (_commands.empty()) { return; } if (_commandIndex >= _commands.size()) { _commands.clear(); return; } if (now < _nextCommandTime) { return; } _nextCommandTime = 0; QString command = _commands[_commandIndex++]; runCommand(command); } void update() { auto now = usecTimestampNow(); static auto last = now; runNextCommand(now); float delta = now - last; // Update the camera _camera.update(delta / USECS_PER_SECOND); { _viewFrustum = ViewFrustum(); _viewFrustum.setProjection(_camera.matrices.perspective); auto view = glm::inverse(_camera.matrices.view); _viewFrustum.setPosition(glm::vec3(view[3])); _viewFrustum.setOrientation(glm::quat_cast(view)); // Failing to do the calculation of the bound planes causes everything to be considered inside the frustum if (_cullingEnabled) { _viewFrustum.calculate(); } } EntityUpdateOperator updateOperator(now); //getEntities()->getTree()->recurseTreeWithOperator(&updateOperator); { for (auto& iter : _postUpdateLambdas) { iter.second(); } _postUpdateLambdas.clear(); } last = now; getEntities()->update(false); { PerformanceTimer perfTimer("SceneProcessTransaction"); _main3DScene->processTransactionQueue(); } } void render(RenderArgs* renderArgs) { auto& gpuContext = renderArgs->_context; gpuContext->beginFrame(); gpu::doInBatch("QTestWindow::render", gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); PROFILE_RANGE(render, __FUNCTION__); PerformanceTimer perfTimer("draw"); // The pending changes collecting the changes here render::Transaction transaction; { PerformanceTimer perfTimer("SceneProcessTransaction"); _main3DScene->enqueueTransaction(transaction); _main3DScene->processTransactionQueue(); } // For now every frame pass the renderContext { PerformanceTimer perfTimer("EngineRun"); _renderEngine->getRenderContext()->args = renderArgs; // Before the deferred pass, let's try to use the render engine _renderEngine->run(); } auto frame = gpuContext->endFrame(); frame->framebuffer = renderArgs->_blitFramebuffer; frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) { DependencyManager::get()->releaseFramebuffer(framebuffer); }; _renderThread.submitFrame(frame); if (!_renderThread.isThreaded()) { _renderThread.process(); } } void resizeWindow(const QSize& size) { _size = size; _camera.setAspectRatio((float)_size.width() / (float)_size.height()); if (!_ready) { return; } _renderThread._size = size; //_textOverlay->resize(toGlm(_size)); //glViewport(0, 0, size.width(), size.height()); } void parsePath(const QString& viewpointString) { static const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; static const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*"; static const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)"; static const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + "\\s*$"; static QRegExp orientationRegex(QUAT_REGEX_STRING); static QRegExp positionRegex(POSITION_REGEX_STRING); if (positionRegex.indexIn(viewpointString) != -1) { // we have at least a position, so emit our signal to say we need to change position glm::vec3 newPosition(positionRegex.cap(1).toFloat(), positionRegex.cap(2).toFloat(), positionRegex.cap(3).toFloat()); _camera.setPosition(newPosition); if (!glm::any(glm::isnan(newPosition))) { // we may also have an orientation if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { glm::vec4 v = glm::vec4(orientationRegex.cap(1).toFloat(), orientationRegex.cap(2).toFloat(), orientationRegex.cap(3).toFloat(), orientationRegex.cap(4).toFloat()); if (!glm::any(glm::isnan(v))) { _camera.setRotation(glm::quat(v.w, v.x, v.y, v.z)); } } } } } void importScene(const QString& fileName) { auto assetClient = DependencyManager::get(); QFileInfo fileInfo(fileName); QString atpPath = fileInfo.absolutePath() + "/" + fileInfo.baseName() + ".atp"; qDebug() << atpPath; QFileInfo atpPathInfo(atpPath); if (atpPathInfo.exists()) { QString atpUrl = QUrl::fromLocalFile(atpPath).toString(); DependencyManager::get()->setUrlPrefixOverride("atp:/", atpUrl + "/"); } _octree->clear(); _octree->getTree()->readFromURL(fileName); } void importScene() { auto lastScene = _settings.value(LAST_SCENE_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("Hifi Exports (*.json *.svo)")); if (fileName.isNull()) { return; } _settings.setValue(LAST_SCENE_KEY, fileName); importScene(fileName); } void goTo() { QString destination = QInputDialog::getText(nullptr, tr("Go To Location"), "Enter path"); if (destination.isNull()) { return; } parsePath(destination); } void reloadScene() { QVariant lastScene = _settings.value(LAST_SCENE_KEY); if (lastScene.isValid()) { importScene(lastScene.toString()); } } void savePosition() { // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 glm::quat q = _camera.getOrientation(); glm::vec3 v = _camera.position; QString viewpoint = QString("/%1,%2,%3/%4,%5,%6,%7").arg(v.x).arg(v.y).arg(v.z).arg(q.x).arg(q.y).arg(q.z).arg(q.w); _settings.setValue(LAST_LOCATION_KEY, viewpoint); _camera.setRotation(q); } void restorePosition() { // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 QVariant viewpoint = _settings.value(LAST_LOCATION_KEY); if (viewpoint.isValid()) { parsePath(viewpoint.toString()); } } void resetPosition() { _camera.setRotation(quat()); _camera.setPosition(vec3()); } void toggleCulling() { _cullingEnabled = !_cullingEnabled; } void cycleMode() { static auto defaultProjection = SimpleCamera().matrices.perspective; _renderMode = (RenderMode)((_renderMode + 1) % RENDER_MODE_COUNT); if (_renderMode == HMD) { _camera.matrices.perspective[0] = vec4{ 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; _camera.matrices.perspective[1] = vec4{ 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; _camera.matrices.perspective[2] = vec4{ -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; _camera.matrices.perspective[3] = vec4{ 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; } else { _camera.matrices.perspective = defaultProjection; _camera.setAspectRatio((float)_size.width() / (float)_size.height()); } } QSharedPointer getEntities() { return _octree; } private: render::CullFunctor _cullFunctor{ [&](const RenderArgs* args, const AABox& bounds) -> bool { if (_cullingEnabled) { return cull(args, bounds); } else { return true; } } }; render::EnginePointer _renderEngine{ new render::RenderEngine() }; render::ScenePointer _main3DScene{ new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; QSize _size; QSettings _settings; std::atomic _renderCount{ 0 }; gl::OffscreenContext _initContext; RenderThread _renderThread; QWindowCamera _camera; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. graphics::SunSkyStage _sunSkyStage; graphics::LightPointer _globalLight{ std::make_shared() }; bool _ready{ false }; EntitySimulationPointer _entitySimulation; ConicalViewFrustums _view; QStringList _commands; QString _commandPath; int _commandLoops{ 0 }; int _commandIndex{ -1 }; uint64_t _nextCommandTime{ 0 }; //TextOverlay* _textOverlay; static bool _cullingEnabled; enum RenderMode { NORMAL = 0, STEREO, HMD, RENDER_MODE_COUNT }; RenderMode _renderMode{ NORMAL }; QSharedPointer _octree; }; bool QTestWindow::_cullingEnabled = true; const char* LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; int main(int argc, char** argv) { setupHifiApplication("RenderPerf"); QApplication app(argc, argv); logger.reset(new FileLogger()); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow::setup(); QTestWindow window; //window.loadCommands("C:/Users/bdavis/Git/dreaming/exports2/commands.txt"); app.exec(); return 0; }