Revision 35efca7662eafe5c0648fbb4e97a7bc1eda84322 authored by Christoph Neuhauser on 23 October 2023, 22:10:04 UTC, committed by Christoph Neuhauser on 23 October 2023, 22:10:04 UTC
1 parent ef25cab
Raw File
MainApp.cpp
/*
 * BSD 2-Clause License
 *
 * Copyright (c) 2022, Christoph Neuhauser
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <memory>
#include <stack>
#include <algorithm>
#include <csignal>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/color_space.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <boost/algorithm/string.hpp>

#ifdef USE_ZEROMQ
#include <zmq.h>
#endif

#ifdef SUPPORT_QUICK_MLP
#include <ckl/kernel_loader.h>
#include <qmlp/qmlp.h>
#endif

#include <Utils/Timer.hpp>
#include <Utils/StringUtils.hpp>
#include <Utils/AppSettings.hpp>
#include <Utils/Dialog.hpp>
#include <Utils/File/Logfile.hpp>
#include <Utils/File/FileUtils.hpp>
#include <Utils/Regex/TransformString.hpp>
#include <Utils/File/FileLoader.hpp>
#include <Input/Keyboard.hpp>
#include <Input/Mouse.hpp>
#include <Math/Math.hpp>
#include <Math/Geometry/MatrixUtil.hpp>
#include <Graphics/Window.hpp>
#include <Graphics/Vulkan/Utils/Instance.hpp>
#include <Graphics/Vulkan/Utils/Swapchain.hpp>
#include <Graphics/Vulkan/Render/Renderer.hpp>
#ifdef SUPPORT_OPENCL_INTEROP
#include <Graphics/Vulkan/Utils/InteropOpenCL.hpp>
#endif

#include <ImGui/ImGuiWrapper.hpp>
#include <ImGui/ImGuiFileDialog/ImGuiFileDialog.h>
#include <ImGui/imgui_internal.h>
#include <ImGui/imgui_custom.h>
#include <ImGui/imgui_stdlib.h>
#include <ImGui/Widgets/ColorLegendWidget.hpp>

#include "Volume/VolumeData.hpp"
#include "Calculators/Similarity.hpp"
#include "Renderers/DvrRenderer.hpp"
#include "Renderers/IsoSurfaceRayCastingRenderer.hpp"
#include "Renderers/IsoSurfaceRasterizer.hpp"
#include "Renderers/DomainOutlineRenderer.hpp"
#include "Renderers/SliceRenderer.hpp"
#include "Renderers/WorldMapRenderer.hpp"
#include "Renderers/Diagram/DiagramRenderer.hpp"
#include "Utils/CurlWrapper.hpp"
#include "Utils/AutomaticPerformanceMeasurer.hpp"
#include "Optimization/TFOptimization.hpp"

#include "Widgets/ViewManager.hpp"
#include "Widgets/DataView.hpp"
#include "MainApp.hpp"

void vulkanErrorCallback() {
    SDL_CaptureMouse(SDL_FALSE);
    std::cerr << "Application callback" << std::endl;
}

#ifdef __linux__
void signalHandler(int signum) {
    SDL_CaptureMouse(SDL_FALSE);
    std::cerr << "Interrupt signal (" << signum << ") received." << std::endl;
    exit(signum);
}
#endif

MainApp::MainApp()
        : sceneData(
                &rendererVk, &sceneTextureVk, &sceneDepthTextureVk,
                &viewportPositionX, &viewportPositionY,
                &viewportWidth, &viewportHeight, &viewportWidth, &viewportHeight,
                camera, &clearColor, &screenshotTransparentBackground,
                &performanceMeasurer, &continuousRendering, &recording,
                &useCameraFlight, &MOVE_SPEED, &MOUSE_ROT_SPEED,
                &nonBlockingMsgBoxHandles),
          boundingBox() {
    sgl::AppSettings::get()->getVulkanInstance()->setDebugCallback(&vulkanErrorCallback);
    clearColor = sgl::Color(0, 0, 0, 255);
    clearColorSelection = ImColor(0, 0, 0, 255);
    std::string clearColorString;
    if (sgl::AppSettings::get()->getSettings().getValueOpt("clearColor", clearColorString)) {
        std::vector<std::string> clearColorStringParts;
        sgl::splitString(clearColorString, ',', clearColorStringParts);
        if (clearColorStringParts.size() == 3 || clearColorStringParts.size() == 4) {
            clearColor.setR(uint8_t(sgl::fromString<int>(clearColorStringParts.at(0))));
            clearColor.setG(uint8_t(sgl::fromString<int>(clearColorStringParts.at(1))));
            clearColor.setB(uint8_t(sgl::fromString<int>(clearColorStringParts.at(2))));
            clearColorSelection = ImColor(clearColor.getR(), clearColor.getG(), clearColor.getB(), 255);
        }
    }

#ifdef SUPPORT_CUDA_INTEROP
    sgl::vk::Device* device = sgl::AppSettings::get()->getPrimaryDevice();
    if (device->getDeviceDriverId() == VK_DRIVER_ID_NVIDIA_PROPRIETARY) {
        cudaInteropInitialized = true;
        if (!sgl::vk::initializeCudaDeviceApiFunctionTable()) {
            cudaInteropInitialized = false;
            sgl::Logfile::get()->writeError(
                    "Error in MainApp::MainApp: sgl::vk::initializeCudaDeviceApiFunctionTable() returned false.",
                    false);
        }

        if (cudaInteropInitialized) {
            CUresult cuResult = sgl::vk::g_cudaDeviceApiFunctionTable.cuInit(0);
            if (cuResult == CUDA_ERROR_NO_DEVICE) {
                sgl::Logfile::get()->writeInfo("No CUDA-capable device was found. Disabling CUDA interop support.");
                cudaInteropInitialized = false;
            } else {
                sgl::vk::checkCUresult(cuResult, "Error in cuInit: ");
            }
        }

        if (cudaInteropInitialized) {
            CUresult cuResult = sgl::vk::g_cudaDeviceApiFunctionTable.cuCtxCreate(
                    &cuContext, CU_CTX_SCHED_SPIN, cuDevice);
            sgl::vk::checkCUresult(cuResult, "Error in cuCtxCreate: ");
        }
    }

    if (cudaInteropInitialized) {
        nvrtcInitialized = true;
        if (!sgl::vk::initializeNvrtcFunctionTable()) {
            nvrtcInitialized = false;
            sgl::Logfile::get()->writeWarning(
                    "Warning in MainApp::MainApp: sgl::vk::initializeNvrtcFunctionTable() returned false.",
                    false);
        }
    }
#endif

#ifdef SUPPORT_OPENCL_INTEROP
    openclInteropInitialized = true;
    if (!sgl::vk::initializeOpenCLFunctionTable()) {
        openclInteropInitialized = false;
    }
#endif

    viewManager = new ViewManager(&clearColor, rendererVk);
    sgl::ColorLegendWidget::setFontScaleStandard(1.0f);

    checkpointWindow.setStandardWindowSize(1254, 390);
    checkpointWindow.setStandardWindowPosition(841, 53);

    propertyEditor.setInitWidthValues(sgl::ImGuiWrapper::get()->getScaleDependentSize(280.0f));

    camera->setNearClipDistance(0.01f);
    camera->setFarClipDistance(100.0f);

    CAMERA_PATH_TIME_PERFORMANCE_MEASUREMENT = TIME_PERFORMANCE_MEASUREMENT;
    usePerformanceMeasurementMode = false;
    if (sgl::FileUtils::get()->get_argc() > 1) {
        if (strcmp(sgl::FileUtils::get()->get_argv()[1], "--perf") == 0) {
            usePerformanceMeasurementMode = true;
        }
    }
    cameraPath.setApplicationCallback([this](
            const std::string& modelFilename, glm::vec3& centerOffset, float& startAngle, float& pulseFactor,
            float& standardZoom) {
    });

    curlInitWrapper();

    useDockSpaceMode = true;
    sgl::AppSettings::get()->getSettings().getValueOpt("useDockSpaceMode", useDockSpaceMode);
    sgl::AppSettings::get()->getSettings().getValueOpt("useFixedSizeViewport", useFixedSizeViewport);
    sgl::AppSettings::get()->getSettings().getValueOpt("fixedViewportSizeX", fixedViewportSize.x);
    sgl::AppSettings::get()->getSettings().getValueOpt("fixedViewportSizeY", fixedViewportSize.y);
    fixedViewportSizeEdit = fixedViewportSize;
    showPropertyEditor = true;
    sgl::ImGuiWrapper::get()->setUseDockSpaceMode(useDockSpaceMode);
    //useDockSpaceMode = false;

#ifdef NDEBUG
    showFpsOverlay = false;
#else
    showFpsOverlay = true;
#endif
    sgl::AppSettings::get()->getSettings().getValueOpt("showFpsOverlay", showFpsOverlay);
    sgl::AppSettings::get()->getSettings().getValueOpt("showCoordinateAxesOverlay", showCoordinateAxesOverlay);

    useLinearRGB = false;
    coordinateAxesOverlayWidget.setClearColor(clearColor);

    resolutionChanged(sgl::EventPtr());

    if (usePerformanceMeasurementMode) {
        useCameraFlight = true;
    }
    if (useCameraFlight && recording) {
        sgl::Window *window = sgl::AppSettings::get()->getMainWindow();
        window->setWindowSize(recordingResolution.x, recordingResolution.y);
        realTimeCameraFlight = false;
        loadVolumeDataSet({ sgl::AppSettings::get()->getDataDirectory() + "VolumeDataSets/test.nc" });
    }

    fileDialogInstance = IGFD_Create();
    customDataSetFileName = sgl::FileUtils::get()->getUserDirectory();
    loadAvailableDataSetInformation();

    if (!recording && !usePerformanceMeasurementMode) {
        // Just for convenience...
        int desktopWidth = 0;
        int desktopHeight = 0;
        int refreshRate = 60;
        sgl::AppSettings::get()->getDesktopDisplayMode(desktopWidth, desktopHeight, refreshRate);
        if (desktopWidth == 3840 && desktopHeight == 2160) {
            sgl::Window *window = sgl::AppSettings::get()->getMainWindow();
            window->setWindowSize(2186, 1358);
        }
    }

    //if (!useDockSpaceMode) {
    //    setRenderer(sceneData, oldRenderingMode, renderingMode, volumeRenderer, 0);
    //}

    if (!sgl::AppSettings::get()->getSettings().hasKey("cameraNavigationMode")) {
        cameraNavigationMode = sgl::CameraNavigationMode::TURNTABLE;
        updateCameraNavigationMode();
    }

    addNewDataView();

    recordingTimeStampStart = sgl::Timer->getTicksMicroseconds();
    usesNewState = true;
    if (usePerformanceMeasurementMode) {
        sgl::FileUtils::get()->ensureDirectoryExists("images");
        performanceMeasurer = new AutomaticPerformanceMeasurer(
                rendererVk, getTestModes(),
                "performance.csv", "depth_complexity.csv",
                [this](const InternalState &newState) { this->setNewState(newState); });
    }

    tfOptimization = new TFOptimization(rendererVk);
#ifdef CUDA_ENABLED
    if (cudaInteropInitialized) {
        tfOptimization->setCudaContext(cuContext);
    }
#endif
    tfOptimization->initialize();

#ifdef __linux__
    signal(SIGSEGV, signalHandler);
#endif
}

MainApp::~MainApp() {
    device->waitIdle();

    if (usePerformanceMeasurementMode) {
        performanceMeasurer->cleanup();
        delete performanceMeasurer;
        performanceMeasurer = nullptr;
    }

    delete tfOptimization;
    volumeRenderers = {};
    volumeData = {};
    dataViews.clear();
    delete viewManager;
    viewManager = nullptr;

    IGFD_Destroy(fileDialogInstance);

#ifdef SUPPORT_QUICK_MLP
    qmlp::QuickMLP::DeleteInstance();
    ckl::KernelLoader::DeleteInstance();
#endif

#ifdef SUPPORT_CUDA_INTEROP
    if (sgl::vk::getIsNvrtcFunctionTableInitialized()) {
        sgl::vk::freeNvrtcFunctionTable();
    }
    if (sgl::vk::getIsCudaDeviceApiFunctionTableInitialized()) {
        if (cuContext) {
            CUresult cuResult = sgl::vk::g_cudaDeviceApiFunctionTable.cuCtxDestroy(cuContext);
            sgl::vk::checkCUresult(cuResult, "Error in cuCtxDestroy: ");
            cuContext = {};
        }
        sgl::vk::freeCudaDeviceApiFunctionTable();
    }
#endif
#ifdef SUPPORT_OPENCL_INTEROP
    if (sgl::vk::getIsOpenCLFunctionTableInitialized()) {
        sgl::vk::freeOpenCLFunctionTable();
    }
#endif

    curlFreeWrapper();

    for (int i = 0; i < int(nonBlockingMsgBoxHandles.size()); i++) {
        auto& handle = nonBlockingMsgBoxHandles.at(i);
        if (handle->ready(0)) {
            nonBlockingMsgBoxHandles.erase(nonBlockingMsgBoxHandles.begin() + i);
            i--;
        } else {
            handle->kill();
        }
    }
    nonBlockingMsgBoxHandles.clear();

    std::string clearColorString =
            std::to_string(int(clearColor.getR())) + ","
            + std::to_string(int(clearColor.getG())) + ","
            + std::to_string(int(clearColor.getB()));
    sgl::AppSettings::get()->getSettings().addKeyValue("clearColor", clearColorString);
    sgl::AppSettings::get()->getSettings().addKeyValue("useDockSpaceMode", useDockSpaceMode);
    if (!usePerformanceMeasurementMode) {
        sgl::AppSettings::get()->getSettings().addKeyValue("useFixedSizeViewport", useFixedSizeViewport);
        sgl::AppSettings::get()->getSettings().addKeyValue("fixedViewportSizeX", fixedViewportSize.x);
        sgl::AppSettings::get()->getSettings().addKeyValue("fixedViewportSizeY", fixedViewportSize.y);
    }
    sgl::AppSettings::get()->getSettings().addKeyValue("showFpsOverlay", showFpsOverlay);
    sgl::AppSettings::get()->getSettings().addKeyValue("showCoordinateAxesOverlay", showCoordinateAxesOverlay);
}

void MainApp::setNewState(const InternalState &newState) {
    rendererVk->getDevice()->waitIdle();

    if (performanceMeasurer) {
        performanceMeasurer->setCurrentAlgorithmBufferSizeBytes(0);
    }

    // 1. Change the window resolution?
    glm::ivec2 newResolution = newState.windowResolution;
    if (useDockSpaceMode) {
        useFixedSizeViewport = true;
        fixedViewportSizeEdit = newResolution;
        fixedViewportSize = newResolution;
    } else {
        sgl::Window *window = sgl::AppSettings::get()->getMainWindow();
        int currentWindowWidth = window->getWidth();
        int currentWindowHeight = window->getHeight();
        if (newResolution.x > 0 && newResolution.y > 0 && currentWindowWidth != newResolution.x
            && currentWindowHeight != newResolution.y) {
            window->setWindowSize(newResolution.x, newResolution.y);
        }
    }

    // 1.1. Handle the new tiling mode for SSBO accesses.
    /*VolumeRenderer::setNewTilingMode(
            newState.tilingWidth, newState.tilingHeight,
            newState.useMortonCodeForTiling);

    // 1.2. Load the new transfer function if necessary.
    if (!newState.transferFunctionName.empty() && newState.transferFunctionName != lastState.transferFunctionName) {
        transferFunctionWindow.loadFunctionFromFile(
                sgl::AppSettings::get()->getDataDirectory()
                + "TransferFunctions/" + newState.transferFunctionName);
    }

    // 2.1. Do we need to load new renderers?
    if (firstState || newState.renderingMode != lastState.renderingMode
            || newState.rendererSettings != lastState.rendererSettings) {
        dataViews.clear();
        if (useDockSpaceMode) {
            if (dataViews.empty()) {
                addNewDataView();
            }
            RenderingMode newRenderingMode = newState.renderingMode;
            setRenderer(
                    dataViews[0]->sceneData, dataViews[0]->oldRenderingMode, newRenderingMode,
                    dataViews[0]->volumeRenderer, 0);
            dataViews[0]->renderingMode = newRenderingMode;
            dataViews[0]->updateCameraMode();
        } else {
            renderingMode = newState.renderingMode;
            setRenderer(sceneData, oldRenderingMode, renderingMode, volumeRenderer, 0);
        }
    }

    // 2.2. Set the new renderer settings.
    bool reloadGatherShader = false;
    std::vector<bool> reloadGatherShaderDataViewList;
    if (useDockSpaceMode) {
        for (DataViewPtr& dataView : dataViews) {
            bool reloadGatherShaderLocal = reloadGatherShader;
            if (dataView->volumeRenderer) {
                dataView->volumeRenderer->setNewState(newState);
                reloadGatherShaderLocal |= dataView->volumeRenderer->setNewSettings(newState.rendererSettings);
                reloadGatherShaderDataViewList.push_back(reloadGatherShaderLocal);
            }
        }
    } else {
        if (volumeRenderer) {
            volumeRenderer->setNewState(newState);
            reloadGatherShader |= volumeRenderer->setNewSettings(newState.rendererSettings);
        }
    }

    // 3. Load the correct data set file.
    if (newState.dataSetDescriptor != lastState.dataSetDescriptor) {
        selectedDataSetIndex = 0;
        std::string nameLower = boost::algorithm::to_lower_copy(newState.dataSetDescriptor.name);
        for (size_t i = 0; i < dataSetInformationList.size(); i++) {
            if (boost::algorithm::to_lower_copy(dataSetInformationList.at(i)->name) == nameLower) {
                selectedDataSetIndex = int(i) + NUM_MANUAL_LOADERS;
                break;
            }
        }
        if (selectedDataSetIndex == 0) {
            if (dataSetInformationList.at(selectedDataSetIndex - NUM_MANUAL_LOADERS)->type
                    == DATA_SET_TYPE_STRESS_VOLUMES && newState.dataSetDescriptor.enabledFileIndices.size() == 3) {
                VolumeDataStress::setUseMajorPS(newState.dataSetDescriptor.enabledFileIndices.at(0));
                VolumeDataStress::setUseMediumPS(newState.dataSetDescriptor.enabledFileIndices.at(1));
                VolumeDataStress::setUseMinorPS(newState.dataSetDescriptor.enabledFileIndices.at(2));
            }
            loadVolumeDataSet(newState.dataSetDescriptor.filenames, true);
        } else {
            if (newState.dataSetDescriptor.type == DATA_SET_TYPE_STRESS_VOLUMES
                && newState.dataSetDescriptor.enabledFileIndices.size() == 3) {
                VolumeDataStress::setUseMajorPS(newState.dataSetDescriptor.enabledFileIndices.at(0));
                VolumeDataStress::setUseMediumPS(newState.dataSetDescriptor.enabledFileIndices.at(1));
                VolumeDataStress::setUseMinorPS(newState.dataSetDescriptor.enabledFileIndices.at(2));
            }
            loadVolumeDataSet(getSelectedDataSetFilenames(), true);
        }
    }

    // 4. Pass state change to filters to handle internally necessary state changes.
    for (VolumeFilter* filter : dataFilters) {
        filter->setNewState(newState);
    }
    for (size_t i = 0; i < newState.filterSettings.size(); i++) {
        dataFilters.at(i)->setNewSettings(newState.filterSettings.at(i));
    }

    // 5. Pass state change to renderers to handle internally necessary state changes.
    if (volumeData) {
        reloadGatherShader |= volumeData->setNewSettings(newState.dataSetSettings);
    }

    // 6. Reload the gather shader if necessary.
    if (useDockSpaceMode) {
        size_t idx = 0;
        for (DataViewPtr& dataView : dataViews) {
            bool reloadGatherShaderLocal = reloadGatherShader || reloadGatherShaderDataViewList.at(idx);
            if (dataView->volumeRenderer && reloadGatherShaderLocal) {
                dataView->volumeRenderer->reloadGatherShaderExternal();
            }
            idx++;
        }
    } else {
        if (volumeRenderer && reloadGatherShader) {
            volumeRenderer->reloadGatherShaderExternal();
        }
    }*/

    recordingTime = 0.0f;
    recordingTimeLast = 0.0f;
    recordingTimeStampStart = sgl::Timer->getTicksMicroseconds();
    lastState = newState;
    firstState = false;
    usesNewState = true;
}

