347 lines · 12.1 KB · C++
RawDownload
////  GraphicsEngine.cpp////  Created by Sam Gateau on 29/6/2018.//  Copyright 2018 High Fidelity, Inc.//  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0//#include "GraphicsEngine.h" #include <shared/GlobalAppProperties.h> #include "WorldBox.h"#include "LODManager.h" #include <GeometryCache.h>#include <TextureCache.h>#include <FramebufferCache.h>#include <UpdateSceneTask.h>#include <RenderViewTask.h>#include <SecondaryCamera.h> #include "RenderEventHandler.h" #include <gpu/Batch.h>#include <gpu/Context.h>#include <gpu/gl/GLBackend.h>#ifndef USE_GL#include <gpu/vk/VKBackend.h>#endif#include <display-plugins/DisplayPlugin.h> #include <display-plugins/CompositorHelper.h>#include <QMetaObject>#include "ui/Stats.h"#include "Application.h" GraphicsEngine::GraphicsEngine() {    const QString SPLASH_SKYBOX { "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };    _splashScreen->parse(SPLASH_SKYBOX);    const QUrl SPLASH_IMAGE { PathUtils::resourcesUrl("images/splashShaders.png") };    _texture = DependencyManager::get<TextureCache>()->getTexture(SPLASH_IMAGE);} GraphicsEngine::~GraphicsEngine() {} #ifdef USE_GLvoid GraphicsEngine::initializeGPU(GLWidget* primaryWidget) {#elsevoid GraphicsEngine::initializeGPU(VKWidget* primaryWidget) {#endif     _renderEventHandler = new RenderEventHandler(        [this]() { return this->shouldPaint(); },        [this]() { this->render_performFrame(); }    );     // Requires the window context, because that's what's used in the actual rendering    // and the GPU backend will make things like the VAO which cannot be shared across    // contexts    primaryWidget->makeCurrent();#ifdef USE_GL    gpu::Context::init<gpu::gl::GLBackend>();#else    gpu::Context::init<gpu::vk::VKBackend>();#endif    primaryWidget->makeCurrent();    _gpuContext = std::make_shared<gpu::Context>(); #ifndef Q_OS_ANDROID    _gpuContext->pushProgramsToSync(shader::startupPrograms(), [this] {        _programsCompiled.store(true);    }, 1);#endif     DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);} void GraphicsEngine::initializeRender() {     // Set up the render engine    render::CullFunctor cullFunctor = LODManager::shouldRender;    _renderEngine->addJob<UpdateSceneTask>("UpdateScene");#ifndef Q_OS_ANDROID    _renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor);#endif    _renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);    _renderEngine->load();    _renderEngine->registerScene(_renderScene);     // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.    DependencyManager::get<GeometryCache>()->initializeShapePipelines();} void GraphicsEngine::startup() {    static_cast<RenderEventHandler*>(_renderEventHandler)->resumeThread();} void GraphicsEngine::shutdown() {    // The cleanup process enqueues the transactions but does not process them.  Calling this here will force the actual    // removal of the items.    // See https://highfidelity.fogbugz.com/f/cases/5328    _renderScene->enqueueFrame(); // flush all the transactions    _renderScene->processTransactionQueue(); // process and apply deletions     _gpuContext->shutdown();      // shutdown render engine    _renderScene = nullptr;    _renderEngine = nullptr;     _renderEventHandler->deleteLater();}  void GraphicsEngine::render_runRenderFrame(RenderArgs* renderArgs) {    PROFILE_RANGE(render, __FUNCTION__);    PerformanceTimer perfTimer("render");     // Make sure the WorldBox is in the scene    // For the record, this one RenderItem is the first one we created and added to the scene.    // We could move that code elsewhere but you know...    if (!render::Item::isValidID(WorldBoxRenderData::_item)) {        render::Transaction transaction;        auto worldBoxRenderData = std::make_shared<WorldBoxRenderData>();        auto worldBoxRenderPayload = std::make_shared<WorldBoxRenderData::Payload>(worldBoxRenderData);         WorldBoxRenderData::_item = _renderScene->allocateID();         transaction.resetItem(WorldBoxRenderData::_item, worldBoxRenderPayload);        _renderScene->enqueueTransaction(transaction);    }     {        _renderEngine->getRenderContext()->args = renderArgs;        _renderEngine->run();    }} static const unsigned int THROTTLED_SIM_FRAMERATE = 15;static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE; bool GraphicsEngine::shouldPaint() const {    auto displayPlugin = qApp->getActiveDisplayPlugin();    if (!displayPlugin) {        // We're shutting down        return false;    } #ifdef DEBUG_PAINT_DELAY        static uint64_t paintDelaySamples{ 0 };        static uint64_t paintDelayUsecs{ 0 };         paintDelayUsecs += displayPlugin->getPaintDelayUsecs();         static const int PAINT_DELAY_THROTTLE = 1000;        if (++paintDelaySamples % PAINT_DELAY_THROTTLE == 0) {            qCDebug(interfaceapp).nospace() <<                "Paint delay (" << paintDelaySamples << " samples): " <<                (float)paintDelaySamples / paintDelayUsecs << "us";        }#endif     // Throttle if requested    //if (displayPlugin->isThrottled() && (_graphicsEngine._renderEventHandler->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {    //TODO: this may be obsolete? Isn't framerate managed in a different way now?    if (displayPlugin->isThrottled() &&            (static_cast<RenderEventHandler*>(_renderEventHandler)->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {        return false;    }     return true;} bool GraphicsEngine::checkPendingRenderEvent() {    bool expected = false;    return (_renderEventHandler && static_cast<RenderEventHandler*>(_renderEventHandler)->_pendingRenderEvent.compare_exchange_strong(expected, true));} void GraphicsEngine::render_performFrame() {    // Some plugins process message events, allowing paintGL to be called reentrantly.     _renderFrameCount++;     auto lastPaintBegin = usecTimestampNow();    PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_renderFrameCount);    PerformanceTimer perfTimer("paintGL");     DisplayPluginPointer displayPlugin;    {        PROFILE_RANGE(render, "/getActiveDisplayPlugin");        displayPlugin = qApp->getActiveDisplayPlugin();        if (!displayPlugin) {            // We're shutting down            return;        }    }     {        PROFILE_RANGE(render, "/pluginBeginFrameRender");        // If a display plugin loses its underlying support, it        // needs to be able to signal us to not use it        if (!displayPlugin->beginFrameRender(_renderFrameCount)) {            QMetaObject::invokeMethod(qApp, "updateDisplayMode");            return;        }    }     RenderArgs renderArgs;    glm::mat4  HMDSensorPose;    glm::mat4  eyeToWorld;    glm::mat4  sensorToWorld;    ViewFrustum viewFrustum;     bool isStereo;    glm::mat4  stereoEyeOffsets[2];    glm::mat4  stereoEyeProjections[2];     {        QMutexLocker viewLocker(&_renderArgsMutex);        renderArgs = _appRenderArgs._renderArgs;         // don't render if there is no context.        if (!_appRenderArgs._renderArgs._context) {            return;        }         HMDSensorPose = _appRenderArgs._headPose;        eyeToWorld = _appRenderArgs._eyeToWorld;        sensorToWorld = _appRenderArgs._sensorToWorld;        isStereo = _appRenderArgs._isStereo;        for_each_eye([&](Eye eye) {            stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];            stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[eye];        });        viewFrustum = _appRenderArgs._renderArgs.getViewFrustum();    }     {        PROFILE_RANGE(render, "/gpuContextReset");        getGPUContext()->beginFrame(_appRenderArgs._view, HMDSensorPose);        // Reset the gpu::Context Stages        // Back to the default framebuffer;        gpu::doInBatch("Application_render::gpuContextReset", getGPUContext(), [&](gpu::Batch& batch) {            batch.resetStages();        });         if (isStereo) {            renderArgs._context->enableStereo(true);            renderArgs._context->setStereoProjections(stereoEyeProjections);            renderArgs._context->setStereoViews(stereoEyeOffsets);        }    }     gpu::FramebufferPointer finalFramebuffer;    QSize finalFramebufferSize;    {        PROFILE_RANGE(render, "/getOutputFramebuffer");        // Primary rendering pass        auto framebufferCache = DependencyManager::get<FramebufferCache>();        finalFramebufferSize = framebufferCache->getFrameBufferSize();        // Final framebuffer that will be handed to the display-plugin        finalFramebuffer = framebufferCache->getFramebuffer();    }     std::queue<Application::SnapshotOperator> snapshotOperators;    if (!_programsCompiled.load()) {        if (_texture->isLoaded()) {            gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {                batch.setFramebuffer(finalFramebuffer);                batch.enableSkybox(true);                batch.enableStereo(isStereo);                batch.clearDepthStencilFramebuffer(1.0, 0);                batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });                batch.setResourceTexture(0, _texture->getGPUTexture());                _splashScreen->render(batch, viewFrustum, renderArgs._renderMethod == RenderArgs::RenderMethod::FORWARD, render::RenderEngine::TS_BACKGROUND_VIEW);            });        }    } else {        {            PROFILE_RANGE(render, "/renderOverlay");            PerformanceTimer perfTimer("renderOverlay");            // NOTE: There is no batch associated with this renderArgs            // the ApplicationOverlay class assumes its viewport is set up to be the device size            renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize());            qApp->getApplicationOverlay().renderOverlay(&renderArgs);        }         {            PROFILE_RANGE(render, "/updateCompositor");            qApp->getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);        }         {            PROFILE_RANGE(render, "/runRenderFrame");            renderArgs._hudOperator = qApp->getApplicationOverlay().enabled() ? displayPlugin->getHUDOperator() : nullptr;            renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();            renderArgs._takingSnapshot = qApp->takeSnapshotOperators(snapshotOperators);            renderArgs._blitFramebuffer = finalFramebuffer;            render_runRenderFrame(&renderArgs);        }    }     auto frame = getGPUContext()->endFrame();    frame->frameIndex = _renderFrameCount;    frame->framebuffer = finalFramebuffer;    frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) {        auto frameBufferCache = DependencyManager::get<FramebufferCache>();        if (frameBufferCache) {            frameBufferCache->releaseFramebuffer(framebuffer);        }    };    frame->snapshotOperators = snapshotOperators;    // deliver final scene rendering commands to the display plugin    {        PROFILE_RANGE(render, "/pluginOutput");        PerformanceTimer perfTimer("pluginOutput");        _renderLoopCounter.increment();        displayPlugin->submitFrame(frame);    }     // Reset the framebuffer and stereo state    renderArgs._blitFramebuffer.reset();    renderArgs._context->enableStereo(false); #if !defined(DISABLE_QML)    {        auto stats = Stats::getInstance();        if (stats) {            stats->setRenderDetails(renderArgs._details);        }    }#endif     uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;    _frameTimingsScriptingInterface.addValue(lastPaintDuration);} void GraphicsEngine::editRenderArgs(RenderArgsEditor editor) {    QMutexLocker renderLocker(&_renderArgsMutex);    editor(_appRenderArgs);}