Files
OpenSpace/src/rendering/renderengine.cpp
Alexander Bock 6edea5cae7 Apply the hue, value, saturation, and gamma values from the renderengine to screenspace renderables. Change the screenspace's own gamma value into an offset (#3293)
* Apply the hue, value, saturation, and gamma values from the renderengine to screenspace renderables.  Change the screenspace's own gamma value into an offset

* Make it clearer that the gamma correction is now an offset
2024-06-04 16:28:52 +02:00

1420 lines
50 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2024 *
* *
* 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/globalscallbacks.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/events/event.h>
#include <openspace/events/eventengine.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/navigation/orbitalnavigator.h>
#include <openspace/mission/missionmanager.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/memorymanager.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/font.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/io/model/modelreader.h>
#include <ghoul/io/model/modelreaderassimp.h>
#include <ghoul/io/model/modelreaderbinary.h>
#include <ghoul/io/texture/texturereaderstb.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/easing.h>
#include <ghoul/misc/profiling.h>
#include <ghoul/misc/stringconversion.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/opengl/openglstatecache.h>
#include <ghoul/systemcapabilities/openglcapabilitiescomponent.h>
#include "renderengine_lua.inl"
namespace {
constexpr std::string_view _loggerCat = "RenderEngine";
constexpr std::chrono::seconds ScreenLogTimeToLive(15);
constexpr std::string_view RenderFsPath = "${SHADERS}/render.frag";
constexpr std::string_view KeyFontMono = "Mono";
constexpr std::string_view KeyFontLight = "Light";
constexpr openspace::properties::Property::PropertyInfo ShowOverlayClientsInfo = {
"ShowOverlayOnClients",
"Show Overlay Information on Clients",
"If this value is enabled, the overlay information text is also automatically "
"rendered on client nodes. This values is disabled by default.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::User
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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 camera state is "
"shown on the screen.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ScreenshotWindowIdsInfo = {
"ScreenshotWindowId",
"Screenshow Window Ids",
"The list of window identifiers whose screenshot will be taken the next time "
"anyone triggers a screenshot. If this list is empty (the default), all windows "
"will have their screenshot taken. Id's that do not exist are silently ignored.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ShowStatisticsInfo = {
"ShowStatistics",
"Show Statistics",
"Show updating, rendering, and network statistics on all rendering nodes.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ScreenshotUseDateInfo = {
"ScreenshotUseDate",
"Screenshot Folder uses Date",
"If this value is set to 'true', screenshots will be saved to a folder that "
"contains the time at which this value was enabled.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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.",
openspace::properties::Property::Visibility::Hidden
};
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.",
openspace::properties::Property::Visibility::AdvancedUser
};
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",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo HueInfo = {
"Hue",
"Hue",
"Hue.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SaturationInfo = {
"Saturation",
"Saturation",
"Saturation.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ValueInfo = {
"Value",
"Value",
"Value.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo FramerateLimitInfo = {
"FramerateLimit",
"Framerate Limit",
"If set to a value bigger than 0, the framerate will be limited to that many "
"frames per second without using V-Sync.",
openspace::properties::Property::Visibility::User
};
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.",
openspace::properties::Property::Visibility::User
};
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.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ApplyBlackoutToMasterInfo = {
"ApplyBlackoutToMaster",
"Apply Blackout to Master",
"If this value is 'true', the blackout factor is applied to the master node. "
"Regardless of this value, the clients will always adhere to the factor.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo FXAAInfo = {
"FXAA",
"Enable FXAA",
"Enable FXAA.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo EnabledFontColorInfo = {
"EnabledFontColor",
"Enabled Font Color",
"The font color used for enabled options.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo DisabledFontColorInfo = {
"DisabledFontColor",
"Disabled Font Color",
"The font color used for disabled options.",
openspace::properties::Property::Visibility::AdvancedUser
};
} // namespace
namespace openspace {
RenderEngine::RenderEngine()
: properties::PropertyOwner({ "RenderEngine", "Render Engine" })
, _showOverlayOnClients(ShowOverlayClientsInfo, false)
, _showLog(ShowLogInfo, true)
, _verticalLogOffset(VerticalLogOffsetInfo, 0.f, 0.f, 1.f)
, _showVersionInfo(ShowVersionInfo, true)
, _showCameraInfo(ShowCameraInfo, true)
, _screenshotWindowIds(ScreenshotWindowIdsInfo)
, _applyWarping(ApplyWarpingInfo, false)
, _showStatistics(ShowStatisticsInfo, false)
, _screenshotUseDate(ScreenshotUseDateInfo, false)
, _showFrameInformation(ShowFrameNumberInfo, false)
, _disableMasterRendering(DisableMasterInfo, false)
, _globalBlackOutFactor(GlobalBlackoutFactorInfo, 1.f, 0.f, 1.f)
, _applyBlackoutToMaster(ApplyBlackoutToMasterInfo, true)
, _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.f, 2.f)
, _value(ValueInfo, 1.f, 0.f, 2.f)
, _framerateLimit(FramerateLimitInfo, 0, 0, 500)
, _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>())
)
, _enabledFontColor(EnabledFontColorInfo, glm::vec4(0.2f, 0.75f, 0.2f, 1.f))
, _disabledFontColor(DisabledFontColorInfo, glm::vec4(0.55f, 0.2f, 0.2f, 1.f))
{
addProperty(_showOverlayOnClients);
addProperty(_showLog);
addProperty(_verticalLogOffset);
addProperty(_showVersionInfo);
addProperty(_showCameraInfo);
_enableFXAA.onChange([this]() { _renderer.enableFXAA(_enableFXAA); });
addProperty(_enableFXAA);
_disableHDRPipeline.onChange([this]() {
_renderer.setDisableHDR(_disableHDRPipeline);
});
addProperty(_disableHDRPipeline);
_hdrExposure.onChange([this]() { _renderer.setHDRExposure(_hdrExposure); });
addProperty(_hdrExposure);
_gamma.onChange([this]() { _renderer.setGamma(_gamma); });
addProperty(_gamma);
auto setHueValueSaturation = [this]() {
_renderer.setHueValueSaturation(_hue / 360.f, _value, _saturation);
};
_hue.onChange(setHueValueSaturation);
addProperty(_hue);
_saturation.onChange(setHueValueSaturation);
addProperty(_saturation);
_value.onChange(setHueValueSaturation);
addProperty(_value);
addProperty(_globalBlackOutFactor);
addProperty(_applyBlackoutToMaster);
addProperty(_screenshotWindowIds);
addProperty(_applyWarping);
_showStatistics.onChange([this]() {
global::windowDelegate->showStatistics(_showStatistics);
});
addProperty(_showStatistics);
_screenshotUseDate.onChange([this]() {
// If there is no screenshot folder, don't bother with handling the change
if (!FileSys.hasRegisteredToken("${STARTUP_SCREENSHOT}")) {
return;
}
if (_screenshotUseDate) {
// Going from 'false' -> 'true'
// We might need to create the folder first
const std::time_t now = std::time(nullptr);
std::tm* nowTime = std::localtime(&now);
std::array<char, 128> date;
strftime(date.data(), sizeof(date), "%Y-%m-%d-%H-%M", nowTime);
const std::filesystem::path newFolder = absPath(
"${STARTUP_SCREENSHOT}/" + std::string(date.data())
);
FileSys.registerPathToken(
"${SCREENSHOTS}",
newFolder,
ghoul::filesystem::FileSystem::Override::Yes
);
}
else {
// Going from 'true' -> 'false'
// We reset the screenshot folder back to what it was in the beginning
FileSys.registerPathToken(
"${SCREENSHOTS}",
absPath("${STARTUP_SCREENSHOT}"),
ghoul::filesystem::FileSystem::Override::Yes
);
}
global::windowDelegate->setScreenshotFolder(absPath("${SCREENSHOTS}"));
});
addProperty(_screenshotUseDate);
_horizFieldOfView.onChange([this]() {
if (global::windowDelegate->isMaster()) {
global::windowDelegate->setHorizFieldOfView(_horizFieldOfView);
}
});
addProperty(_horizFieldOfView);
addProperty(_showFrameInformation);
addProperty(_framerateLimit);
addProperty(_globalRotation);
addProperty(_screenSpaceRotation);
addProperty(_masterRotation);
addProperty(_disableMasterRendering);
_enabledFontColor.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_enabledFontColor);
_disabledFontColor.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_disabledFontColor);
}
RenderEngine::~RenderEngine() {}
const FramebufferRenderer& RenderEngine::renderer() const {
return _renderer;
}
void RenderEngine::initialize() {
ZoneScoped;
// We have to perform these initializations here as the OsEng has not been initialized
// in our constructor
_globalRotation = global::configuration->globalRotation;
_screenSpaceRotation = global::configuration->screenSpaceRotation;
_masterRotation = global::configuration->masterRotation;
_disableMasterRendering = global::configuration->isRenderingOnMasterDisabled;
_screenshotUseDate = global::configuration->shouldUseScreenshotDate;
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderSTB>()
);
ghoul::io::TextureReader::ref().addReader(
std::make_unique<ghoul::io::TextureReaderCMAP>()
);
ghoul::io::ModelReader::ref().addReader(
std::make_unique<ghoul::io::ModelReaderAssimp>()
);
ghoul::io::ModelReader::ref().addReader(
std::make_unique<ghoul::io::ModelReaderBinary>()
);
_versionString = OPENSPACE_VERSION_STRING_FULL;
if (global::versionChecker->hasLatestVersionInfo()) {
VersionChecker::SemanticVersion latest = global::versionChecker->latestVersion();
const VersionChecker::SemanticVersion current {
OPENSPACE_VERSION_MAJOR,
OPENSPACE_VERSION_MINOR,
OPENSPACE_VERSION_PATCH
};
if (current < latest) {
_versionString += std::format(
" [Available: {}.{}.{}]", latest.major, latest.minor, latest.patch
);
}
}
}
void RenderEngine::initializeGL() {
ZoneScoped;
LTRACE("RenderEngine::initializeGL(begin)");
_renderer.setResolution(renderingResolution());
_renderer.enableFXAA(_enableFXAA);
_renderer.setHDRExposure(_hdrExposure);
_renderer.initialize();
// 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());
const Configuration::FontSizes fontSize = global::configuration->fontSize;
{
ZoneScopedN("Fonts");
TracyGpuZone("Fonts");
_fontFrameInfo = global::fontManager->font(KeyFontMono, fontSize.frameInfo);
_fontShutdown = global::fontManager->font(KeyFontMono, fontSize.shutdown);
_fontCameraInfo = global::fontManager->font(KeyFontMono, fontSize.cameraInfo);
_fontVersionInfo = global::fontManager->font(KeyFontMono, fontSize.versionInfo);
_fontLog = global::fontManager->font(KeyFontLight, fontSize.log);
}
{
ZoneScopedN("Log");
LINFO("Initializing Log");
auto 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.deinitialize();
}
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->currentViewportResolution();
}
}
glm::mat4 RenderEngine::globalRotation() const {
const glm::vec3 rot = _globalRotation;
const glm::quat pitch = glm::angleAxis(rot.x, glm::vec3(1.f, 0.f, 0.f));
const glm::quat yaw = glm::angleAxis(rot.y, glm::vec3(0.f, 1.f, 0.f));
const 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 {
const glm::vec3 rot = _screenSpaceRotation;
const glm::quat pitch = glm::angleAxis(rot.x, glm::vec3(1.f, 0.f, 0.f));
const glm::quat yaw = glm::angleAxis(rot.y, glm::vec3(0.f, 1.f, 0.f));
const 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);
}
const glm::vec3 rot = _masterRotation;
const glm::quat pitch = glm::angleAxis(rot.x, glm::vec3(1.f, 0.f, 0.f));
const glm::quat yaw = glm::angleAxis(rot.y, glm::vec3(0.f, 1.f, 0.f));
const 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();
const 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 int fpsLimit = _framerateLimit;
if (fpsLimit > 0) {
// Using a sleep here is not optimal, but we are not looking for perfect timing
std::this_thread::sleep_until(_lastFrameTime);
const double delta = (1.0 / fpsLimit) * 1000.0 * 1000.0;
auto now = std::chrono::high_resolution_clock::now();
_lastFrameTime = now + std::chrono::microseconds(static_cast<int>(delta));
}
const bool renderingEnabled = delegate.isMaster() ? !_disableMasterRendering : true;
if (renderingEnabled && combinedBlackoutFactor() > 0.f) {
_renderer.render(_scene, _camera, combinedBlackoutFactor());
}
// The CEF webbrowser fix has to be called at least once per frame and we are doing
// that in the renderer::render method. So if we disable the rendering, that fix is
// no longer called as we lose access to the Web UI. Since we are calling the fix
// many times anyway, we can just add one call to it here and not lose much
if (global::callback::webBrowserPerformanceHotfix) {
(*global::callback::webBrowserPerformanceHotfix)();
}
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);
const WindowDelegate::Frustum frustum = global::windowDelegate->frustumMode();
std::string fr = [](WindowDelegate::Frustum f) -> std::string {
switch (f) {
case WindowDelegate::Frustum::Mono: return "";
case WindowDelegate::Frustum::LeftEye: return "(left)";
case WindowDelegate::Frustum::RightEye: return "(right)";
default: throw ghoul::MissingCaseException();
}
}(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());
const std::string res = std::format(
"Frame: {} {}\nSwap group frame: {}\nDt: {}\nAvg Dt: {}",
fn, fr, sgFn, dt, avgDt
);
RenderFont(*_fontFrameInfo, penPosition, res);
}
if (renderingEnabled && !delegate.isGuiWindow()) {
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);
ScreenSpaceRenderable::RenderData data = {
.blackoutFactor = combinedBlackoutFactor(),
.hue = _hue / 360.f,
.value = _value,
.saturation = _saturation,
.gamma = _gamma
};
for (ScreenSpaceRenderable* ssr : ssrs) {
ssr->render(data);
}
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 std::string_view ToggleRotationFrictionScript = R"(
local f = 'NavigationHandler.OrbitalNavigator.Friction.RotationalFriction';
openspace.setPropertyValueSingle(f, not openspace.propertyValue(f));)";
global::scriptEngine->queueScript(
std::string(ToggleRotationFrictionScript),
scripting::ScriptEngine::ShouldBeSynchronized::Yes,
scripting::ScriptEngine::ShouldSendToRemote::Yes
);
return true;
}
if (intersects(mousePosition, _cameraButtonLocations.zoom)) {
constexpr std::string_view ToggleZoomFrictionScript = R"(
local f = 'NavigationHandler.OrbitalNavigator.Friction.ZoomFriction';
openspace.setPropertyValueSingle(f, not openspace.propertyValue(f));)";
global::scriptEngine->queueScript(
std::string(ToggleZoomFrictionScript),
scripting::ScriptEngine::ShouldBeSynchronized::Yes,
scripting::ScriptEngine::ShouldSendToRemote::Yes
);
return true;
}
if (intersects(mousePosition, _cameraButtonLocations.roll)) {
constexpr std::string_view ToggleRollFrictionScript = R"(
local f = 'NavigationHandler.OrbitalNavigator.Friction.RollFriction';
openspace.setPropertyValueSingle(f, not openspace.propertyValue(f));)";
global::scriptEngine->queueScript(
std::string(ToggleRollFrictionScript),
scripting::ScriptEngine::ShouldBeSynchronized::Yes,
scripting::ScriptEngine::ShouldSendToRemote::Yes
);
return true;
}
return false;
}
void RenderEngine::renderOverlays(const ShutdownInformation& shutdownInfo) {
ZoneScoped;
const bool isMaster = global::windowDelegate->isMaster();
if (isMaster || _showOverlayOnClients) {
renderScreenLog();
renderVersionInformation();
renderDashboard();
renderCameraInformation();
if (shutdownInfo.inShutdown) {
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->firstWindowResolution()) / dpiScaling;
glViewport(0, 0, res.x, res.y);
constexpr std::string_view Text = "Shutting down";
const glm::vec2 size = _fontShutdown->boundingBox(Text);
glm::vec2 penPosition = glm::vec2(
fontResolution().x / 2 - size.x / 2,
fontResolution().y / 2 - size.y / 2
);
RenderFont(*_fontShutdown, penPosition, Text);
}
void RenderEngine::renderShutdownInformation(float timer, float fullTime) {
ZoneScoped;
timer = std::max(timer, 0.f);
// Render progressive overlay
glEnable(GL_BLEND);
// t = 1.f -> start of shutdown counter t = 0.f -> timer has reached shutdown
const float t = 1.f - (timer / fullTime);
rendering::helper::renderBox(
glm::vec2(0.f),
glm::vec2(1.f),
glm::vec4(0.f, 0.f, 0.f, ghoul::circularEaseOut(t))
);
// No need to print the text if we are just about to finish since otherwise we'll be
// overplotting the actual "shutdown in progress" text
if (timer == 0.f) {
return;
}
constexpr std::string_view FirstLine = "Shutdown in: {:.2f}s/{:.2f}s";
const glm::vec2 size1 = _fontShutdown->boundingBox(
std::format(FirstLine, timer, fullTime)
);
glm::vec2 penPosition = glm::vec2(
fontResolution().x / 2 - size1.x / 2,
fontResolution().y / 2 - size1.y / 2
);
RenderFont(
*_fontShutdown,
penPosition,
std::format(FirstLine, timer, fullTime),
ghoul::fontrendering::CrDirection::Down
);
// Important: Length of this string is the same as the first line to make them align
RenderFont(*_fontShutdown, penPosition, "Press ESC again to abort");
}
void RenderEngine::renderDashboard() const {
ZoneScoped;
const glm::vec2 dashboardStart = global::dashboard->getStartPositionOffset();
glm::vec2 penPosition = glm::vec2(
dashboardStart.x,
dashboardStart.y + fontResolution().y - global::luaConsole->currentHeight()
);
global::dashboard->render(penPosition);
}
float RenderEngine::combinedBlackoutFactor() const {
if (global::windowDelegate->isMaster()) {
return _applyBlackoutToMaster ? _globalBlackOutFactor : 1.f;
}
else {
return _globalBlackOutFactor;
}
}
void RenderEngine::postDraw() {
ZoneScoped;
++_frameNumber;
}
Scene* RenderEngine::scene() {
return _scene;
}
void RenderEngine::setScene(Scene* scene) {
_scene = scene;
}
void RenderEngine::setCamera(Camera* camera) {
_camera = camera;
}
ghoul::opengl::OpenGLStateCache& RenderEngine::openglStateCache() {
if (_openglStateCache == nullptr) {
_openglStateCache = ghoul::opengl::OpenGLStateCache::instance();
}
return *_openglStateCache;
}
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::filesystem::path& vsPath,
const std::filesystem::path& 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", fsPath);
using namespace ghoul::opengl;
std::unique_ptr<ProgramObject> program = ProgramObject::Build(
name,
vsPath,
absPath(RenderFsPath),
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::filesystem::path& vsPath,
const std::filesystem::path& fsPath,
const std::filesystem::path& 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", fsPath);
using namespace ghoul::opengl;
std::unique_ptr<ProgramObject> program = ProgramObject::Build(
name,
vsPath,
absPath(RenderFsPath),
csPath,
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);
}
}
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);
}
}
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);
}
}
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 (!std::filesystem::is_directory(absPath("${SCREENSHOTS}"))) {
std::filesystem::create_directories(absPath("${SCREENSHOTS}"));
}
_latestScreenshotNumber = global::windowDelegate->takeScreenshot(
_applyWarping,
_screenshotWindowIds
);
}
/**
* Resets the screenshot index to 0
*/
void RenderEngine::resetScreenshotNumber() {
_latestScreenshotNumber = 0;
global::windowDelegate->resetScreenshotNumber();
}
unsigned int RenderEngine::latestScreenshotNumber() const {
return _latestScreenshotNumber;
}
scripting::LuaLibrary RenderEngine::luaLibrary() {
return {
"",
{
codegen::lua::AddScreenSpaceRenderable,
codegen::lua::RemoveScreenSpaceRenderable,
codegen::lua::TakeScreenshot,
codegen::lua::DpiScaling,
codegen::lua::ResetScreenshotNumber
}
};
}
void RenderEngine::addScreenSpaceRenderable(std::unique_ptr<ScreenSpaceRenderable> s) {
const std::string identifier = s->identifier();
auto it = std::find_if(
global::screenSpaceRenderables->begin(),
global::screenSpaceRenderables->end(),
[&identifier](const std::unique_ptr<ScreenSpaceRenderable>& ssr) {
return ssr->identifier() == identifier;
}
);
if (it != global::screenSpaceRenderables->end()) {
LERROR(std::format(
"Cannot add scene space renderable. Identifier '{}' already exists",
identifier
));
return;
}
s->initialize();
s->initializeGL();
// We should do one update cycle to make sure that we have all the data that we need
s->update();
ScreenSpaceRenderable* ssr = s.get();
global::screenSpaceRootPropertyOwner->addPropertySubOwner(ssr);
global::screenSpaceRenderables->push_back(std::move(s));
global::eventEngine->publishEvent<events::EventScreenSpaceRenderableAdded>(ssr);
}
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()) {
global::eventEngine->publishEvent<events::EventScreenSpaceRenderableRemoved>(s);
s->deinitializeGL();
s->deinitialize();
global::screenSpaceRootPropertyOwner->removePropertySubOwner(s);
global::screenSpaceRenderables->erase(it);
}
}
void RenderEngine::removeScreenSpaceRenderable(std::string_view identifier) {
ScreenSpaceRenderable* s = screenSpaceRenderable(identifier);
if (s) {
removeScreenSpaceRenderable(s);
}
}
ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable(std::string_view 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;
}
void RenderEngine::renderCameraInformation() {
ZoneScoped;
if (!_showCameraInfo) {
return;
}
const glm::vec4 EnabledColor = _enabledFontColor;
const glm::vec4 DisabledColor = _disabledFontColor;
const glm::vec2 rotationBox = _fontCameraInfo->boundingBox("Rotation");
float penPosY = fontResolution().y - rotationBox.y;
constexpr float YSeparation = 5.f;
constexpr float XSeparation = 5.f;
const interaction::OrbitalNavigator& nav =
global::navigationHandler->orbitalNavigator();
using FR = ghoul::fontrendering::FontRenderer;
_cameraButtonLocations.rotation = glm::ivec4(
fontResolution().x - rotationBox.x - XSeparation,
fontResolution().y - penPosY,
rotationBox.x,
rotationBox.y
);
FR::defaultRenderer().render(
*_fontCameraInfo,
glm::vec2(fontResolution().x - rotationBox.x - XSeparation, penPosY),
"Rotation",
nav.hasRotationalFriction() ? EnabledColor : DisabledColor
);
penPosY -= rotationBox.y + YSeparation;
const glm::vec2 zoomBox = _fontCameraInfo->boundingBox("Zoom");
_cameraButtonLocations.zoom = glm::ivec4(
fontResolution().x - zoomBox.x - XSeparation,
fontResolution().y - penPosY,
zoomBox.x,
zoomBox.y
);
FR::defaultRenderer().render(
*_fontCameraInfo,
glm::vec2(fontResolution().x - zoomBox.x - XSeparation, penPosY),
"Zoom",
nav.hasZoomFriction() ? EnabledColor : DisabledColor
);
penPosY -= zoomBox.y + YSeparation;
const glm::vec2 rollBox = _fontCameraInfo->boundingBox("Roll");
_cameraButtonLocations.roll = glm::ivec4(
fontResolution().x - rollBox.x - XSeparation,
fontResolution().y - penPosY,
rollBox.x,
rollBox.y
);
FR::defaultRenderer().render(
*_fontCameraInfo,
glm::vec2(fontResolution().x - rollBox.x - XSeparation, penPosY),
"Roll",
nav.hasRollFriction() ? EnabledColor : DisabledColor
);
}
void RenderEngine::renderVersionInformation() {
ZoneScoped;
if (!_showVersionInfo) {
return;
}
using FR = ghoul::fontrendering::FontRenderer;
const glm::vec2 versionBox = _fontVersionInfo->boundingBox(_versionString);
const glm::vec2 commitBox = _fontVersionInfo->boundingBox(OPENSPACE_GIT_FULL);
FR::defaultRenderer().render(
*_fontVersionInfo,
glm::vec2(fontResolution().x - versionBox.x - 10.f, 5.f),
_versionString,
glm::vec4(0.5f, 0.5f, 0.5f, 1.f)
);
// If a developer hasn't placed the Git command in the path, this variable will be
// empty
if (!OPENSPACE_GIT_COMMIT.empty()) {
// We check OPENSPACE_GIT_COMMIT but use 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(
*_fontVersionInfo,
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)
);
}
#ifdef _DEBUG
[[maybe_unused]] float debugOffset = 0.f;
{
const glm::vec2 debugBox = _fontVersionInfo->boundingBox("Debug build");
debugOffset = debugBox.y;
const glm::vec2 penPosition = glm::vec2(
fontResolution().x - debugBox.x - 10.f,
versionBox.y + commitBox.y + 5.f
);
FR::defaultRenderer().render(
*_fontVersionInfo,
penPosition,
"Debug build",
glm::vec4(0.2f, 0.75f, 0.15f, 1.f)
);
}
#else // !_DEBUG
[[maybe_unused]] const float debugOffset = 0.f;
#endif // _DEBUG
#ifdef TRACY_ENABLE
{
// If we have Tracy enabled, we should inform the user about it that the
// application will crash after a while if no profiler is attached
ZoneScopedN("Tracy Information");
const glm::vec2 tracyBox = _fontVersionInfo->boundingBox("TRACY PROFILING");
const glm::vec2 penPosition = glm::vec2(
fontResolution().x - tracyBox.x - 10.f,
versionBox.y + commitBox.y + debugOffset + 5.f
);
FR::defaultRenderer().render(
*_fontVersionInfo,
penPosition,
"TRACY PROFILING",
glm::vec4(0.8f, 0.2f, 0.15f, 1.f)
);
}
#endif // TRACY_ENABLE
}
void RenderEngine::renderScreenLog() {
ZoneScoped;
if (!_showLog) {
return;
}
_log->removeExpiredEntries();
constexpr size_t MaxNumberMessages = 20;
constexpr int CategoryLength = 30;
constexpr int MessageLength = 280;
constexpr std::chrono::seconds FadeTime(5);
const std::vector<ScreenLog::LogEntry>& entries = _log->entries();
const glm::ivec2 fontRes = fontResolution();
size_t nRows = 1;
const auto now = std::chrono::steady_clock::now();
for (size_t i = 1; i <= std::min(MaxNumberMessages, entries.size()); i += 1) {
ZoneScopedN("Entry");
const ScreenLog::LogEntry& it = entries[entries.size() - i];
const std::chrono::duration<double> diff = now - it.timeStamp;
float alpha = 1.f;
const std::chrono::duration<double> ttf = ScreenLogTimeToLive - FadeTime;
if (diff > ttf) {
const double d = (diff - ttf).count();
const float t = static_cast<float>(d) / static_cast<float>(FadeTime.count());
const float p = 0.8f - t;
alpha = (p <= 0.f) ? 0.f : std::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 message =
std::string_view(it.message).substr(0, MessageLength);
nRows += std::count(message.begin(), message.end(), '\n');
const glm::vec4 white = glm::vec4(0.9f, 0.9f, 0.9f, alpha);
std::array<char, 15 + 1 + CategoryLength + 3> buf;
{
std::fill(buf.begin(), buf.end(), char(0));
char* end = std::format_to(
buf.data(),
"{:<15} {}{}",
it.timeString,
std::string_view(it.category).substr(0, CategoryLength),
it.category.length() > CategoryLength ? "..." : ""
);
RenderFont(
*_fontLog,
glm::vec2(
10.f,
_fontLog->pointSize() * nRows * 2 + fontRes.y * _verticalLogOffset
),
std::string_view(buf.data(), end - buf.data()),
white
);
}
{
glm::vec4 color = ghoul::toColor(it.level);
color.a = alpha;
const std::string_view lvl = ghoul::to_string(it.level);
std::fill(buf.begin(), buf.end(), char(0));
char* end = std::format_to(buf.data(), "({})", lvl);
RenderFont(
*_fontLog,
glm::vec2(
10 + (30 + 3) * _fontLog->pointSize(),
_fontLog->pointSize() * nRows * 2 + fontRes.y * _verticalLogOffset
),
std::string_view(buf.data(), end - buf.data()),
color
);
}
RenderFont(
*_fontLog,
glm::vec2(
10 + 44 * _fontLog->pointSize(),
_fontLog->pointSize() * nRows * 2 + fontRes.y * _verticalLogOffset
),
message,
white
);
++nRows;
}
}
} // namespace openspace