void MainApp::scheduleRecreateSceneFramebuffer() {
    scheduledRecreateSceneFramebuffer = true;
}

void MainApp::addNewRenderer(RenderingMode renderingMode) {
    RendererPtr volumeRenderer;
    setRenderer(renderingMode, volumeRenderer);

    // Opaque surface renderers are always added before transparent renderers.
    bool isOpaqueRenderer =
            renderingMode != RenderingMode::RENDERING_MODE_DIRECT_VOLUME_RENDERING
            && renderingMode != RenderingMode::RENDERING_MODE_DIAGRAM_RENDERER;
    bool isOverlayRenderer = renderingMode == RenderingMode::RENDERING_MODE_DIAGRAM_RENDERER;
    if (isOpaqueRenderer && !isOverlayRenderer) {
        // Push after last opaque renderer (or at the beginning).
        auto it = volumeRenderers.begin();
        while (it != volumeRenderers.end() && it->get()->getIsOpaqueRenderer()) {
            ++it;
        }
        volumeRenderers.insert(it, volumeRenderer);
    } else if (!isOverlayRenderer) {
        // Push before the last overlay renderer.
        auto it = volumeRenderers.begin();
        while (it != volumeRenderers.end() && !it->get()->getIsOverlayRenderer()) {
            ++it;
        }
        volumeRenderers.insert(it, volumeRenderer);
    } else {
        volumeRenderers.push_back(volumeRenderer);
    }
}

