Files
OpenSpace/src/rendering/renderengine.cpp
Alexander Bock e3699a43a8 Update Ghoul and SGCT
Adapt to changes by using more string_view
2020-08-09 20:11:40 +02:00

1464 lines
48 KiB
C++

/*****************************************************************************************
* *
* 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 <openspace/rendering/renderengine.h>
#include <openspace/openspace.h>
#include <openspace/engine/configuration.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/mission/missionmanager.h>
#include <openspace/rendering/abufferrenderer.h>
#include <openspace/rendering/dashboard.h>
#include <openspace/rendering/deferredcastermanager.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/framebufferrenderer.h>
#include <openspace/rendering/luaconsole.h>
#include <openspace/rendering/raycastermanager.h>
#include <openspace/rendering/screenspacerenderable.h>
#include <openspace/scene/scene.h>
#include <openspace/scripting/scriptengine.h>
#include <openspace/util/timemanager.h>
#include <openspace/util/screenlog.h>
#include <openspace/util/updatestructures.h>
#include <openspace/util/versionchecker.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/font/fontmanager.h>
#include <ghoul/font/fontrenderer.h>
#include <ghoul/io/texture/texturereader.h>
#include <ghoul/io/texture/texturereadercmap.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/profiling.h>
#include <ghoul/misc/stringconversion.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/systemcapabilities/openglcapabilitiescomponent.h>
#ifdef GHOUL_USE_DEVIL
#include <ghoul/io/texture/texturereaderdevil.h>
#endif //GHOUL_USE_DEVIL
#ifdef GHOUL_USE_FREEIMAGE
#include <ghoul/io/texture/texturereaderfreeimage.h>
#endif // GHOUL_USE_FREEIMAGE
#ifdef GHOUL_USE_SOIL
#include <ghoul/io/texture/texturereadersoil.h>
#include <ghoul/io/texture/texturewriter.h>
#include <ghoul/io/texture/texturewritersoil.h>
#endif //GHOUL_USE_SOIL
#ifdef GHOUL_USE_STB_IMAGE
#include <ghoul/io/texture/texturereaderstb.h>
#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<float>()),
glm::vec3(glm::pi<float>())
)
, _screenSpaceRotation(
ScreenSpaceRotationInfo,
glm::vec3(0.f),
glm::vec3(-glm::pi<float>()),
glm::vec3(glm::pi<float>())
)
, _masterRotation(
MasterRotationInfo,
glm::vec3(0.f),
glm::vec3(-glm::pi<float>()),
glm::vec3(glm::pi<float>())
)
{
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<Renderer> newRenderer = nullptr;
switch (_rendererImplementation) {
case RendererImplementation::Framebuffer:
newRenderer = std::make_unique<FramebufferRenderer>();
break;
case RendererImplementation::ABuffer:
#ifdef OPENSPACE_WITH_ABUFFER_RENDERER
newRenderer = std::make_unique<ABufferRenderer>();
#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<glm::vec3>(global::configuration.globalRotation);
_screenSpaceRotation =
static_cast<glm::vec3>(global::configuration.screenSpaceRotation);
_masterRotation = static_cast<glm::vec3>(global::configuration.masterRotation);
_disableMasterRendering = global::configuration.isRenderingOnMasterDisabled;
#ifdef GHOUL_USE_DEVIL
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderDevIL>()
);
#endif // GHOUL_USE_DEVIL
#ifdef GHOUL_USE_FREEIMAGE
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderFreeImage>()
);
#endif // GHOUL_USE_FREEIMAGE
#ifdef GHOUL_USE_SOIL
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderSOIL>()
);
ghoul::io::TextureWriter::ref().addWriter(
std::make_unique<ghoul::io::TextureWriterSOIL>()
);
#endif // GHOUL_USE_SOIL
#ifdef GHOUL_USE_STB_IMAGE
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderSTB>()
);
#endif // GHOUL_USE_STB_IMAGE
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderCMAP>()
);
}
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<float>(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<ScreenLog> log = std::make_unique<ScreenLog>(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<float>(global::windowDelegate.getHorizFieldOfView());
}
_renderer->update();
}
void RenderEngine::updateScreenSpaceRenderables() {
ZoneScoped
for (std::unique_ptr<ScreenSpaceRenderable>& 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<ScreenSpaceRenderable*> ssrs;
ssrs.reserve(global::screenSpaceRenderables.size());
for (const std::unique_ptr<ScreenSpaceRenderable>& 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<ghoul::opengl::ProgramObject> 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<ProgramObject> 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<ghoul::opengl::ProgramObject> 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<ProgramObject> 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> 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<ScreenSpaceRenderable> s) {
const std::string identifier = s->identifier();
if (std::find_if(
global::screenSpaceRenderables.begin(),
global::screenSpaceRenderables.end(),
[&identifier](const std::unique_ptr<ScreenSpaceRenderable>& 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<ScreenSpaceRenderable>& 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<ScreenSpaceRenderable>& s) {
return s->identifier() == identifier;
}
);
if (it != global::screenSpaceRenderables.end()) {
return it->get();
}
else {
return nullptr;
}
}
std::vector<ScreenSpaceRenderable*> RenderEngine::screenSpaceRenderables() const {
std::vector<ScreenSpaceRenderable*> res(global::screenSpaceRenderables.size());
std::transform(
global::screenSpaceRenderables.begin(),
global::screenSpaceRenderables.end(),
res.begin(),
[](const std::unique_ptr<ScreenSpaceRenderable>& p) { return p.get(); }
);
return res;
}
RenderEngine::RendererImplementation RenderEngine::rendererFromString(
const std::string& renderingMethod) const
{
const std::map<std::string, RenderEngine::RendererImplementation> 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<ScreenLog::LogEntry>& 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<double> diff = now - e->timeStamp;
float alpha = 1.f;
std::chrono::duration<double> ttf = ScreenLogTimeToLive - FadeTime;
if (diff > ttf) {
double d = (diff - ttf).count();
float t = static_cast<float>(d) / static_cast<float>(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