/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2020 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #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 #ifdef GHOUL_USE_DEVIL #include #endif //GHOUL_USE_DEVIL #ifdef GHOUL_USE_FREEIMAGE #include #endif // GHOUL_USE_FREEIMAGE #ifdef GHOUL_USE_SOIL #include #include #include #endif //GHOUL_USE_SOIL #ifdef GHOUL_USE_STB_IMAGE #include #endif // GHOUL_USE_STB_IMAGE #include "renderengine_lua.inl" namespace { constexpr const char* _loggerCat = "RenderEngine"; constexpr const std::chrono::seconds ScreenLogTimeToLive(15); constexpr const char* RenderFsPath = "${SHADERS}/render.frag"; constexpr const char* KeyFontMono = "Mono"; constexpr const char* KeyFontLight = "Light"; constexpr openspace::properties::Property::PropertyInfo PerformanceInfo = { "PerformanceMeasurements", "Performance Measurements", "If this value is enabled, detailed performance measurements about the updates " "and rendering of the scene graph nodes are collected each frame. These values " "provide some information about the impact of individual nodes on the overall " "performance." }; constexpr openspace::properties::Property::PropertyInfo ShowOverlaySlavesInfo = { "ShowOverlayOnSlaves", "Show Overlay Information on Slaves", "If this value is enabled, the overlay information text is also automatically " "rendered on the slave nodes. This values is disabled by default." }; constexpr openspace::properties::Property::PropertyInfo ShowLogInfo = { "ShowLog", "Show the on-screen log", "This value determines whether the on-screen log will be shown or hidden. Even " "if it is shown, all 'Debug' and 'Trace' level messages are omitted from this " "log." }; constexpr openspace::properties::Property::PropertyInfo VerticalLogOffsetInfo = { "VerticalLogOffset", "Vertical Log Offset", "The vertical offset for the on-screen log in [0,1] coordinates, a factor that " "is scaled with the vertical resolution" }; constexpr openspace::properties::Property::PropertyInfo ShowVersionInfo = { "ShowVersion", "Shows the version on-screen information", "This value determines whether the Git version information (branch and commit) " "hash are shown on the screen." }; constexpr openspace::properties::Property::PropertyInfo ShowCameraInfo = { "ShowCamera", "Shows information about the current camera state, such as friction", "This value determines whether the information about the current camrea state is " "shown on the screen" }; constexpr openspace::properties::Property::PropertyInfo ApplyWarpingInfo = { "ApplyWarpingScreenshot", "Apply Warping to Screenshots", "This value determines whether a warping should be applied before taking a " "screenshot. If it is enabled, all post processing is applied as well, which " "includes everything rendered on top of the rendering, such as the user " "interface." }; constexpr openspace::properties::Property::PropertyInfo ShowFrameNumberInfo = { "ShowFrameInformation", "Show Frame Information", "If this value is enabled, the current frame number and frame times are rendered " "into the window." }; #ifdef OPENSPACE_WITH_INSTRUMENTATION constexpr openspace::properties::Property::PropertyInfo SaveFrameInfo = { "SaveFrameInformation", "Save Frame Information", "Saves the frame information to disk" }; #endif // OPENSPACE_WITH_INSTRUMENTATION constexpr openspace::properties::Property::PropertyInfo DisableMasterInfo = { "DisableMasterRendering", "Disable Master Rendering", "If this value is enabled, the rendering on the master node will be disabled. " "Every other aspect of the application will be unaffected by this and it will " "still respond to user input. This setting is reasonably only useful in the case " "of multi-pipeline environments, such as planetariums, where the output of the " "master node is not required and performance can be gained by disabling it." }; constexpr openspace::properties::Property::PropertyInfo GlobalRotationInfo = { "GlobalRotation", "Global Rotation", "Applies a global view rotation. Use this to rotate the position of the " "focus node away from the default location on the screen. This setting " "persists even when a new focus node is selected. Defined using pitch, yaw, " "roll in radians" }; constexpr openspace::properties::Property::PropertyInfo ScreenSpaceRotationInfo = { "ScreenSpaceRotation", "Screen Space Rotation", "Applies a rotation to all screen space renderables. " "Defined using pitch, yaw, roll in radians." }; constexpr openspace::properties::Property::PropertyInfo MasterRotationInfo = { "MasterRotation", "Master Rotation", "Applies a view rotation for only the master node, defined using " "pitch, yaw, roll in radians. This can be used to compensate the master view " "direction for tilted display systems in clustered immersive environments." }; constexpr openspace::properties::Property::PropertyInfo DisableHDRPipelineInfo = { "DisableHDRPipeline", "Disable HDR Rendering", "If this value is enabled, the rendering will disable the HDR color handling " "and the LDR color pipeline will be used. Be aware of possible over exposure " "in the final colors." }; constexpr openspace::properties::Property::PropertyInfo HDRExposureInfo = { "HDRExposure", "HDR Exposure", "This value determines the amount of light per unit area reaching the " "equivalent of an electronic image sensor." }; constexpr openspace::properties::Property::PropertyInfo GammaInfo = { "Gamma", "Gamma Correction", "Gamma, is the nonlinear operation used to encode and decode luminance or " "tristimulus values in the image." }; constexpr openspace::properties::Property::PropertyInfo HueInfo = { "Hue", "Hue", "Hue" }; constexpr openspace::properties::Property::PropertyInfo SaturationInfo = { "Saturation", "Saturation", "Saturation" }; constexpr openspace::properties::Property::PropertyInfo ValueInfo = { "Value", "Value", "Value" }; constexpr openspace::properties::Property::PropertyInfo HorizFieldOfViewInfo = { "HorizFieldOfView", "Horizontal Field of View", "Adjusts the degrees of the horizontal field of view. The vertical field of " "view will be automatically adjusted to match, according to the current " "aspect ratio." }; constexpr openspace::properties::Property::PropertyInfo GlobalBlackoutFactorInfo = { "BlackoutFactor", "Blackout Factor", "The blackout factor of the rendering. This can be used for fading in or out the " "rendering window" }; constexpr openspace::properties::Property::PropertyInfo FXAAInfo = { "FXAA", "Enable FXAA", "Enable FXAA" }; } // namespace namespace openspace { RenderEngine::RenderEngine() : properties::PropertyOwner({ "RenderEngine" }) , _showOverlayOnSlaves(ShowOverlaySlavesInfo, false) , _showLog(ShowLogInfo, true) , _verticalLogOffset(VerticalLogOffsetInfo, 0.f, 0.f, 1.f) , _showVersionInfo(ShowVersionInfo, true) , _showCameraInfo(ShowCameraInfo, true) , _applyWarping(ApplyWarpingInfo, false) , _showFrameInformation(ShowFrameNumberInfo, false) #ifdef OPENSPACE_WITH_INSTRUMENTATION , _saveFrameInformation(SaveFrameInfo, false) #endif // OPENSPACE_WITH_INSTRUMENTATION , _disableMasterRendering(DisableMasterInfo, false) , _globalBlackOutFactor(GlobalBlackoutFactorInfo, 1.f, 0.f, 1.f) , _enableFXAA(FXAAInfo, true) , _disableHDRPipeline(DisableHDRPipelineInfo, false) , _hdrExposure(HDRExposureInfo, 3.7f, 0.01f, 10.f) , _gamma(GammaInfo, 0.95f, 0.01f, 5.f) , _hue(HueInfo, 0.f, 0.f, 360.f) , _saturation(SaturationInfo, 1.f, 0.0f, 2.f) , _value(ValueInfo, 1.f, 0.f, 2.f) , _horizFieldOfView(HorizFieldOfViewInfo, 80.f, 1.f, 179.f) , _globalRotation( GlobalRotationInfo, glm::vec3(0.f), glm::vec3(-glm::pi()), glm::vec3(glm::pi()) ) , _screenSpaceRotation( ScreenSpaceRotationInfo, glm::vec3(0.f), glm::vec3(-glm::pi()), glm::vec3(glm::pi()) ) , _masterRotation( MasterRotationInfo, glm::vec3(0.f), glm::vec3(-glm::pi()), glm::vec3(glm::pi()) ) { addProperty(_showOverlayOnSlaves); addProperty(_showLog); addProperty(_verticalLogOffset); addProperty(_showVersionInfo); addProperty(_showCameraInfo); // @TODO (maci 2019-08-23) disabling FXAA on // MacOS for now until we have fix or MSAA option. #ifdef __APPLE__ _enableFXAA = false; #endif _enableFXAA.onChange([this]() { if (_renderer) { _renderer->enableFXAA(_enableFXAA); } }); addProperty(_enableFXAA); _disableHDRPipeline.onChange([this]() { if (_renderer) { _renderer->setDisableHDR(_disableHDRPipeline); } }); addProperty(_disableHDRPipeline); _hdrExposure.onChange([this]() { if (_renderer) { _renderer->setHDRExposure(_hdrExposure); } }); addProperty(_hdrExposure); _gamma.onChange([this]() { if (_renderer) { _renderer->setGamma(_gamma); } }); addProperty(_gamma); _hue.onChange([this]() { if (_renderer) { const float h = _hue / 360.f; _renderer->setHue(h); } }); addProperty(_hue); _saturation.onChange([this]() { if (_renderer) { _renderer->setSaturation(_saturation); } }); addProperty(_saturation); _value.onChange([this]() { if (_renderer) { _renderer->setValue(_value); } }); addProperty(_value); addProperty(_globalBlackOutFactor); addProperty(_applyWarping); _horizFieldOfView.onChange([this]() { if (global::windowDelegate.isMaster()) { global::windowDelegate.setHorizFieldOfView(_horizFieldOfView); } }); addProperty(_horizFieldOfView); addProperty(_showFrameInformation); #ifdef OPENSPACE_WITH_INSTRUMENTATION _saveFrameInformation.onChange([&]() { if (_saveFrameInformation) { _frameInfo.lastSavedFrame = frameNumber(); } }); addProperty(_saveFrameInformation); #endif // OPENSPACE_WITH_INSTRUMENTATION addProperty(_globalRotation); addProperty(_screenSpaceRotation); addProperty(_masterRotation); addProperty(_disableMasterRendering); } RenderEngine::~RenderEngine() {} // NOLINT void RenderEngine::setRendererFromString(const std::string& renderingMethod) { _rendererImplementation = rendererFromString(renderingMethod); std::unique_ptr newRenderer = nullptr; switch (_rendererImplementation) { case RendererImplementation::Framebuffer: newRenderer = std::make_unique(); break; case RendererImplementation::ABuffer: #ifdef OPENSPACE_WITH_ABUFFER_RENDERER newRenderer = std::make_unique(); #endif // OPENSPACE_WITH_ABUFFER_RENDERER break; case RendererImplementation::Invalid: LFATAL(fmt::format("Rendering method '{}' not available", renderingMethod)); return; } setRenderer(std::move(newRenderer)); } void RenderEngine::initialize() { ZoneScoped // We have to perform these initializations here as the OsEng has not been initialized // in our constructor _globalRotation = static_cast(global::configuration.globalRotation); _screenSpaceRotation = static_cast(global::configuration.screenSpaceRotation); _masterRotation = static_cast(global::configuration.masterRotation); _disableMasterRendering = global::configuration.isRenderingOnMasterDisabled; #ifdef GHOUL_USE_DEVIL ghoul::io::TextureReader::ref().addReader( std::make_unique() ); #endif // GHOUL_USE_DEVIL #ifdef GHOUL_USE_FREEIMAGE ghoul::io::TextureReader::ref().addReader( std::make_unique() ); #endif // GHOUL_USE_FREEIMAGE #ifdef GHOUL_USE_SOIL ghoul::io::TextureReader::ref().addReader( std::make_unique() ); ghoul::io::TextureWriter::ref().addWriter( std::make_unique() ); #endif // GHOUL_USE_SOIL #ifdef GHOUL_USE_STB_IMAGE ghoul::io::TextureReader::ref().addReader( std::make_unique() ); #endif // GHOUL_USE_STB_IMAGE ghoul::io::TextureReader::ref().addReader( std::make_unique() ); } void RenderEngine::initializeGL() { ZoneScoped LTRACE("RenderEngine::initializeGL(begin)"); std::string renderingMethod = global::configuration.renderingMethod; if (renderingMethod == "ABuffer") { using Version = ghoul::systemcapabilities::Version; // The default rendering method has a requirement of OpenGL 4.3, so if we are // below that, we will fall back to frame buffer operation if (OpenGLCap.openGLVersion() < Version{ 4,3,0 }) { LINFO("Falling back to framebuffer implementation due to OpenGL limitations"); renderingMethod = "Framebuffer"; } } LINFO(fmt::format("Setting renderer from string: {}", renderingMethod)); setRendererFromString(renderingMethod); // TODO: Fix the power scaled coordinates in such a way that these // values can be set to more realistic values // set the close clip plane and the far clip plane to extreme values while in // development global::windowDelegate.setNearFarClippingPlane(0.001f, 1000.f); // Set horizontal FOV value with whatever the field of view (in degrees) is of the // initialized window _horizFieldOfView = static_cast(global::windowDelegate.getHorizFieldOfView()); constexpr const float FontSizeFrameinfo = 32.f; _fontFrameInfo = global::fontManager.font(KeyFontMono, FontSizeFrameinfo); constexpr const float FontSizeTime = 15.f; _fontDate = global::fontManager.font(KeyFontMono, FontSizeTime); constexpr const float FontSizeMono = 10.f; _fontInfo = global::fontManager.font(KeyFontMono, FontSizeMono); constexpr const float FontSizeLight = 8.f; _fontLog = global::fontManager.font(KeyFontLight, FontSizeLight); LINFO("Initializing Log"); std::unique_ptr log = std::make_unique(ScreenLogTimeToLive); _log = log.get(); ghoul::logging::LogManager::ref().addLog(std::move(log)); LINFO("Finished initializing GL"); LTRACE("RenderEngine::initializeGL(end)"); } void RenderEngine::deinitializeGL() { ZoneScoped _renderer = nullptr; } void RenderEngine::updateScene() { ZoneScoped if (!_scene) { return; } _scene->updateInterpolations(); const Time& currentTime = global::timeManager.time(); const Time& integrateFromTime = global::timeManager.integrateFromTime(); _scene->update({ TransformData{ glm::dvec3(0.0), glm::dmat3(1.0), glm::dvec3(1.0) }, currentTime, integrateFromTime }); LTRACE("RenderEngine::updateSceneGraph(end)"); } void RenderEngine::updateShaderPrograms() { ZoneScoped for (ghoul::opengl::ProgramObject* program : _programs) { try { if (program->isDirty()) { program->rebuildFromFile(); } } catch (const ghoul::opengl::ShaderObject::ShaderCompileError& e) { LERRORC(e.component, e.what()); } } } void RenderEngine::updateRenderer() { ZoneScoped const bool windowResized = global::windowDelegate.windowHasResized(); if (windowResized) { _renderer->setResolution(renderingResolution()); using FR = ghoul::fontrendering::FontRenderer; FR::defaultRenderer().setFramebufferSize(fontResolution()); FR::defaultProjectionRenderer().setFramebufferSize(renderingResolution()); //Override the aspect ratio property value to match that of resized window _horizFieldOfView = static_cast(global::windowDelegate.getHorizFieldOfView()); } _renderer->update(); } void RenderEngine::updateScreenSpaceRenderables() { ZoneScoped for (std::unique_ptr& ssr : global::screenSpaceRenderables) { ssr->update(); } } glm::ivec2 RenderEngine::renderingResolution() const { return global::windowDelegate.currentDrawBufferResolution(); } glm::ivec2 RenderEngine::fontResolution() const { const std::string& value = global::configuration.onScreenTextScaling; if (value == "framebuffer") { return global::windowDelegate.currentViewportSize(); } else { return global::windowDelegate.currentSubwindowSize(); } } glm::mat4 RenderEngine::globalRotation() const { glm::vec3 rot = _globalRotation; glm::quat pitch = glm::angleAxis(rot.x, glm::vec3(1.f, 0.f, 0.f)); glm::quat yaw = glm::angleAxis(rot.y, glm::vec3(0.f, 1.f, 0.f)); glm::quat roll = glm::angleAxis(rot.z, glm::vec3(0.f, 0.f, 1.f)); return glm::mat4_cast(glm::normalize(pitch * yaw * roll)); } glm::mat4 RenderEngine::screenSpaceRotation() const { glm::vec3 rot = _screenSpaceRotation; glm::quat pitch = glm::angleAxis(rot.x, glm::vec3(1.f, 0.f, 0.f)); glm::quat yaw = glm::angleAxis(rot.y, glm::vec3(0.f, 1.f, 0.f)); glm::quat roll = glm::angleAxis(rot.z, glm::vec3(0.f, 0.f, 1.f)); return glm::mat4_cast(glm::normalize(pitch * yaw * roll)); } glm::mat4 RenderEngine::nodeRotation() const { if (!global::windowDelegate.isMaster()) { return glm::mat4(1.f); } glm::vec3 rot = _masterRotation; glm::quat pitch = glm::angleAxis(rot.x, glm::vec3(1.f, 0.f, 0.f)); glm::quat yaw = glm::angleAxis(rot.y, glm::vec3(0.f, 1.f, 0.f)); glm::quat roll = glm::angleAxis(rot.z, glm::vec3(0.f, 0.f, 1.f)); return glm::mat4_cast(glm::normalize(pitch * yaw * roll)); } uint64_t RenderEngine::frameNumber() const { return _frameNumber; } void RenderEngine::render(const glm::mat4& sceneMatrix, const glm::mat4& viewMatrix, const glm::mat4& projectionMatrix) { ZoneScoped LTRACE("RenderEngine::render(begin)"); const WindowDelegate& delegate = global::windowDelegate; const glm::mat4 globalRot = globalRotation(); const glm::mat4 nodeRot = nodeRotation(); glm::mat4 combinedGlobalRot = nodeRot * globalRot; if (_camera) { _camera->sgctInternal.setViewMatrix( viewMatrix * combinedGlobalRot * sceneMatrix ); _camera->sgctInternal.setSceneMatrix(combinedGlobalRot * sceneMatrix); _camera->sgctInternal.setProjectionMatrix(projectionMatrix); _camera->invalidateCache(); } const bool masterEnabled = delegate.isMaster() ? !_disableMasterRendering : true; if (masterEnabled && !delegate.isGuiWindow() && _globalBlackOutFactor > 0.f) { _renderer->render( _scene, _camera, _globalBlackOutFactor ); } if (_showFrameInformation) { ZoneScopedN("Show Frame Information") glm::vec2 penPosition = glm::vec2( fontResolution().x / 2 - 50, fontResolution().y / 3 ); std::string fn = std::to_string(_frameNumber); WindowDelegate::Frustum frustum = global::windowDelegate.frustumMode(); std::string fr = [](WindowDelegate::Frustum frustum) -> std::string { switch (frustum) { case WindowDelegate::Frustum::Mono: return ""; case WindowDelegate::Frustum::LeftEye: return "(left)"; case WindowDelegate::Frustum::RightEye: return "(right)"; default: throw std::logic_error("Unhandled case label"); } }(frustum); std::string sgFn = std::to_string(global::windowDelegate.swapGroupFrameNumber()); std::string dt = std::to_string(global::windowDelegate.deltaTime()); std::string avgDt = std::to_string(global::windowDelegate.averageDeltaTime()); std::string res = "Frame: " + fn + ' ' + fr + '\n' + "Swap group frame: " + sgFn + '\n' + "Dt: " + dt + '\n' + "Avg Dt: " + avgDt; RenderFont(*_fontFrameInfo, penPosition, res); } if (masterEnabled && !delegate.isGuiWindow() && _globalBlackOutFactor > 0.f) { ZoneScopedN("Render Screenspace Renderable") std::vector ssrs; ssrs.reserve(global::screenSpaceRenderables.size()); for (const std::unique_ptr& ssr : global::screenSpaceRenderables) { if (ssr->isEnabled() && ssr->isReady()) { ssrs.push_back(ssr.get()); } } std::sort( ssrs.begin(), ssrs.end(), [](ScreenSpaceRenderable* lhs, ScreenSpaceRenderable* rhs) { // Render back to front. return lhs->depth() > rhs->depth(); } ); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (ScreenSpaceRenderable* ssr : ssrs) { ssr->render(); } glDisable(GL_BLEND); } LTRACE("RenderEngine::render(end)"); } bool RenderEngine::mouseActivationCallback(const glm::dvec2& mousePosition) const { auto intersects = [](const glm::dvec2& mousePos, const glm::ivec4& bbox) { return mousePos.x >= bbox.x && mousePos.x <= bbox.x + bbox.z && mousePos.y <= bbox.y && mousePos.y >= bbox.y - bbox.w; }; if (intersects(mousePosition, _cameraButtonLocations.rotation)) { constexpr const char* ToggleRotationFrictionScript = R"( local f = 'NavigationHandler.OrbitalNavigator.Friction.RotationalFriction'; openspace.setPropertyValue(f, not openspace.getPropertyValue(f));)"; global::scriptEngine.queueScript( ToggleRotationFrictionScript, scripting::ScriptEngine::RemoteScripting::Yes ); return true; } if (intersects(mousePosition, _cameraButtonLocations.zoom)) { constexpr const char* ToggleZoomFrictionScript = R"( local f = 'NavigationHandler.OrbitalNavigator.Friction.ZoomFriction'; openspace.setPropertyValue(f, not openspace.getPropertyValue(f));)"; global::scriptEngine.queueScript( ToggleZoomFrictionScript, scripting::ScriptEngine::RemoteScripting::Yes ); return true; } if (intersects(mousePosition, _cameraButtonLocations.roll)) { constexpr const char* ToggleRollFrictionScript = R"( local f = 'NavigationHandler.OrbitalNavigator.Friction.RollFriction'; openspace.setPropertyValue(f, not openspace.getPropertyValue(f));)"; global::scriptEngine.queueScript( ToggleRollFrictionScript, scripting::ScriptEngine::RemoteScripting::Yes ); return true; } return false; } void RenderEngine::renderOverlays(const ShutdownInformation& shutdownInfo) { ZoneScoped const bool isMaster = global::windowDelegate.isMaster(); if (isMaster || _showOverlayOnSlaves) { renderScreenLog(); renderVersionInformation(); renderDashboard(); if (!shutdownInfo.inShutdown) { // We render the camera information in the same location as the shutdown info // and we won't need this if we are shutting down renderCameraInformation(); } else { // If we are in shutdown mode, we can display the remaining time renderShutdownInformation(shutdownInfo.timer, shutdownInfo.waitTime); } } } void RenderEngine::renderEndscreen() { glEnable(GL_BLEND); rendering::helper::renderBox( glm::vec2(0.f), glm::vec2(1.f), glm::vec4(0.f, 0.f, 0.f, 0.5f) ); const glm::vec2 dpiScaling = global::windowDelegate.dpiScaling(); const glm::ivec2 res = glm::vec2(global::windowDelegate.currentSubwindowSize()) / dpiScaling; glViewport(0, 0, res.x, res.y); using FR = ghoul::fontrendering::FontRenderer; const glm::vec2 size = _fontDate->boundingBox("Shutting down"); glm::vec2 penPosition = glm::vec2( fontResolution().x / 2 - size.x / 2, fontResolution().y / 2 - size.y / 2 ); RenderFont(*_fontDate, penPosition, "Shutting down"); } void RenderEngine::renderShutdownInformation(float timer, float fullTime) { ZoneScoped timer = std::max(timer, 0.f); const glm::vec2 size = _fontDate->boundingBox( fmt::format("Shutdown in: {:.2f}s/{:.2f}s", timer, fullTime) ); glm::vec2 penPosition = glm::vec2( fontResolution().x - size.x - 10, fontResolution().y - size.y ); RenderFont( *_fontDate, penPosition, fmt::format("Shutdown in: {:.2f}s/{:.2f}s", timer, fullTime), ghoul::fontrendering::CrDirection::Down ); RenderFont( *_fontDate, penPosition, // Important: length of this string is the same as the shutdown time text // to make them align "Press ESC again to abort", ghoul::fontrendering::CrDirection::Down ); } void RenderEngine::renderDashboard() { ZoneScoped glm::vec2 dashboardStart = global::dashboard.getStartPositionOffset(); glm::vec2 penPosition = glm::vec2( dashboardStart.x, dashboardStart.y + fontResolution().y - global::luaConsole.currentHeight() ); global::dashboard.render(penPosition); #ifdef REALTIME_CAMERA_POS_DISPLAY penPosition += glm::vec2(0.f, -50.f); glm::dvec3 p = _camera->positionVec3(); glm::dquat rot = _camera->rotationQuaternion(); std::string fc = global::navigationHandler.focusNode()->identifier(); RenderFont( *_fontInfo, penPosition, fmt::format("Pos: {} {} {}\nOrientation: {} {} {} {}\nFocus: {}", p.x, p.y, p.z, rot[0], rot[1], rot[2], rot[3], fc ) ); #endif } void RenderEngine::postDraw() { ZoneScoped ++_frameNumber; #ifdef OPENSPACE_WITH_INSTRUMENTATION if (_saveFrameInformation) { _frameInfo.frames.push_back({ frameNumber(), global::windowDelegate.deltaTime(), global::windowDelegate.averageDeltaTime() }); } const uint16_t next = _frameInfo.lastSavedFrame + _frameInfo.saveEveryNthFrame; const bool shouldSave = _saveFrameInformation && frameNumber() >= next; if (shouldSave) { std::string filename = fmt::format( "_inst_renderengine_{}_{}.txt", _frameInfo.lastSavedFrame, _frameInfo.saveEveryNthFrame ); std::ofstream file(absPath("${BIN}/" + filename)); for (const FrameInfo& i : _frameInfo.frames) { std::string line = fmt::format( "{}\t{}\t{}", i.iFrame, i.deltaTime, i.avgDeltaTime ); file << line << '\n'; } _frameInfo.frames.clear(); _frameInfo.lastSavedFrame = frameNumber(); } #endif // OPENSPACE_WITH_INSTRUMENTATION } Scene* RenderEngine::scene() { return _scene; } void RenderEngine::setScene(Scene* scene) { _scene = scene; } void RenderEngine::setCamera(Camera* camera) { _camera = camera; } const Renderer& RenderEngine::renderer() const { return *_renderer; } RenderEngine::RendererImplementation RenderEngine::rendererImplementation() const { return _rendererImplementation; } float RenderEngine::globalBlackOutFactor() { return _globalBlackOutFactor; } void RenderEngine::setGlobalBlackOutFactor(float opacity) { _globalBlackOutFactor = opacity; } float RenderEngine::hdrExposure() const { return _hdrExposure; } bool RenderEngine::isHdrDisabled() const { return _disableHDRPipeline; } /** * Build a program object for rendering with the used renderer */ std::unique_ptr RenderEngine::buildRenderProgram( const std::string& name, const std::string& vsPath, std::string fsPath, ghoul::Dictionary data) { ghoul::Dictionary dict = std::move(data); // set path to the current renderer's main fragment shader dict.setValue("rendererData", _rendererData); // parameterize the main fragment shader program with specific contents. // fsPath should point to a shader file defining a Fragment getFragment() function // instead of a void main() setting glFragColor, glFragDepth, etc. dict.setValue("fragmentPath", std::move(fsPath)); using namespace ghoul::opengl; std::unique_ptr program = ProgramObject::Build( name, vsPath, absPath(RenderFsPath), std::move(dict) ); if (program) { _programs.push_back(program.get()); } return program; } /** * Build a program object for rendering with the used renderer */ std::unique_ptr RenderEngine::buildRenderProgram( const std::string& name, const std::string& vsPath, std::string fsPath, const std::string& csPath, ghoul::Dictionary data) { ghoul::Dictionary dict = std::move(data); dict.setValue("rendererData", _rendererData); // parameterize the main fragment shader program with specific contents. // fsPath should point to a shader file defining a Fragment getFragment() function // instead of a void main() setting glFragColor, glFragDepth, etc. dict.setValue("fragmentPath", std::move(fsPath)); using namespace ghoul::opengl; std::unique_ptr program = ProgramObject::Build( name, vsPath, absPath(RenderFsPath), csPath, std::move(dict) ); if (program) { _programs.push_back(program.get()); } return program; } void RenderEngine::removeRenderProgram(ghoul::opengl::ProgramObject* program) { if (!program) { return; } auto it = std::find( _programs.begin(), _programs.end(), program ); if (it != _programs.end()) { _programs.erase(it); } } /** * Set renderer data * Called from the renderer, whenever it needs to update * the dictionary of all rendering programs. */ void RenderEngine::setRendererData(ghoul::Dictionary rendererData) { _rendererData = std::move(rendererData); for (ghoul::opengl::ProgramObject* program : _programs) { ghoul::Dictionary dict = program->dictionary(); dict.setValue("rendererData", _rendererData); program->setDictionary(dict); } } /** * Set resolve data * Called from the renderer, whenever it needs to update * the dictionary of all post rendering programs. */ void RenderEngine::setResolveData(ghoul::Dictionary resolveData) { _resolveData = std::move(resolveData); for (ghoul::opengl::ProgramObject* program : _programs) { ghoul::Dictionary dict = program->dictionary(); dict.setValue("resolveData", _resolveData); program->setDictionary(dict); } } /** * Take a screenshot and store it in the ${SCREENSHOTS} directory */ void RenderEngine::takeScreenshot() { // We only create the directory here, as we don't want to spam the users // screenshot folder everytime we start OpenSpace even when we are not taking any // screenshots. So the first time we actually take one, we create the folder: if (!FileSys.directoryExists(absPath("${SCREENSHOTS}"))) { FileSys.createDirectory( absPath("${SCREENSHOTS}"), ghoul::filesystem::FileSystem::Recursive::Yes ); } _latestScreenshotNumber = global::windowDelegate.takeScreenshot(_applyWarping); } /** * Get the latest screenshot filename */ unsigned int RenderEngine::latestScreenshotNumber() const { return _latestScreenshotNumber; } /** * Set raycasting uniforms on the program object, and setup raycasting. */ void RenderEngine::preRaycast(ghoul::opengl::ProgramObject& programObject) { _renderer->preRaycast(programObject); } /** * Tear down raycasting for the specified program object. */ void RenderEngine::postRaycast(ghoul::opengl::ProgramObject& programObject) { _renderer->postRaycast(programObject); } /** * Set renderer */ void RenderEngine::setRenderer(std::unique_ptr renderer) { if (_renderer) { _renderer->deinitialize(); } _renderer = std::move(renderer); _renderer->setResolution(renderingResolution()); _renderer->enableFXAA(_enableFXAA); _renderer->setHDRExposure(_hdrExposure); _renderer->initialize(); } scripting::LuaLibrary RenderEngine::luaLibrary() { return { "", { { "setRenderer", &luascriptfunctions::setRenderer, {}, "string", "Sets the renderer (ABuffer or FrameBuffer)" }, { "addScreenSpaceRenderable", &luascriptfunctions::addScreenSpaceRenderable, {}, "table", "Will create a ScreenSpaceRenderable from a lua Table and add it in the " "RenderEngine" }, { "removeScreenSpaceRenderable", &luascriptfunctions::removeScreenSpaceRenderable, {}, "string", "Given a ScreenSpaceRenderable name this script will remove it from the " "renderengine" }, { "takeScreenshot", &luascriptfunctions::takeScreenshot, {}, "", "Take a screenshot and return the screenshot number. The screenshot will " "be stored in the ${SCREENSHOTS} folder. " } }, }; } void RenderEngine::addScreenSpaceRenderable(std::unique_ptr s) { const std::string identifier = s->identifier(); if (std::find_if( global::screenSpaceRenderables.begin(), global::screenSpaceRenderables.end(), [&identifier](const std::unique_ptr& ssr) { return ssr->identifier() == identifier; }) != global::screenSpaceRenderables.end() ) { LERROR(fmt::format( "Cannot add scene space renderable. " "An element with identifier '{}' already exists", identifier )); return; } s->initialize(); s->initializeGL(); global::screenSpaceRootPropertyOwner.addPropertySubOwner(s.get()); global::screenSpaceRenderables.push_back(std::move(s)); } void RenderEngine::removeScreenSpaceRenderable(ScreenSpaceRenderable* s) { const auto it = std::find_if( global::screenSpaceRenderables.begin(), global::screenSpaceRenderables.end(), [s](const std::unique_ptr& r) { return r.get() == s; } ); if (it != global::screenSpaceRenderables.end()) { s->deinitialize(); global::screenSpaceRootPropertyOwner.removePropertySubOwner(s); global::screenSpaceRenderables.erase(it); } } void RenderEngine::removeScreenSpaceRenderable(const std::string& identifier) { ScreenSpaceRenderable* s = screenSpaceRenderable(identifier); if (s) { removeScreenSpaceRenderable(s); } } ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable( const std::string& identifier) { const auto it = std::find_if( global::screenSpaceRenderables.begin(), global::screenSpaceRenderables.end(), [&identifier](const std::unique_ptr& s) { return s->identifier() == identifier; } ); if (it != global::screenSpaceRenderables.end()) { return it->get(); } else { return nullptr; } } std::vector RenderEngine::screenSpaceRenderables() const { std::vector res(global::screenSpaceRenderables.size()); std::transform( global::screenSpaceRenderables.begin(), global::screenSpaceRenderables.end(), res.begin(), [](const std::unique_ptr& p) { return p.get(); } ); return res; } RenderEngine::RendererImplementation RenderEngine::rendererFromString( const std::string& renderingMethod) const { const std::map RenderingMethods = { #ifdef OPENSPACE_WITH_ABUFFER_RENDERER { "ABuffer", RendererImplementation::ABuffer }, #endif // OPENSPACE_WITH_ABUFFER_RENDERER { "Framebuffer", RendererImplementation::Framebuffer } }; if (RenderingMethods.find(renderingMethod) != RenderingMethods.end()) { return RenderingMethods.at(renderingMethod); } else { return RendererImplementation::Invalid; } } void RenderEngine::renderCameraInformation() { ZoneScoped if (!_showCameraInfo) { return; } const glm::vec4 EnabledColor = glm::vec4(0.2f, 0.75f, 0.2f, 1.f); const glm::vec4 DisabledColor = glm::vec4(0.55f, 0.2f, 0.2f, 1.f); const glm::vec2 rotationBox = _fontInfo->boundingBox("Rotation"); float penPosY = fontResolution().y - rotationBox.y; constexpr const float YSeparation = 5.f; constexpr const float XSeparation = 5.f; const interaction::OrbitalNavigator& nav = global::navigationHandler.orbitalNavigator(); using FR = ghoul::fontrendering::FontRenderer; _cameraButtonLocations.rotation = { fontResolution().x - rotationBox.x - XSeparation, fontResolution().y - penPosY, rotationBox.x, rotationBox.y }; FR::defaultRenderer().render( *_fontInfo, glm::vec2(fontResolution().x - rotationBox.x - XSeparation, penPosY), "Rotation", nav.hasRotationalFriction() ? EnabledColor : DisabledColor ); penPosY -= rotationBox.y + YSeparation; const glm::vec2 zoomBox = _fontInfo->boundingBox("Zoom"); _cameraButtonLocations.zoom = { fontResolution().x - zoomBox.x - XSeparation, fontResolution().y - penPosY, zoomBox.x, zoomBox.y }; FR::defaultRenderer().render( *_fontInfo, glm::vec2(fontResolution().x - zoomBox.x - XSeparation, penPosY), "Zoom", nav.hasZoomFriction() ? EnabledColor : DisabledColor ); penPosY -= zoomBox.y + YSeparation; const glm::vec2 rollBox = _fontInfo->boundingBox("Roll"); _cameraButtonLocations.roll = { fontResolution().x - rollBox.x - XSeparation, fontResolution().y - penPosY, rollBox.x, rollBox.y }; FR::defaultRenderer().render( *_fontInfo, glm::vec2(fontResolution().x - rollBox.x - XSeparation, penPosY), "Roll", nav.hasRollFriction() ? EnabledColor : DisabledColor ); } void RenderEngine::renderVersionInformation() { ZoneScoped if (!_showVersionInfo) { return; } std::string versionString = OPENSPACE_VERSION_STRING_FULL; if (global::versionChecker.hasLatestVersionInfo()) { VersionChecker::SemanticVersion latestVersion = global::versionChecker.latestVersion(); VersionChecker::SemanticVersion currentVersion { OPENSPACE_VERSION_MAJOR, OPENSPACE_VERSION_MINOR, OPENSPACE_VERSION_PATCH }; if (currentVersion < latestVersion) { versionString += fmt::format( " [Available: {}.{}.{}]", latestVersion.major, latestVersion.minor, latestVersion.patch ); } } using FR = ghoul::fontrendering::FontRenderer; const glm::vec2 versionBox = _fontInfo->boundingBox(versionString); const glm::vec2 commitBox = _fontInfo->boundingBox( fmt::format("{}@{}", OPENSPACE_GIT_BRANCH, OPENSPACE_GIT_COMMIT) ); FR::defaultRenderer().render( *_fontInfo, glm::vec2( fontResolution().x - versionBox.x - 10.f, 5.f ), versionString, glm::vec4(0.5, 0.5, 0.5, 1.f) ); // If a developer hasn't placed the Git command in the path, this variable will be // empty if (!std::string(OPENSPACE_GIT_COMMIT).empty()) { // We check OPENSPACE_GIT_COMMIT but puse OPENSPACE_GIT_FULL on purpose since // OPENSPACE_GIT_FULL will never be empty (always will contain at least @, but // checking for that is a bit brittle) FR::defaultRenderer().render( *_fontInfo, glm::vec2(fontResolution().x - commitBox.x - 10.f, versionBox.y + 5.f), OPENSPACE_GIT_FULL, glm::vec4(0.5, 0.5, 0.5, 1.f) ); } } void RenderEngine::renderScreenLog() { ZoneScoped if (!_showLog) { return; } _log->removeExpiredEntries(); constexpr const int MaxNumberMessages = 10; constexpr const int CategoryLength = 30; constexpr const int MessageLength = 140; constexpr const std::chrono::seconds FadeTime(5); const std::vector& entries = _log->entries(); auto lastEntries = entries.size() > MaxNumberMessages ? std::make_pair(entries.rbegin(), entries.rbegin() + MaxNumberMessages) : std::make_pair(entries.rbegin(), entries.rend()); const glm::ivec2 fontRes = fontResolution(); size_t nr = 1; const auto now = std::chrono::steady_clock::now(); for (auto& it = lastEntries.first; it != lastEntries.second; ++it) { const ScreenLog::LogEntry* e = &(*it); std::chrono::duration diff = now - e->timeStamp; float alpha = 1.f; std::chrono::duration ttf = ScreenLogTimeToLive - FadeTime; if (diff > ttf) { double d = (diff - ttf).count(); float t = static_cast(d) / static_cast(FadeTime.count()); float p = 0.8f - t; alpha = (p <= 0.f) ? 0.f : pow(p, 0.4f); } // Since all log entries are ordered, once one exceeds alpha, all have if (alpha <= 0.f) { break; } const std::string_view lvl = ghoul::to_string(e->level); const std::string_view message = std::string_view(e->message).substr(0, MessageLength); nr += std::count(message.begin(), message.end(), '\n'); const glm::vec4 white(0.9f, 0.9f, 0.9f, alpha); std::string str = fmt::format( "{:<15} {}{}", e->timeString, e->category.substr(0, CategoryLength), e->category.length() > CategoryLength ? "..." : "" ); RenderFont( *_fontLog, glm::vec2( 10.f, _fontLog->pointSize() * nr * 2 + fontRes.y * _verticalLogOffset ), str, white ); const glm::vec4 color = [alpha, white](ScreenLog::LogLevel level) { switch (level) { case ghoul::logging::LogLevel::Debug: return glm::vec4(0.f, 1.f, 0.f, alpha); case ghoul::logging::LogLevel::Warning: return glm::vec4(1.f, 1.f, 0.f, alpha); case ghoul::logging::LogLevel::Error: return glm::vec4(1.f, 0.f, 0.f, alpha); case ghoul::logging::LogLevel::Fatal: return glm::vec4(0.3f, 0.3f, 0.85f, alpha); default: return white; } }(e->level); RenderFont( *_fontLog, glm::vec2( 10 + 30 * _fontLog->pointSize(), _fontLog->pointSize() * nr * 2 + fontRes.y * _verticalLogOffset ), fmt::format("({})", lvl), color ); RenderFont( *_fontLog, glm::vec2( 10 + 41 * _fontLog->pointSize(), _fontLog->pointSize() * nr * 2 + fontRes.y * _verticalLogOffset ), message, white ); ++nr; } } } // namespace openspace