void MainApp::setRenderer(RenderingMode newRenderingMode, RendererPtr& newVolumeRenderer) {
    size_t creationId;
    if (newVolumeRenderer) {
        creationId = newVolumeRenderer->getCreationId();
        device->waitIdle();
        newVolumeRenderer = {};
    } else {
        creationId = rendererCreationCounter;
        rendererCreationCounter++;
    }

    if (newRenderingMode == RENDERING_MODE_DIRECT_VOLUME_RENDERING) {
        newVolumeRenderer = std::make_shared<DvrRenderer>(viewManager);
    } else if (newRenderingMode == RENDERING_MODE_ISOSURFACE_RAYCASTER) {
        newVolumeRenderer = std::make_shared<IsoSurfaceRayCastingRenderer>(viewManager);
    } else if (newRenderingMode == RENDERING_MODE_ISOSURFACE_RASTERIZER) {
        newVolumeRenderer = std::make_shared<IsoSurfaceRasterizer>(viewManager);
    } else if (newRenderingMode == RENDERING_MODE_DOMAIN_OUTLINE_RENDERER) {
        newVolumeRenderer = std::make_shared<DomainOutlineRenderer>(viewManager);
    } else if (newRenderingMode == RENDERING_MODE_SLICE_RENDERER) {
        newVolumeRenderer = std::make_shared<SliceRenderer>(viewManager);
    } else if (newRenderingMode == RENDERING_MODE_WORLD_MAP_RENDERER) {
        newVolumeRenderer = std::make_shared<WorldMapRenderer>(viewManager);
    } else if (newRenderingMode == RENDERING_MODE_DIAGRAM_RENDERER) {
        newVolumeRenderer = std::make_shared<DiagramRenderer>(viewManager);
    } else {
        int idx = std::clamp(int(newRenderingMode), 0, IM_ARRAYSIZE(RENDERING_MODE_NAMES) - 1);
        std::string warningText =
                std::string() + "The selected renderer \"" + RENDERING_MODE_NAMES[idx] + "\" is not "
                + "supported in this build configuration or incompatible with this system.";
        onUnsupportedRendererSelected(warningText, newVolumeRenderer);
    }

    newVolumeRenderer->setCreationId(creationId);
    newVolumeRenderer->initialize();
    newVolumeRenderer->setUseLinearRGB(useLinearRGB);
    newVolumeRenderer->setFileDialogInstance(fileDialogInstance);

    for (size_t viewIdx = 0; viewIdx < dataViews.size(); viewIdx++) {
        newVolumeRenderer->addView(uint32_t(viewIdx));
        auto& viewSceneData = dataViews.at(viewIdx)->sceneData;
        if (*viewSceneData.sceneTexture) {
            newVolumeRenderer->recreateSwapchainView(
                    uint32_t(viewIdx), *viewSceneData.viewportWidthVirtual, *viewSceneData.viewportHeightVirtual);
        }
    }
}

void MainApp::onUnsupportedRendererSelected(const std::string& warningText, RendererPtr& newVolumeRenderer) {
    sgl::Logfile::get()->writeWarning(
            "Warning in MainApp::setRenderer: " + warningText, false);
    auto handle = sgl::dialog::openMessageBox(
            "Unsupported Renderer", warningText, sgl::dialog::Icon::WARNING);
    nonBlockingMsgBoxHandles.push_back(handle);
    newVolumeRenderer = std::make_shared<DvrRenderer>(viewManager);
}

void MainApp::resolutionChanged(sgl::EventPtr event) {
    SciVisApp::resolutionChanged(event);
    if (!useDockSpaceMode) {
        auto* window = sgl::AppSettings::get()->getMainWindow();
        viewportWidth = uint32_t(window->getWidth());
        viewportHeight = uint32_t(window->getHeight());
        for (auto& volumeRenderer : volumeRenderers) {
            volumeRenderer->recreateSwapchainView(0, viewportWidth, viewportHeight);
        }
        if (volumeData) {
            volumeData->recreateSwapchainView(0, viewportWidth, viewportHeight);
        }
    }
}

void MainApp::updateColorSpaceMode() {
    SciVisApp::updateColorSpaceMode();
    volumeData->setUseLinearRGB(useLinearRGB);
    if (useDockSpaceMode) {
        for (DataViewPtr& dataView : dataViews) {
            dataView->useLinearRGB = useLinearRGB;
            dataView->viewportWidth = 0;
            dataView->viewportHeight = 0;
        }
    }
    for (auto& volumeRenderer : volumeRenderers) {
        volumeRenderer->setUseLinearRGB(useLinearRGB);
    }
}

void MainApp::beginFrameMarker() {
#ifdef SUPPORT_RENDERDOC_DEBUGGER
    renderDocDebugger.startFrameCapture();
#endif
}

void MainApp::endFrameMarker() {
#ifdef SUPPORT_RENDERDOC_DEBUGGER
    renderDocDebugger.endFrameCapture();
#endif
}

void MainApp::render() {
    // Debug Code.
    /*static bool isFirstFrame = true;
    if (isFirstFrame) {
        selectedDataSetIndex = 0;
        customDataSetFileName = "/home/christoph/datasets/Toy/chord/linear_4x4.nc";
        dataSetType = DataSetType::VOLUME;
        loadVolumeDataSet({ customDataSetFileName });
        isFirstFrame = false;
    }

    static int frameNum = 0;
    frameNum++;
    if (frameNum == 10) {
        quit();
    }*/

    if (usePerformanceMeasurementMode) {
        performanceMeasurer->beginRenderFunction();
    }

    if (scheduledRecreateSceneFramebuffer) {
        device->waitIdle();
        sgl::vk::Swapchain* swapchain = sgl::AppSettings::get()->getSwapchain();
        createSceneFramebuffer();
        if (swapchain && sgl::AppSettings::get()->getUseGUI()) {
            sgl::ImGuiWrapper::get()->setVkRenderTarget(compositedTextureVk->getImageView());
            sgl::ImGuiWrapper::get()->onResolutionChanged();
        }
        if (videoWriter) {
            videoWriter->onSwapchainRecreated();
        }
        scheduledRecreateSceneFramebuffer = false;
    }

    SciVisApp::preRender();
    if (useDockSpaceMode) {
        for (DataViewPtr& dataView : dataViews) {
            dataView->saveScreenshotDataIfAvailable();
        }
    }

    if (!useDockSpaceMode) {
        prepareVisualizationPipeline();

        componentOtherThanRendererNeedsReRender = reRender;
        if (volumeData != nullptr) {
            bool volumeDataNeedsReRender = volumeData->needsReRender();
            reRender = reRender || volumeDataNeedsReRender;
            componentOtherThanRendererNeedsReRender = componentOtherThanRendererNeedsReRender || volumeDataNeedsReRender;
        }
    }

    if (!useDockSpaceMode) {
        for (auto& volumeRenderer : volumeRenderers) {
            reRender = reRender || volumeRenderer->needsReRender();
            //componentOtherThanRendererNeedsReRender |= volumeRenderer->needsInternalReRender();
        }
        if (componentOtherThanRendererNeedsReRender) {
            // If the re-rendering was triggered from an outside source, frame accumulation cannot be used!
            for (auto& volumeRenderer : volumeRenderers) {
                volumeRenderer->notifyReRenderTriggeredExternally();
            }
        }

        if (reRender || continuousRendering) {
            if (usePerformanceMeasurementMode) {
                performanceMeasurer->startMeasure(recordingTimeLast);
            }

            SciVisApp::prepareReRender();

            // TODO
            if (screenshotTransparentBackground) {
                clearColor.setA(0);
            }
            rendererVk->insertImageMemoryBarriers(
                    { sceneTextureVk->getImage(), sceneDepthTextureVk->getImage() },
                    VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL,
                    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
                    VK_ACCESS_NONE_KHR, VK_ACCESS_TRANSFER_WRITE_BIT);
            sceneTextureVk->getImageView()->clearColor(
                    clearColor.getFloatColorRGBA(), rendererVk->getVkCommandBuffer());
            sceneDepthTextureVk->getImageView()->clearDepthStencil(
                    1.0f, 0, rendererVk->getVkCommandBuffer());
            if (screenshotTransparentBackground) {
                clearColor.setA(255);
            }

            if (volumeData) {
                volumeData->renderViewCalculator(0);
            }
            for (auto& volumeRenderer : volumeRenderers) {
                volumeRenderer->renderView(0);
            }

            if (usePerformanceMeasurementMode) {
                performanceMeasurer->endMeasure();
            }

            reRender = false;
        }
    }

    SciVisApp::postRender();

    if (useDockSpaceMode && !dataViews.empty() && !uiOnScreenshot && recording && !isFirstRecordingFrame) {
        auto dataView = dataViews.at(0);
        if (dataView->viewportWidth > 0 && dataView->viewportHeight > 0) {
            rendererVk->transitionImageLayout(
                    dataView->compositedTextureVk->getImage(),
                    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
            videoWriter->pushFramebufferImage(dataView->compositedTextureVk->getImage());
            rendererVk->transitionImageLayout(
                    dataView->compositedTextureVk->getImage(),
                    VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
        }
    }
}

void MainApp::renderGui() {
    focusedWindowIndex = -1;
    mouseHoverWindowIndex = -1;

    if (useReplicabilityStampMode) {
        const auto NUM_STEPS = int(sgl::AppSettings::get()->getSwapchain()->getNumImages());
        if (replicabilityFrameNumber < NUM_STEPS) {
            replicabilityFrameNumber++;
        } else if (replicabilityFrameNumber == NUM_STEPS) {
            useReplicabilityStampMode = false;
            replicabilityFrameNumber++;
            loadReplicabilityStampState();
        }
    }

    if (sgl::Keyboard->keyPressed(SDLK_o) && (sgl::Keyboard->getModifier() & (KMOD_LCTRL | KMOD_RCTRL)) != 0) {
        openFileDialog();
    }

    if (IGFD_DisplayDialog(
            fileDialogInstance,
            "ChooseDataSetFile", ImGuiWindowFlags_NoCollapse,
            sgl::ImGuiWrapper::get()->getScaleDependentSize(1000, 580),
            ImVec2(FLT_MAX, FLT_MAX))) {
        if (IGFD_IsOk(fileDialogInstance)) {
            std::string filePathName = IGFD_GetFilePathName(fileDialogInstance);
            std::string filePath = IGFD_GetCurrentPath(fileDialogInstance);
            std::string filter = IGFD_GetCurrentFilter(fileDialogInstance);
            std::string userDatas;
            if (IGFD_GetUserDatas(fileDialogInstance)) {
                userDatas = std::string((const char*)IGFD_GetUserDatas(fileDialogInstance));
            }
            auto selection = IGFD_GetSelection(fileDialogInstance);

            const char* currentPath = IGFD_GetCurrentPath(fileDialogInstance);
            std::string filename = currentPath;
            if (!filename.empty() && filename.back() != '/' && filename.back() != '\\') {
                filename += "/";
            }
            if (selection.count != 0) {
                filename += selection.table[0].fileName;
            }
            IGFD_Selection_DestroyContent(&selection);
            if (currentPath) {
                free((void*)currentPath);
                currentPath = nullptr;
            }

            fileDialogDirectory = sgl::FileUtils::get()->getPathToFile(filename);

            std::string filenameLower = boost::to_lower_copy(filename);

            if (boost::ends_with(filenameLower, ".vtk")
                    || boost::ends_with(filenameLower, ".vti")
                    || boost::ends_with(filenameLower, ".vts")
                    || boost::ends_with(filenameLower, ".nc")
                    || boost::ends_with(filenameLower, ".zarr")
                    || boost::ends_with(filenameLower, ".am")
                    || boost::ends_with(filenameLower, ".bin")
                    || boost::ends_with(filenameLower, ".field")
                    || boost::ends_with(filenameLower, ".cvol")
                    || boost::ends_with(filenameLower, ".nii")
#ifdef USE_ECCODES
                    || boost::ends_with(filenameLower, ".grib")
                    || boost::ends_with(filenameLower, ".grb")
#endif
                    || boost::ends_with(filenameLower, ".dat")
                    || boost::ends_with(filenameLower, ".raw")) {
                selectedDataSetIndex = 0;
                customDataSetFileName = filename;
                dataSetType = DataSetType::VOLUME;
                loadVolumeDataSet(getSelectedDataSetFilenames());
            } else {
                sgl::Logfile::get()->writeError(
                        "The selected file name has an unknown extension \""
                        + sgl::FileUtils::get()->getFileExtension(filenameLower) + "\".");
            }
        }
        IGFD_CloseDialog(fileDialogInstance);
    }

    if (IGFD_DisplayDialog(
            fileDialogInstance,
            "ChooseExportFieldFile", ImGuiWindowFlags_NoCollapse,
            sgl::ImGuiWrapper::get()->getScaleDependentSize(1000, 580),
            ImVec2(FLT_MAX, FLT_MAX))) {
        if (IGFD_IsOk(fileDialogInstance)) {
            std::string filePathName = IGFD_GetFilePathName(fileDialogInstance);
            std::string filePath = IGFD_GetCurrentPath(fileDialogInstance);
            std::string filter = IGFD_GetCurrentFilter(fileDialogInstance);
            std::string userDatas;
            if (IGFD_GetUserDatas(fileDialogInstance)) {
                userDatas = std::string((const char*)IGFD_GetUserDatas(fileDialogInstance));
            }
            auto selection = IGFD_GetSelection(fileDialogInstance);

            const char* currentPath = IGFD_GetCurrentPath(fileDialogInstance);
            std::string filename = currentPath;
            if (!filename.empty() && filename.back() != '/' && filename.back() != '\\') {
                filename += "/";
            }
            std::string currentFileName;
            if (filter == ".*") {
                currentFileName = IGFD_GetCurrentFileNameRaw(fileDialogInstance);
            } else {
                currentFileName = IGFD_GetCurrentFileName(fileDialogInstance);
            }
            if (selection.count != 0 && selection.table[0].fileName == currentFileName) {
                filename += selection.table[0].fileName;
            } else {
                filename += currentFileName;
            }
            IGFD_Selection_DestroyContent(&selection);
            if (currentPath) {
                free((void*)currentPath);
                currentPath = nullptr;
            }

            exportFieldFileDialogDirectory = sgl::FileUtils::get()->getPathToFile(filename);

            std::string filenameLower = boost::to_lower_copy(filename);
            if (boost::ends_with(filenameLower, ".nc") || boost::ends_with(filenameLower, ".cvol")) {
                volumeData->saveFieldToFile(filename, FieldType::SCALAR, selectedFieldIndexExport);
            } else {
                sgl::Logfile::get()->writeError(
                        "The selected file name has an unsupported extension \""
                        + sgl::FileUtils::get()->getFileExtension(filenameLower) + "\".");
            }
        }
        IGFD_CloseDialog(fileDialogInstance);
    }

    if (IGFD_DisplayDialog(
            fileDialogInstance,
            "ChooseStateFile", ImGuiWindowFlags_NoCollapse,
            sgl::ImGuiWrapper::get()->getScaleDependentSize(1000, 580),
            ImVec2(FLT_MAX, FLT_MAX))) {
        if (IGFD_IsOk(fileDialogInstance)) {
            std::string filePathName = IGFD_GetFilePathName(fileDialogInstance);
            std::string filePath = IGFD_GetCurrentPath(fileDialogInstance);
            std::string filter = IGFD_GetCurrentFilter(fileDialogInstance);
            std::string userDatas;
            if (IGFD_GetUserDatas(fileDialogInstance)) {
                userDatas = std::string((const char*)IGFD_GetUserDatas(fileDialogInstance));
            }
            auto selection = IGFD_GetSelection(fileDialogInstance);

            const char* currentPath = IGFD_GetCurrentPath(fileDialogInstance);
            std::string filename = currentPath;
            if (!filename.empty() && filename.back() != '/' && filename.back() != '\\') {
                filename += "/";
            }
            std::string currentFileName;
            if (filter == ".*") {
                currentFileName = IGFD_GetCurrentFileNameRaw(fileDialogInstance);
            } else {
                currentFileName = IGFD_GetCurrentFileName(fileDialogInstance);
            }
            if (selection.count != 0 && selection.table[0].fileName == currentFileName) {
                filename += selection.table[0].fileName;
            } else {
                filename += currentFileName;
            }
            IGFD_Selection_DestroyContent(&selection);
            if (currentPath) {
                free((void*)currentPath);
                currentPath = nullptr;
            }

            stateFileDirectory = sgl::FileUtils::get()->getPathToFile(filename);
            if (stateModeSave) {
                saveStateToFile(filename);
            } else {
                loadStateFromFile(filename);
            }
        }
        IGFD_CloseDialog(fileDialogInstance);
    }

    if (useDockSpaceMode) {
        if (isFirstFrame && dataViews.size() == 1) {
            if (volumeRenderers.empty()) {
                initializeFirstDataView();
            }
            isFirstFrame = false;
        }

        static bool isProgramStartup = true;
        ImGuiID dockSpaceId = ImGui::DockSpaceOverViewport(ImGui::GetMainViewport());
        if (isProgramStartup) {
            ImGuiDockNode* centralNode = ImGui::DockBuilderGetNode(dockSpaceId);
            if (centralNode->IsEmpty()) {
                auto* window = sgl::AppSettings::get()->getMainWindow();
                //const ImVec2 dockSpaceSize = ImGui::GetMainViewport()->Size;//ImGui::GetContentRegionAvail();
                const ImVec2 dockSpaceSize(float(window->getWidth()), float(window->getHeight()));
                ImGui::DockBuilderSetNodeSize(dockSpaceId, dockSpaceSize);

                ImGuiID dockLeftId, dockMainId;
                ImGui::DockBuilderSplitNode(
                        dockSpaceId, ImGuiDir_Left, 0.29f, &dockLeftId, &dockMainId);
                ImGui::DockBuilderSetNodeSize(dockLeftId, ImVec2(dockSpaceSize.x * 0.29f, dockSpaceSize.y));
                ImGui::DockBuilderDockWindow("Data View###data_view_0", dockMainId);

                ImGuiID dockLeftUpId, dockLeftDownId;
                ImGui::DockBuilderSplitNode(
                        dockLeftId, ImGuiDir_Up, 0.45f, &dockLeftUpId, &dockLeftDownId);
                ImGui::DockBuilderDockWindow("Property Editor", dockLeftUpId);

                ImGuiID dockLeftDownUpId, dockLeftDownDownId;
                ImGui::DockBuilderSplitNode(
                        dockLeftDownId, ImGuiDir_Up, 0.28f,
                        &dockLeftDownUpId, &dockLeftDownDownId);
                ImGui::DockBuilderDockWindow("Transfer Function", dockLeftDownDownId);
                ImGui::DockBuilderDockWindow("Multi-Var Transfer Function", dockLeftDownDownId);
                ImGui::DockBuilderDockWindow("Camera Checkpoints", dockLeftDownUpId);
                ImGui::DockBuilderDockWindow("Replay Widget", dockLeftDownUpId);

                ImGui::DockBuilderFinish(dockSpaceId);
            }
            isProgramStartup = false;
        }

        renderGuiMenuBar();

        if (showPropertyEditor) {
            renderGuiPropertyEditorWindow();
        }

        prepareVisualizationPipeline();

        componentOtherThanRendererNeedsReRender = reRender;
        if (volumeData != nullptr) {
            bool volumeDataNeedsReRender = volumeData->needsReRender();
            reRender = reRender || volumeDataNeedsReRender;
            componentOtherThanRendererNeedsReRender = componentOtherThanRendererNeedsReRender || volumeDataNeedsReRender;
        }

        bool rendererNeedsReRender = false;
        bool componentOtherThanRendererNeedsReRenderLocal = componentOtherThanRendererNeedsReRender;
        for (auto& volumeRenderer : volumeRenderers) {
            rendererNeedsReRender |= volumeRenderer->needsReRender();
            //componentOtherThanRendererNeedsReRenderLocal |= volumeRenderer->needsInternalReRender();
        }
        if (componentOtherThanRendererNeedsReRenderLocal) {
            // If the re-rendering was triggered from an outside source, frame accumulation cannot be used!
            for (auto& volumeRenderer : volumeRenderers) {
                volumeRenderer->notifyReRenderTriggeredExternally();
            }
        }

        int first3dViewIdx = 0;
        for (int i = 0; i < int(dataViews.size()); i++) {
            for (auto& renderer : volumeRenderers) {
                if (renderer->isVisibleInView(i) && !renderer->getIsOverlayRenderer()) {
                    first3dViewIdx = i;
                    break;
                }
            }
        }

        for (int i = 0; i < int(dataViews.size()); i++) {
            auto viewIdx = uint32_t(i);
            DataViewPtr& dataView = dataViews.at(i);
            if (dataView->showWindow) {
                std::string windowName = dataView->getWindowNameImGui(dataViews, i);
                bool isViewOpen = true;
                sgl::ImGuiWrapper::get()->setNextWindowStandardSize(800, 600);
                ImGui::SetNextTabbarMenu([this] {
                    if (ImGui::BeginPopup("#NewTab")) {
                        addNewDataView();
                        ImGui::EndPopup();
                    }

                    return "#NewTab";
                });
                if (ImGui::Begin(windowName.c_str(), &isViewOpen)) {
                    if (ImGui::IsWindowFocused()) {
                        focusedWindowIndex = i;
                    }
                    sgl::ImGuiWrapper::get()->setWindowViewport(i, ImGui::GetWindowViewport());
                    sgl::ImGuiWrapper::get()->setWindowViewport(i, ImGui::GetWindowViewport());
                    sgl::ImGuiWrapper::get()->setWindowPosAndSize(i, ImGui::GetWindowPos(), ImGui::GetWindowSize());

                    ImVec2 cursorPos = ImGui::GetCursorScreenPos();
                    dataView->viewportPositionX = int32_t(cursorPos.x);
                    dataView->viewportPositionY = int32_t(cursorPos.y);
                    ImVec2 sizeContent = ImGui::GetContentRegionAvail();
                    if (useFixedSizeViewport) {
                        sizeContent = ImVec2(float(fixedViewportSize.x), float(fixedViewportSize.y));
                    }
                    if (int(sizeContent.x) != int(dataView->viewportWidth)
                            || int(sizeContent.y) != int(dataView->viewportHeight)) {
                        rendererVk->getDevice()->waitIdle();
                        dataView->resize(int(sizeContent.x), int(sizeContent.y));
                        if (dataView->viewportWidth > 0 && dataView->viewportHeight > 0) {
                            for (auto& volumeRenderer : volumeRenderers) {
                                volumeRenderer->recreateSwapchainView(
                                        viewIdx, dataView->viewportWidthVirtual, dataView->viewportHeightVirtual);
                            }
                            if (volumeData) {
                                volumeData->recreateSwapchainView(
                                        viewIdx, dataView->viewportWidthVirtual, dataView->viewportHeightVirtual);
                            }
                        }
                        dataView->reRender = true;
                    }

                    bool reRenderLocal = reRender || dataView->reRender || rendererNeedsReRender;
                    dataView->reRender = false;
                    for (auto& volumeRenderer : volumeRenderers) {
                        reRenderLocal |= volumeRenderer->needsReRenderView(viewIdx);
                    }

                    if (dataView->viewportWidth > 0 && dataView->viewportHeight > 0
                            && (reRenderLocal || continuousRendering)) {
                        dataView->beginRender();

                        if (usePerformanceMeasurementMode) {
                            performanceMeasurer->startMeasure(recordingTimeLast);
                        }

                        if (volumeData) {
                            volumeData->renderViewCalculator(viewIdx);
                            for (auto& volumeRenderer : volumeRenderers) {
                                volumeRenderer->renderViewPre(viewIdx);
                            }
                            int rendererIdx = 0;
                            for (auto& volumeRenderer : volumeRenderers) {
                                volumeRenderer->renderView(viewIdx);
                                if (rendererIdx != int(volumeRenderers.size() - 1) && volumeRenderer->getIsOpaqueRenderer()
                                        && !volumeRenderers.at(rendererIdx + 1)->getIsOpaqueRenderer()) {
                                    volumeData->renderViewCalculatorPostOpaque(viewIdx);
                                    for (auto& volumeRendererPostOpaque : volumeRenderers) {
                                        volumeRendererPostOpaque->renderViewPostOpaque(viewIdx);
                                    }
                                }
                                rendererIdx++;
                            }
                        }

                        if (usePerformanceMeasurementMode) {
                            performanceMeasurer->endMeasure();
                        }

                        reRenderLocal = false;

                        dataView->endRender();
                    }

                    if (dataView->viewportWidth > 0 && dataView->viewportHeight > 0) {
                        if (!uiOnScreenshot && screenshot) {
                            printNow = true;
                            std::string screenshotFilename =
                                    saveDirectoryScreenshots + saveFilenameScreenshots
                                    + "_" + sgl::toString(screenshotNumber);
                            if (dataViews.size() > 1) {
                                screenshotFilename += "_view" + sgl::toString(i);
                            }
                            screenshotFilename += ".png";

                            dataView->screenshotReadbackHelper->setScreenshotTransparentBackground(
                                    screenshotTransparentBackground);
                            dataView->saveScreenshot(screenshotFilename);
                            screenshot = false;

                            printNow = false;
                            screenshot = true;
                        }

                        if (isViewOpen) {
                            ImTextureID textureId = dataView->getImGuiTextureId();
                            ImGui::Image(
                                    textureId, sizeContent,
                                    ImVec2(0, 0), ImVec2(1, 1));
                            if (ImGui::IsItemHovered()) {
                                mouseHoverWindowIndex = i;
                            }
                        }

                        if (i == first3dViewIdx && showFpsOverlay) {
                            renderGuiFpsOverlay();
                        }
                        if (i == first3dViewIdx && showCoordinateAxesOverlay) {
                            renderGuiCoordinateAxesOverlay(dataView->camera);
                        }

                        for (auto& volumeRenderer : volumeRenderers) {
                            volumeRenderer->renderGuiOverlay(viewIdx);
                        }
                        if (volumeData) {
                            volumeData->renderGuiOverlay(viewIdx);
                        }
                    }
                }
                ImGui::End();

                if (!isViewOpen) {
                    viewManager->removeView(i);
                    dataViews.erase(dataViews.begin() + i);
                    for (auto& volumeRenderer : volumeRenderers) {
                        volumeRenderer->removeView(viewIdx);
                    }
                    if (volumeData) {
                        volumeData->removeView(viewIdx);
                    }
                    i--;
                }
            }
        }

        for (auto& volumeRenderer : volumeRenderers) {
            volumeRenderer->renderGuiWindowSecondary();
        }

        if (!uiOnScreenshot && screenshot) {
            screenshot = false;
            screenshotNumber++;
        }
        reRender = false;
    } else {
        if (showPropertyEditor) {
            renderGuiPropertyEditorWindow();
        }

        for (auto& volumeRenderer : volumeRenderers) {
            volumeRenderer->renderGuiOverlay(0);
        }
        if (volumeData) {
            volumeData->renderGuiOverlay(0);
        }
    }

    if (checkpointWindow.renderGui()) {
        fovDegree = camera->getFOVy() / sgl::PI * 180.0f;
        reRender = true;
        hasMoved();
        onCameraReset();
    }

    if (volumeData) {
        volumeData->renderGuiWindowSecondary();
    }
}

void MainApp::loadAvailableDataSetInformation() {
    dataSetNames.clear();
    dataSetNames.emplace_back("Local file...");
    selectedDataSetIndex = 0;

    const std::string volumeDataSetsDirectory = sgl::AppSettings::get()->getDataDirectory() + "VolumeDataSets/";
    if (sgl::FileUtils::get()->exists(volumeDataSetsDirectory + "datasets.json")) {
        dataSetInformationRoot = loadDataSetList(volumeDataSetsDirectory + "datasets.json");

        std::stack<std::pair<DataSetInformationPtr, size_t>> dataSetInformationStack;
        dataSetInformationStack.push(std::make_pair(dataSetInformationRoot, 0));
        while (!dataSetInformationStack.empty()) {
            std::pair<DataSetInformationPtr, size_t> dataSetIdxPair = dataSetInformationStack.top();
            DataSetInformationPtr dataSetInformationParent = dataSetIdxPair.first;
            size_t idx = dataSetIdxPair.second;
            dataSetInformationStack.pop();
            while (idx < dataSetInformationParent->children.size()) {
                DataSetInformationPtr dataSetInformationChild =
                        dataSetInformationParent->children.at(idx);
                idx++;
                if (dataSetInformationChild->type == DataSetType::NODE) {
                    dataSetInformationStack.push(std::make_pair(dataSetInformationRoot, idx));
                    dataSetInformationStack.push(std::make_pair(dataSetInformationChild, 0));
                    break;
                } else {
                    dataSetInformationChild->sequentialIndex = int(dataSetNames.size());
                    dataSetInformationList.push_back(dataSetInformationChild);
                    dataSetNames.push_back(dataSetInformationChild->name);
                }
            }
        }
    }
}

std::vector<std::string> MainApp::getSelectedDataSetFilenames() {
    std::vector<std::string> filenames;
    if (selectedDataSetIndex == 0) {
        filenames.push_back(customDataSetFileName);
    } else {
        dataSetType = dataSetInformationList.at(selectedDataSetIndex - NUM_MANUAL_LOADERS)->type;
        for (const std::string& filename : dataSetInformationList.at(
                selectedDataSetIndex - NUM_MANUAL_LOADERS)->filenames) {
            filenames.push_back(filename);
        }
    }
    return filenames;
}

void MainApp::renderGuiGeneralSettingsPropertyEditor() {
    if (propertyEditor.addColorEdit3("Clear Color", (float*)&clearColorSelection, 0)) {
        clearColor = sgl::colorFromFloat(
                clearColorSelection.x, clearColorSelection.y, clearColorSelection.z, clearColorSelection.w);
        coordinateAxesOverlayWidget.setClearColor(clearColor);
        if (volumeData) {
            volumeData->setClearColor(clearColor);
        }
        for (DataViewPtr& dataView : dataViews) {
            dataView->setClearColor(clearColor);
        }
        for (auto& volumeRenderer : volumeRenderers) {
            volumeRenderer->setClearColor(clearColor);
        }
        reRender = true;
    }

    // TODO: Remove option?
    /*newDockSpaceMode = useDockSpaceMode;
    if (propertyEditor.addCheckbox("Use Docking Mode", &newDockSpaceMode)) {
        scheduledDockSpaceModeChange = true;
    }*/

    if (useDockSpaceMode && propertyEditor.addSliderInt("Supersampling Factor", &supersamplingFactor, 1, 4)) {
        for (DataViewPtr& dataView : dataViews) {
            dataView->supersamplingFactor = supersamplingFactor;
            dataView->viewportWidth = 0;
            dataView->viewportHeight = 0;
        }
    }

    if (propertyEditor.addCheckbox("Fixed Size Viewport", &useFixedSizeViewport)) {
        reRender = true;
    }
    if (useFixedSizeViewport) {
        if (propertyEditor.addSliderInt2Edit("Viewport Size", &fixedViewportSizeEdit.x, 1, 8192)
            == ImGui::EditMode::INPUT_FINISHED) {
            fixedViewportSize = fixedViewportSizeEdit;
            reRender = true;
        }
    }
}

void MainApp::addNewDataView() {
    DataViewPtr dataView = std::make_shared<DataView>(&sceneData);
    dataView->supersamplingFactor = supersamplingFactor;
    dataView->useLinearRGB = useLinearRGB;
    dataView->clearColor = clearColor;
    dataViews.push_back(dataView);
    viewManager->addView(dataView.get(), &dataView->sceneData);
    auto viewIdx = uint32_t(dataViews.size() - 1);
    for (auto& volumeRenderer : volumeRenderers) {
        volumeRenderer->addView(viewIdx);
    }
    if (volumeData) {
        volumeData->addView(viewIdx);
    }
}

void MainApp::initializeFirstDataView() {
    DataViewPtr dataView = dataViews.back();
    addNewRenderer(RENDERING_MODE_DOMAIN_OUTLINE_RENDERER);
    addNewRenderer(RENDERING_MODE_DIRECT_VOLUME_RENDERING);
    // Debug Code.
    //addNewRenderer(RENDERING_MODE_DIAGRAM_RENDERER);
    prepareVisualizationPipeline();
}

void MainApp::openFileDialog() {
    selectedDataSetIndex = 0;
    if (fileDialogDirectory.empty() || !sgl::FileUtils::get()->directoryExists(fileDialogDirectory)) {
        fileDialogDirectory = sgl::AppSettings::get()->getDataDirectory() + "VolumeDataSets/";
        if (!sgl::FileUtils::get()->exists(fileDialogDirectory)) {
            fileDialogDirectory = sgl::AppSettings::get()->getDataDirectory();
        }
    }
    IGFD_OpenModal(
            fileDialogInstance,
            "ChooseDataSetFile", "Choose a File",
            ".*,.vtk,.vti,.vts,.nc,.zarr,.am,.bin,.field,.cvol,.grib,.grb,.dat,.raw",
            fileDialogDirectory.c_str(),
            "", 1, nullptr,
            ImGuiFileDialogFlags_None);
}

void MainApp::openExportFieldFileDialog() {
    if (exportFieldFileDialogDirectory.empty() || !sgl::FileUtils::get()->directoryExists(exportFieldFileDialogDirectory)) {
        exportFieldFileDialogDirectory = sgl::AppSettings::get()->getDataDirectory() + "VolumeDataSets/";
        if (!sgl::FileUtils::get()->exists(exportFieldFileDialogDirectory)) {
            exportFieldFileDialogDirectory = sgl::AppSettings::get()->getDataDirectory();
        }
    }
    IGFD_OpenModal(
            fileDialogInstance,
            "ChooseExportFieldFile", "Choose a File",
            ".*,.nc,.cvol",
            exportFieldFileDialogDirectory.c_str(),
            "", 1, nullptr,
            ImGuiFileDialogFlags_ConfirmOverwrite);
}

void MainApp::openSelectStateDialog() {
    if (stateFileDirectory.empty() || !sgl::FileUtils::get()->directoryExists(exportFieldFileDialogDirectory)) {
        stateFileDirectory = sgl::AppSettings::get()->getDataDirectory() + "States/";
        if (!sgl::FileUtils::get()->exists(stateFileDirectory)) {
            sgl::FileUtils::get()->ensureDirectoryExists(stateFileDirectory);
        }
    }
    IGFD_OpenModal(
            fileDialogInstance, "ChooseStateFile", "Choose a File", ".json", stateFileDirectory.c_str(), "", 1, nullptr,
            stateModeSave ? ImGuiFileDialogFlags_ConfirmOverwrite : ImGuiFileDialogFlags_None);
}

void MainApp::renderGuiMenuBar() {
    bool openExportFieldDialog = false;
    bool openFieldSimilarityDialog = false;
    bool openFieldSimilarityResultDialog = false;
    bool openOptimizeTFDialog = false;
    if (ImGui::BeginMainMenuBar()) {
        if (ImGui::BeginMenu("File")) {
            if (ImGui::MenuItem("Open Dataset...", "CTRL+O")) {
                openFileDialog();
            }

            if (ImGui::BeginMenu("Datasets")) {
                if (dataSetInformationRoot) {
                    std::stack<std::pair<DataSetInformationPtr, size_t>> dataSetInformationStack;
                    dataSetInformationStack.push(std::make_pair(dataSetInformationRoot, 0));
                    while (!dataSetInformationStack.empty()) {
                        std::pair<DataSetInformationPtr, size_t> dataSetIdxPair = dataSetInformationStack.top();
                        DataSetInformationPtr dataSetInformationParent = dataSetIdxPair.first;
                        size_t idx = dataSetIdxPair.second;
                        dataSetInformationStack.pop();
                        while (idx < dataSetInformationParent->children.size()) {
                            DataSetInformationPtr dataSetInformationChild =
                                    dataSetInformationParent->children.at(idx);
                            if (dataSetInformationChild->type == DataSetType::NODE) {
                                if (ImGui::BeginMenu(dataSetInformationChild->name.c_str())) {
                                    dataSetInformationStack.push(std::make_pair(dataSetInformationRoot, idx + 1));
                                    dataSetInformationStack.push(std::make_pair(dataSetInformationChild, 0));
                                    break;
                                }
                            } else {
                                if (ImGui::MenuItem(dataSetInformationChild->name.c_str())) {
                                    selectedDataSetIndex = int(dataSetInformationChild->sequentialIndex);
                                    loadVolumeDataSet(getSelectedDataSetFilenames());
                                }
                            }
                            idx++;
                        }

                        if (idx == dataSetInformationParent->children.size() && !dataSetInformationStack.empty()) {
                            ImGui::EndMenu();
                        }
                    }
                }

                ImGui::EndMenu();
            }

            if (ImGui::MenuItem("Quit", "CTRL+Q")) {
                quit();
            }
            ImGui::EndMenu();
        }
        if (ImGui::BeginMenu("Window")) {
            if (ImGui::BeginMenu("New Renderer...")) {
                for (int i = 0; i < IM_ARRAYSIZE(RENDERING_MODE_NAMES); i++) {
                    if (ImGui::MenuItem(RENDERING_MODE_NAMES[i])) {
                        addNewRenderer(RenderingMode(i));
                    }
                }
                ImGui::EndMenu();
            }
            if (ImGui::MenuItem("New View...")) {
                addNewDataView();
                //if (dataViews.size() == 1) {
                //    initializeFirstDataView();
                //}
            }
            if (volumeData && ImGui::BeginMenu("New Calculator...")) {
                volumeData->renderGuiNewCalculators();
                ImGui::EndMenu();
            }
            ImGui::Separator();
            for (int i = 0; i < int(dataViews.size()); i++) {
                DataViewPtr& dataView = dataViews.at(i);
                std::string windowName = dataView->getWindowNameImGui(dataViews, i);
                if (ImGui::MenuItem(windowName.c_str(), nullptr, dataView->showWindow)) {
                    dataView->showWindow = !dataView->showWindow;
                }
            }
            if (ImGui::MenuItem("FPS Overlay", nullptr, showFpsOverlay)) {
                showFpsOverlay = !showFpsOverlay;
            }
            if (ImGui::MenuItem("Coordinate Axes Overlay", nullptr, showCoordinateAxesOverlay)) {
                showCoordinateAxesOverlay = !showCoordinateAxesOverlay;
            }
            if (ImGui::MenuItem("Property Editor", nullptr, showPropertyEditor)) {
                showPropertyEditor = !showPropertyEditor;
            }
            if (ImGui::MenuItem("Checkpoint Window", nullptr, checkpointWindow.getShowWindow())) {
                checkpointWindow.setShowWindow(!checkpointWindow.getShowWindow());
            }
            if (volumeData) {
                auto& tfWindow = volumeData->getMultiVarTransferFunctionWindow();
                if (ImGui::MenuItem("Transfer Function Window", nullptr, tfWindow.getShowWindow())) {
                    tfWindow.setShowWindow(!tfWindow.getShowWindow());
                }
            }
            //if (ImGui::MenuItem("Replay Widget", nullptr, replayWidget.getShowWindow())) {
            //    replayWidget.setShowWindow(!replayWidget.getShowWindow());
            //}
            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu("Tools")) {
            if (volumeData && ImGui::MenuItem("Export Field...")) {
                openExportFieldDialog = true;
            }
            if (volumeData && ImGui::MenuItem("Compute Field Similarity...")) {
                openFieldSimilarityDialog = true;
            }
            if (volumeData && ImGui::MenuItem("Optimize Transfer Function...")) {
                openOptimizeTFDialog = true;
            }

            if (ImGui::MenuItem("Load State...")) {
                stateModeSave = false;
                openSelectStateDialog();
            }
            if (ImGui::MenuItem("Save State...")) {
                stateModeSave = true;
                openSelectStateDialog();
            }

            if (ImGui::MenuItem("Print Camera State")) {
                std::cout << "Position: (" << camera->getPosition().x << ", " << camera->getPosition().y
                          << ", " << camera->getPosition().z << ")" << std::endl;
                std::cout << "Look At: (" << camera->getLookAtLocation().x << ", " << camera->getLookAtLocation().y
                          << ", " << camera->getLookAtLocation().z << ")" << std::endl;
                std::cout << "Yaw: " << camera->getYaw() << std::endl;
                std::cout << "Pitch: " << camera->getPitch() << std::endl;
                std::cout << "FoVy: " << (camera->getFOVy() / sgl::PI * 180.0f) << std::endl;
            }
            ImGui::EndMenu();
        }

        /*bool isRendererComputationRunning = false;
        for (DataViewPtr& dataView : dataViews) {
            isRendererComputationRunning =
                    isRendererComputationRunning
                    || (dataView->lineRenderer && dataView->lineRenderer->getIsComputationRunning());
            if (isRendererComputationRunning) {
                break;
            }
        }

        if (lineDataRequester.getIsProcessingRequest() || isRendererComputationRunning) {
            ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - ImGui::GetTextLineHeight());
            ImGui::ProgressSpinner(
                    "##progress-spinner", -1.0f, -1.0f, 4.0f,
                    ImVec4(0.1f, 0.5f, 1.0f, 1.0f));
        }*/

        ImGui::EndMainMenuBar();
    }

    if (openExportFieldDialog) {
        ImGui::OpenPopup("Export Field");
    }
    if (ImGui::BeginPopupModal("Export Field", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
        auto fieldNames = volumeData->getFieldNames(FieldType::SCALAR);
        selectedFieldIndexExport = std::min(selectedFieldIndexExport, int(fieldNames.size()) - 1);
        ImGui::Combo(
                "Field Name", &selectedFieldIndexExport,
                fieldNames.data(), int(fieldNames.size()));
        if (ImGui::Button("OK", ImVec2(120, 0))) {
            ImGui::CloseCurrentPopup();
            openExportFieldFileDialog();
        }
        ImGui::SetItemDefaultFocus();
        ImGui::SameLine();
        if (ImGui::Button("Cancel", ImVec2(120, 0))) {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }

    if (openFieldSimilarityDialog) {
        ImGui::OpenPopup("Compute Field Similarity");
    }
    if (ImGui::BeginPopupModal("Compute Field Similarity", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
        auto fieldNames = volumeData->getFieldNames(FieldType::SCALAR);
        similarityFieldIdx0 = std::min(similarityFieldIdx0, int(fieldNames.size()) - 1);
        similarityFieldIdx1 = std::min(similarityFieldIdx1, int(fieldNames.size()) - 1);
        ImGui::Combo(
                "Field #1", &similarityFieldIdx0,
                fieldNames.data(), int(fieldNames.size()));
        ImGui::Combo(
                "Field #2", &similarityFieldIdx1,
                fieldNames.data(), int(fieldNames.size()));
        ImGui::Combo(
                "Correlation Measure", (int*)&correlationMeasureFieldSimilarity,
                CORRELATION_MEASURE_TYPE_NAMES, IM_ARRAYSIZE(CORRELATION_MEASURE_TYPE_NAMES));
        ImGui::Combo("Accuracy", (int*)&useFieldAccuracyDouble, FIELD_ACCURACY_NAMES, 2);
        if (ImGui::Button("OK", ImVec2(120, 0))) {
            ImGui::CloseCurrentPopup();
            openFieldSimilarityResultDialog = true;
        }
        ImGui::SetItemDefaultFocus();
        ImGui::SameLine();
        if (ImGui::Button("Cancel", ImVec2(120, 0))) {
            ImGui::CloseCurrentPopup();
        }
        ImGui::EndPopup();
    }
    if (openFieldSimilarityResultDialog) {
        ImGui::OpenPopup("Field Similarity Result");
        if (useFieldAccuracyDouble) {
            similarityMetricNumber = computeFieldSimilarity<double>(
                    volumeData.get(), similarityFieldIdx0, similarityFieldIdx1, correlationMeasureFieldSimilarity,
                    maxCorrelationValue);
        } else {
            similarityMetricNumber = computeFieldSimilarity<float>(
                    volumeData.get(), similarityFieldIdx0, similarityFieldIdx1, correlationMeasureFieldSimilarity,
                    maxCorrelationValue);
        }
    }
    if (ImGui::BeginPopupModal("Field Similarity Result", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
        auto fieldNames = volumeData->getFieldNames(FieldType::SCALAR);
        std::string resultText0 =
                std::string("Similarity Measure: ")
                + CORRELATION_MEASURE_TYPE_NAMES[int(correlationMeasureFieldSimilarity)];
        std::string resultText1 =
                "Fields: " + sgl::toString(fieldNames.at(similarityFieldIdx0)) + ", "
                + sgl::toString(fieldNames.at(similarityFieldIdx1));
        std::string resultText2 = "Value: " + sgl::toString(similarityMetricNumber);
        if (correlationMeasureFieldSimilarity == CorrelationMeasureType::MUTUAL_INFORMATION_KRASKOV) {
            resultText2 += " (max: " + sgl::toString(maxCorrelationValue) + ")";
        }
        ImGui::TextUnformatted(resultText0.c_str());
        ImGui::TextUnformatted(resultText1.c_str());
        ImGui::TextUnformatted(resultText2.c_str());
        if (ImGui::Button("Close", ImVec2(120, 0))) {
            ImGui::CloseCurrentPopup();
        }
        ImGui::SetItemDefaultFocus();
        ImGui::EndPopup();
    }

    if (openOptimizeTFDialog) {
        tfOptimization->openDialog();
    }
    tfOptimization->renderGuiDialog();
    if (tfOptimization->getNeedsReRender()) {
        reRender = true;
    }
}

void MainApp::renderGuiPropertyEditorBegin() {
    if (!useDockSpaceMode) {
        renderGuiFpsCounter();

        if (ImGui::Combo(
                "Data Set", &selectedDataSetIndex, dataSetNames.data(),
                int(dataSetNames.size()))) {
            if (selectedDataSetIndex >= NUM_MANUAL_LOADERS) {
                loadVolumeDataSet(getSelectedDataSetFilenames());
            }
        }

        /*if (lineDataRequester.getIsProcessingRequest() || lineRenderer->getIsComputationRunning()) {
            ImGui::SameLine();
            ImGui::ProgressSpinner(
                    "##progress-spinner", -1.0f, -1.0f, 4.0f,
                    ImVec4(0.1f, 0.5f, 1.0f, 1.0f));
        }*/

        if (selectedDataSetIndex == 0) {
            ImGui::InputText("##datasetfilenamelabel", &customDataSetFileName);
            ImGui::SameLine();
            if (ImGui::Button("Load File")) {
                loadVolumeDataSet(getSelectedDataSetFilenames());
            }
        }

        ImGui::Separator();
    }
}

void MainApp::renderGuiPropertyEditorCustomNodes() {
    if (volumeData) {
        volumeData->renderGui(propertyEditor);
    }

    if (useDockSpaceMode && dataViews.size() > 1) {
        for (int i = 0; i < int(dataViews.size()); i++) {
            DataViewPtr& dataView = dataViews.at(i);
            bool beginNode = propertyEditor.beginNode(dataView->getWindowNameImGui(dataViews, i));
            if (beginNode) {
                if (propertyEditor.addInputAction("View Name", &dataView->getViewName())) {
                    dataView->reRender = true;
                    for (auto& volumeRenderer : volumeRenderers) {
                        volumeRenderer->notifyReRenderTriggeredExternally();
                    }
                }
                if (propertyEditor.addCheckbox("Sync with Global Camera", &dataView->syncWithParentCamera)) {
                    dataView->reRender = true;
                    for (auto& volumeRenderer : volumeRenderers) {
                        volumeRenderer->notifyReRenderTriggeredExternally();
                    }
                }
                propertyEditor.endNode();
            }
        }
    }
    for (int i = 0; i < int(volumeRenderers.size()); i++) {
        auto& volumeRenderer = volumeRenderers.at(i);
        bool removeRenderer = false;
        std::string windowName =
                volumeRenderer->getWindowName() + "###renderer_" + std::to_string(volumeRenderer->getCreationId());
        bool beginNode = propertyEditor.beginNode(windowName);
        ImGui::SameLine();
        float indentWidth = ImGui::GetContentRegionAvail().x;
        ImGui::Indent(indentWidth);
        std::string buttonName = "X###x_renderer" + std::to_string(i);
        if (ImGui::Button(buttonName.c_str())) {
            removeRenderer = true;
        }
        ImGui::Unindent(indentWidth);
        if (beginNode) {
            std::string previewValue = volumeRenderer->getWindowName();
            if (propertyEditor.addBeginCombo("Rendering Mode", previewValue)) {
                for (int j = 0; j < IM_ARRAYSIZE(RENDERING_MODE_NAMES); j++) {
                    if (ImGui::Selectable(
                            RENDERING_MODE_NAMES[int(j)], false,
                            ImGuiSelectableFlags_::ImGuiSelectableFlags_DontClosePopups)) {
                        ImGui::CloseCurrentPopup();
                        setRenderer(RenderingMode(j), volumeRenderer);
                        prepareVisualizationPipeline();
                        reRender = true;
                    }
                }
                propertyEditor.addEndCombo();
            }

            volumeRenderer->renderGui(propertyEditor);
            propertyEditor.endNode();
        }
        if (removeRenderer) {
            reRender = true;
            volumeRenderers.erase(volumeRenderers.begin() + i);
            i--;
        }
    }

    if (volumeData) {
        volumeData->renderGuiCalculators(propertyEditor);
    }
}

void MainApp::update(float dt) {
    sgl::SciVisApp::update(dt);

    for (int i = 0; i < int(nonBlockingMsgBoxHandles.size()); i++) {
        auto& handle = nonBlockingMsgBoxHandles.at(i);
        if (handle->ready(0)) {
            nonBlockingMsgBoxHandles.erase(nonBlockingMsgBoxHandles.begin() + i);
            i--;
        }
    }

    if (scheduledDockSpaceModeChange) {
        if (useDockSpaceMode) {
            dataViews.clear();
        } else {
            addNewDataView();
        }

        useDockSpaceMode = newDockSpaceMode;
        scheduledDockSpaceModeChange = false;
    }

    if (usePerformanceMeasurementMode && !performanceMeasurer->update(recordingTime)) {
        // All modes were tested -> quit.
        quit();
    }

#ifdef SUPPORT_RENDERDOC_DEBUGGER
    renderDocDebugger.update();
#endif

    updateCameraFlight(volumeData.get() != nullptr, usesNewState);

    viewManager->setMouseHoverWindowIndex(mouseHoverWindowIndex);

    if (volumeData) {
        volumeData->update(dt);
    }

    ImGuiIO &io = ImGui::GetIO();
    if (!io.WantCaptureKeyboard || recording || focusedWindowIndex != -1) {
        if (useDockSpaceMode) {
            for (int i = 0; i < int(dataViews.size()); i++) {
                DataViewPtr& dataView = dataViews.at(i);
                if (i != focusedWindowIndex) {
                    continue;
                }

                sgl::CameraPtr parentCamera = this->camera;
                bool reRenderOld = reRender;
                if (!dataView->syncWithParentCamera) {
                    this->camera = dataView->camera;
                    hasMovedIndex = i;
                }
                this->reRender = false;
                moveCameraKeyboard(dt);
                if (this->reRender && dataView->syncWithParentCamera) {
                    for (DataViewPtr& dataViewLocal : dataViews) {
                        if (dataViewLocal->syncWithParentCamera) {
                            dataViewLocal->reRender = dataView->reRender || this->reRender;
                        }
                    }
                }
                if (!dataView->syncWithParentCamera) {
                    dataView->reRender = dataView->reRender || this->reRender;
                    this->camera = parentCamera;
                    hasMovedIndex = -1;
                }
                this->reRender = reRenderOld;
            }
        } else {
            moveCameraKeyboard(dt);
        }
    }

    // Update in inverse order due to back-to-front composited rendering.
    bool hasGrabbedMouse = io.WantCaptureMouse && mouseHoverWindowIndex < 0;
    for (auto it = volumeRenderers.rbegin(); it != volumeRenderers.rend(); it++) {
        auto& volumeRenderer = *it;
        volumeRenderer->update(dt, hasGrabbedMouse);
        hasGrabbedMouse = hasGrabbedMouse || volumeRenderer->getHasGrabbedMouse();
    }
    if (!io.WantCaptureMouse || mouseHoverWindowIndex != -1) {
        if (useDockSpaceMode) {
            for (int i = 0; i < int(dataViews.size()); i++) {
                DataViewPtr& dataView = dataViews.at(i);
                if (i != mouseHoverWindowIndex) {
                    continue;
                }

                sgl::CameraPtr parentCamera = this->camera;
                bool reRenderOld = reRender;
                if (!dataView->syncWithParentCamera) {
                    this->camera = dataView->camera;
                    hasMovedIndex = i;
                }
                this->reRender = false;
                if (!hasGrabbedMouse) {
                    moveCameraMouse(dt);
                }
                if (this->reRender && dataView->syncWithParentCamera) {
                    for (DataViewPtr& dataViewLocal : dataViews) {
                        if (dataViewLocal->syncWithParentCamera) {
                            dataViewLocal->reRender = dataView->reRender || this->reRender;
                        }
                    }
                }
                if (!dataView->syncWithParentCamera) {
                    dataView->reRender = dataView->reRender || this->reRender;
                    this->camera = parentCamera;
                    hasMovedIndex = -1;
                }
                this->reRender = reRenderOld;
            }
        } else {
            if (!hasGrabbedMouse) {
                moveCameraMouse(dt);
            }
        }
    }
}

void MainApp::hasMoved() {
    if (useDockSpaceMode) {
        if (hasMovedIndex < 0) {
            uint32_t viewIdx = 0;
            for (DataViewPtr& dataView : dataViews) {
                if (dataView->syncWithParentCamera) {
                    dataView->syncCamera();
                }
                for (auto& volumeRenderer : volumeRenderers) {
                    volumeRenderer->onHasMoved(viewIdx);
                }
                viewIdx++;
            }
        } else {
            for (auto& volumeRenderer : volumeRenderers) {
                volumeRenderer->onHasMoved(uint32_t(hasMovedIndex));
            }
        }
    } else {
        for (auto& volumeRenderer : volumeRenderers) {
            volumeRenderer->onHasMoved(0);
        }
    }
}

void MainApp::onCameraReset() {
    if (useDockSpaceMode) {
        for (DataViewPtr& dataView : dataViews) {
            dataView->camera->setNearClipDistance(camera->getNearClipDistance());
            dataView->camera->setFarClipDistance(camera->getFarClipDistance());
            dataView->camera->setYaw(camera->getYaw());
            dataView->camera->setPitch(camera->getPitch());
            dataView->camera->setFOVy(camera->getFOVy());
            dataView->camera->setPosition(camera->getPosition());
            dataView->camera->resetLookAtLocation();
        }
    }
}



// --- Visualization pipeline ---

void MainApp::loadVolumeDataSet(const std::vector<std::string>& fileNames) {
    if (fileNames.empty() || fileNames.front().empty()) {
        volumeData = {};
        return;
    }
    currentlyLoadedDataSetIndex = selectedDataSetIndex;

    DataSetInformation selectedDataSetInformation;
    if (selectedDataSetIndex >= NUM_MANUAL_LOADERS && !dataSetInformationList.empty()) {
        selectedDataSetInformation = *dataSetInformationList.at(selectedDataSetIndex - NUM_MANUAL_LOADERS);
    } else {
        selectedDataSetInformation.type = dataSetType;
        selectedDataSetInformation.filenames = fileNames;
    }

    glm::mat4 transformationMatrix = sgl::matrixIdentity();
    glm::mat4* transformationMatrixPtr = nullptr;
    if (selectedDataSetInformation.hasCustomTransform) {
        transformationMatrix *= selectedDataSetInformation.transformMatrix;
        transformationMatrixPtr = &transformationMatrix;
    }
    if (rotateModelBy90DegreeTurns != 0) {
        transformationMatrix *= glm::rotate(float(rotateModelBy90DegreeTurns) * sgl::HALF_PI, modelRotationAxis);
        transformationMatrixPtr = &transformationMatrix;
    }
    if (selectedDataSetInformation.heightScale != 1.0f) {
        transformationMatrix *= glm::scale(glm::vec3(1.0f, selectedDataSetInformation.heightScale, 1.0f));
        transformationMatrixPtr = &transformationMatrix;
    }

    VolumeDataPtr newVolumeData;
    if (dataSetType == DataSetType::VOLUME) {
        newVolumeData = std::make_shared<VolumeData>(rendererVk);
    } else {
        sgl::Logfile::get()->writeError("Error in MainApp::loadVolumeDataSet: Invalid data set type.");
        return;
    }
    newVolumeData->setFileDialogInstance(fileDialogInstance);
    newVolumeData->setViewManager(viewManager);

    bool dataLoaded = newVolumeData->setInputFiles(fileNames, selectedDataSetInformation, transformationMatrixPtr);
    sgl::ColorLegendWidget::resetStandardSize();

    if (dataLoaded) {
        volumeData = newVolumeData;
        //lineData->onMainThreadDataInit();
        volumeData->recomputeHistogram();
        volumeData->setClearColor(clearColor);
        volumeData->setUseLinearRGB(useLinearRGB);
        for (size_t viewIdx = 0; viewIdx < dataViews.size(); viewIdx++) {
            volumeData->addView(uint32_t(viewIdx));
            auto& viewSceneData = dataViews.at(viewIdx)->sceneData;
            if (*viewSceneData.sceneTexture) {
                volumeData->recreateSwapchainView(
                        uint32_t(viewIdx), *viewSceneData.viewportWidthVirtual, *viewSceneData.viewportHeightVirtual);
            }
        }
        newDataLoaded = true;
        reRender = true;
        boundingBox = volumeData->getBoundingBoxRendering();
        selectedFieldIndexExport = 0;
        similarityFieldIdx0 = 0;
        similarityFieldIdx1 = 0;

        std::string meshDescriptorName = fileNames.front();
        if (fileNames.size() > 1) {
            meshDescriptorName += std::string() + "_" + std::to_string(fileNames.size());
        }
        checkpointWindow.onLoadDataSet(meshDescriptorName);

        if (true) { // useCameraFlight
            std::string cameraPathFilename =
                    saveDirectoryCameraPaths + sgl::FileUtils::get()->getPathAsList(meshDescriptorName).back()
                    + ".binpath";
            if (sgl::FileUtils::get()->exists(cameraPathFilename)) {
                cameraPath.fromBinaryFile(cameraPathFilename);
            } else {
                cameraPath.fromCirclePath(
                        boundingBox, meshDescriptorName,
                        usePerformanceMeasurementMode
                        ? CAMERA_PATH_TIME_PERFORMANCE_MEASUREMENT : CAMERA_PATH_TIME_RECORDING,
                        usePerformanceMeasurementMode);
                //cameraPath.saveToBinaryFile(cameraPathFilename);
            }
        }
    }
}

void MainApp::reloadDataSet() {
    loadVolumeDataSet(getSelectedDataSetFilenames());
}

void MainApp::prepareVisualizationPipeline() {
    if (volumeData && volumeData->isDirty()) {
        tfOptimization->setVolumeData(volumeData.get(), newDataLoaded);
    }
    if (volumeData && !volumeRenderers.empty()) {
        bool isPreviousNodeDirty = volumeData->isDirty();
        for (auto& volumeRenderer : volumeRenderers) {
            if (volumeRenderer->isDirty() || isPreviousNodeDirty) {
                rendererVk->getDevice()->waitIdle();
                volumeRenderer->setVolumeData(volumeData, newDataLoaded);
                volumeRenderer->resetDirty();
            }
        }
        volumeData->resetDirty();
    }
    newDataLoaded = false;
}
back to top