/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2020 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* _loggerCat = "RenderableMultiresVolume"; constexpr const char* KeyDataSource = "Source"; constexpr const char* KeyErrorHistogramsSource = "ErrorHistogramsSource"; constexpr const char* KeyHints = "Hints"; constexpr const char* KeyTransferFunction = "TransferFunction"; constexpr const char* KeyVolumeName = "VolumeName"; constexpr const char* KeyBrickSelector = "BrickSelector"; constexpr const char* KeyStartTime = "StartTime"; constexpr const char* KeyEndTime = "EndTime"; constexpr const char* GlslHelpersPath = "${MODULES}/multiresvolume/shaders/helpers_fs.glsl"; constexpr const char* GlslHelperPath = "${MODULES}/multiresvolume/shaders/helper.glsl"; constexpr const char* GlslHeaderPath = "${MODULES}/multiresvolume/shaders/header.glsl"; constexpr openspace::properties::Property::PropertyInfo StepSizeCoefficientInfo = { "StepSizeCoefficient", "Stepsize Coefficient", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo CurrentTimeInfo = { "CurrentTime", "Current Time", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo MemoryBudgetInfo = { "MemoryBudget", "Memory Budget", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo StreamingBudgetInfo = { "StreamingBudget", "Streaming Budget", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo UseGlobalTimeInfo = { "UseGlobalTime", "Global Time", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo LoopInfo = { "Loop", "Loop", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo SelectorNameInfo = { "Selector", "Brick Selector", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo StatsToFileInfo = { "PrintStats", "Print Stats", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo StatsToFileNameInfo = { "PrintStatsFileName", "Stats Filename", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ScalingExponentInfo = { "ScalingExponent", "Scaling Exponent", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ScalingInfo = { "Scaling", "Scaling", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo TranslationInfo = { "Translation", "Translation", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo RotationInfo = { "Rotation", "Euler rotation", "" // @TODO Missing documentation }; } // namespace namespace openspace { RenderableMultiresVolume::RenderableMultiresVolume(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _useGlobalTime(UseGlobalTimeInfo, false) , _loop(LoopInfo, false) , _currentTime(CurrentTimeInfo, 0, 0, 0) , _memoryBudget(MemoryBudgetInfo, 0, 0, 0) , _streamingBudget(StreamingBudgetInfo, 0, 0, 0) , _stepSizeCoefficient(StepSizeCoefficientInfo, 1.f, 0.01f, 10.f) , _selectorName(SelectorNameInfo, "tf") , _statsToFile(StatsToFileInfo, false) , _statsToFileName(StatsToFileNameInfo) , _scalingExponent(ScalingExponentInfo, 1, -10, 20) , _translation(TranslationInfo, glm::vec3(0.f), glm::vec3(0.f), glm::vec3(10.f)) , _rotation( RotationInfo, glm::vec3(0.f), glm::vec3(0.f), glm::vec3(glm::two_pi()) ) , _scaling(ScalingInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(10.f)) { if (dictionary.hasKeyAndValue(KeyDataSource)) { _filename = absPath(dictionary.value(KeyDataSource)); } else { LERROR(fmt::format("Node did not contain a valid '{}'", KeyDataSource)); return; } if (dictionary.hasKeyAndValue(KeyErrorHistogramsSource)) { _errorHistogramsPath = absPath( dictionary.value(KeyErrorHistogramsSource) ); } if (dictionary.hasKeyAndValue("ScalingExponent")) { _scalingExponent = static_cast(dictionary.value("ScalingExponent")); } if (dictionary.hasKeyAndValue("StepSizeCoefficient")) { _stepSizeCoefficient = static_cast( dictionary.value("StepSizeCoefficient") ); } if (dictionary.hasKeyAndValue("Scaling")) { _scaling = dictionary.value("Scaling"); } if (dictionary.hasKeyAndValue("Translation")) { _translation = dictionary.value("Translation"); } if (dictionary.hasKeyAndValue("Rotation")) { _rotation = dictionary.value("Rotation"); } std::string startTimeString, endTimeString; bool hasTimeData = dictionary.getValue(KeyStartTime, startTimeString) && dictionary.getValue(KeyEndTime, endTimeString); if (hasTimeData) { _startTime = SpiceManager::ref().ephemerisTimeFromDate(startTimeString); _endTime = SpiceManager::ref().ephemerisTimeFromDate(endTimeString); _loop = false; } else { _loop = true; LWARNING("Node does not provide time information. Viewing one image / frame"); } if (dictionary.hasKeyAndValue(KeyTransferFunction)) { _transferFunctionPath = absPath( dictionary.value(KeyTransferFunction) ); _transferFunction = std::make_shared(_transferFunctionPath); } else { LERROR(fmt::format("Node did not contain a valid '{}'", KeyTransferFunction)); return; } //_pscOffset = psc(glm::vec4(0.0)); //_boxScaling = glm::vec3(1.0); /*if (dictionary.hasKey(KeyBoxScaling)) { glm::vec4 scalingVec4(_boxScaling, _w); success = dictionary.getValue(KeyBoxScaling, scalingVec4); if (success) { _boxScaling = scalingVec4.xyz; _w = scalingVec4.w; } else { success = dictionary.getValue(KeyBoxScaling, _boxScaling); if (!success) { LERROR("Node '" << name << "' did not contain a valid '" << KeyBoxScaling << "'"); return; } } }*/ _tsp = std::make_shared(_filename); _atlasManager = std::make_shared(_tsp.get()); if (dictionary.hasKeyAndValue(KeyBrickSelector)) { _selectorName = dictionary.value(KeyBrickSelector); } std::string selectorName = _selectorName; if (selectorName == "simple") { _selector = Selector::SIMPLE; } else if (selectorName == "local") { _selector = Selector::LOCAL; } else { _selector = Selector::TF; } addProperty(_selectorName); _selectorName.onChange([&]() { Selector s; std::string newSelectorName = _selectorName; if (newSelectorName == "simple") { s = Selector::SIMPLE; } else if (newSelectorName == "local") { s = Selector::LOCAL; } else if (newSelectorName == "tf") { s = Selector::TF; } else { return; } setSelectorType(s); }); addProperty(_stepSizeCoefficient); addProperty(_useGlobalTime); addProperty(_loop); addProperty(_statsToFile); addProperty(_statsToFileName); addProperty(_scaling); addProperty(_scalingExponent); addProperty(_translation); addProperty(_rotation); } RenderableMultiresVolume::~RenderableMultiresVolume() {} void RenderableMultiresVolume::setSelectorType(Selector selector) { // @TODO(abock): Can these if statements be simplified by checking if // selector == _selector before and bailing out early? _selector = selector; switch (_selector) { case Selector::TF: if (!_tfBrickSelector) { _errorHistogramManager = std::make_unique( _tsp.get() ); _tfBrickSelector = std::make_unique( _tsp.get(), _errorHistogramManager.get(), _transferFunction.get(), _memoryBudget, _streamingBudget ); _transferFunction->setCallback([this](const TransferFunction&) { _tfBrickSelector->calculateBrickErrors(); }); if (initializeSelector()) { _tfBrickSelector->calculateBrickErrors(); return; } } break; case Selector::SIMPLE: if (!_simpleTfBrickSelector) { _histogramManager = std::make_unique(); _simpleTfBrickSelector = std::make_unique( _tsp.get(), _histogramManager.get(), _transferFunction.get(), _memoryBudget, _streamingBudget ); _transferFunction->setCallback([this](const TransferFunction&) { _simpleTfBrickSelector->calculateBrickImportances(); }); if (initializeSelector()) { _simpleTfBrickSelector->calculateBrickImportances(); return; } } break; case Selector::LOCAL: if (!_localTfBrickSelector) { _localErrorHistogramManager = std::make_unique(_tsp.get()); _localTfBrickSelector = std::make_unique( _tsp.get(), _localErrorHistogramManager.get(), _transferFunction.get(), _memoryBudget, _streamingBudget ); _transferFunction->setCallback([this](const TransferFunction&) { _localTfBrickSelector->calculateBrickErrors(); }); if (initializeSelector()) { _localTfBrickSelector->calculateBrickErrors(); return; } } break; } } void RenderableMultiresVolume::initializeGL() { bool success = _tsp && _tsp->load(); unsigned int maxNumBricks = _tsp->header().xNumBricks * _tsp->header().yNumBricks * _tsp->header().zNumBricks; constexpr unsigned int MaxInitialBudget = 2048; int initialBudget = std::min(MaxInitialBudget, maxNumBricks); _currentTime = properties::IntProperty( CurrentTimeInfo, 0, 0, _tsp->header().numTimesteps - 1 ); _memoryBudget = properties::IntProperty( MemoryBudgetInfo, initialBudget, 0, maxNumBricks ); _streamingBudget = properties::IntProperty( StreamingBudgetInfo, initialBudget, 0, maxNumBricks ); addProperty(_currentTime); addProperty(_memoryBudget); addProperty(_streamingBudget); if (success) { _brickIndices.resize(maxNumBricks, 0); setSelectorType(_selector); } success &= _atlasManager && _atlasManager->initialize(); _transferFunction->update(); success &= isReady(); _raycaster = std::make_unique( _tsp, _atlasManager, _transferFunction ); _raycaster->initialize(); global::raycasterManager.attachRaycaster(*_raycaster); auto onChange = [&](bool enabled) { if (enabled) { global::raycasterManager.attachRaycaster(*_raycaster); } else { global::raycasterManager.detachRaycaster(*_raycaster); } }; onEnabledChange(onChange); if (!success) { throw ghoul::RuntimeError("Error during initialization"); } } void RenderableMultiresVolume::deinitializeGL() { _tsp = nullptr; _transferFunction = nullptr; } bool RenderableMultiresVolume::isReady() const { return true; } bool RenderableMultiresVolume::initializeSelector() { int nHistograms = 50; bool success = true; switch (_selector) { case Selector::TF: if (_errorHistogramManager) { std::stringstream cacheName; ghoul::filesystem::File f = _filename; cacheName << f.baseName() << "_" << nHistograms << "_errorHistograms"; std::string cacheFilename; cacheFilename = FileSys.cacheManager()->cachedFilename( cacheName.str(), "", ghoul::filesystem::CacheManager::Persistent::Yes ); std::ifstream cacheFile(cacheFilename, std::ios::in | std::ios::binary); std::string errorHistogramsPath = _errorHistogramsPath; if (cacheFile.is_open()) { // Read histograms from cache. cacheFile.close(); LINFO( fmt::format("Loading histograms from cache: {}", cacheFilename) ); success &= _errorHistogramManager->loadFromFile(cacheFilename); } else if (_errorHistogramsPath != "") { // Read histograms from scene data. LINFO(fmt::format( "Loading histograms from scene data: {}", _errorHistogramsPath )); success &= _errorHistogramManager->loadFromFile(_errorHistogramsPath); } else { // Build histograms from tsp file. LWARNING(fmt::format("Failed to open {}", cacheFilename)); success &= _errorHistogramManager->buildHistograms(nHistograms); if (success) { LINFO(fmt::format("Writing cache to {}", cacheFilename)); _errorHistogramManager->saveToFile(cacheFilename); } } success &= _tfBrickSelector && _tfBrickSelector->initialize(); } break; case Selector::SIMPLE: if (_histogramManager) { std::stringstream cacheName; ghoul::filesystem::File f = _filename; cacheName << f.baseName() << "_" << nHistograms << "_histograms"; std::string cacheFilename; cacheFilename = FileSys.cacheManager()->cachedFilename( cacheName.str(), "", ghoul::filesystem::CacheManager::Persistent::Yes ); std::ifstream cacheFile(cacheFilename, std::ios::in | std::ios::binary); if (cacheFile.is_open()) { // Read histograms from cache. cacheFile.close(); LINFO(fmt::format("Loading histograms from {}", cacheFilename)); success &= _histogramManager->loadFromFile(cacheFilename); } else { // Build histograms from tsp file. LWARNING(fmt::format("Failed to open '{}'", cacheFilename)); success &= _histogramManager->buildHistograms( _tsp.get(), nHistograms ); if (success) { LINFO(fmt::format("Writing cache to {}", cacheFilename)); _histogramManager->saveToFile(cacheFilename); } } success &= _simpleTfBrickSelector && _simpleTfBrickSelector->initialize(); } break; case Selector::LOCAL: if (_localErrorHistogramManager) { ghoul::filesystem::File f = _filename; std::string cacheFilename; cacheFilename = FileSys.cacheManager()->cachedFilename( fmt::format("{}_{}_localErrorHistograms", f.baseName(), nHistograms), "", ghoul::filesystem::CacheManager::Persistent::Yes ); std::ifstream cacheFile(cacheFilename, std::ios::in | std::ios::binary); if (cacheFile.is_open()) { // Read histograms from cache. cacheFile.close(); LINFO(fmt::format("Loading histograms from {}", cacheFilename)); success &= _localErrorHistogramManager->loadFromFile(cacheFilename); } else { // Build histograms from tsp file. LWARNING(fmt::format("Failed to open {}", cacheFilename)); success &= _localErrorHistogramManager->buildHistograms(nHistograms); if (success) { LINFO(fmt::format("Writing cache to {}", cacheFilename)); _localErrorHistogramManager->saveToFile(cacheFilename); } } success &= _localTfBrickSelector && _localTfBrickSelector->initialize(); } break; } return success; } /* void RenderableMultiresVolume::preResolve(ghoul::opengl::ProgramObject* program) { RenderableVolume::preResolve(program); std::stringstream ss; ss << "opacity_" << getId(); program->setUniform(ss.str(), visible ? 1.0f : 0.0f); ss.str(std::string()); ss << "stepSizeCoefficient_" << getId(); program->setUniform(ss.str(), _stepSizeCoefficient); ss.str(std::string()); ss << "transferFunction_" << getId(); program->setUniform(ss.str(), getTextureUnit(_transferFunction->texture())); ss.str(std::string()); ss << "textureAtlas_" << getId(); program->setUniform(ss.str(), getTextureUnit(_atlasManager->textureAtlas())); ss.str(std::string()); ss << "atlasMapBlock_" << getId(); program->setSsboBinding(ss.str(), getSsboBinding(_atlasManager->atlasMapBuffer())); ss.str(std::string()); ss << "gridType" << getId(); program->setUniform(ss.str(), static_cast(_tsp->header().gridType)); ss.str(std::string()); ss << "maxNumBricksPerAxis_" << getId(); program->setUniform(ss.str(), static_cast(_tsp->header().xNumBricks)); ss.str(std::string()); ss << "paddedBrickDim_" << getId(); program->setUniform(ss.str(), static_cast(_tsp->paddedBrickDim())); ss.str(std::string()); ss << "atlasSize_" << getId(); glm::size3_t size = _atlasManager->textureSize(); glm::ivec3 atlasSize(size.x, size.y, size.z); program->setUniform(ss.str(), atlasSize); _timestep++; } */ /* std::vector RenderableMultiresVolume::getTextures() { std::vector textures{ _transferFunction->texture(), _atlasManager->textureAtlas() }; return textures; } std::vector RenderableMultiresVolume::getBuffers() { std::vector buffers{_atlasManager->atlasMapBuffer()}; return buffers; }*/ void RenderableMultiresVolume::update(const UpdateData& data) { _timestep++; _time = data.time.j2000Seconds(); if (_gatheringStats) { std::chrono::system_clock::time_point frameEnd = std::chrono::system_clock::now(); std::chrono::duration frameDuration = frameEnd - _frameStart; // Make sure that the directory exists ghoul::filesystem::File file(_statsFileName); ghoul::filesystem::Directory directory(file.directoryName()); FileSys.createDirectory(directory, ghoul::filesystem::FileSystem::Recursive::Yes); std::ofstream ofs(_statsFileName, std::ofstream::out); ofs << frameDuration.count() << " " << _selectionDuration.count() << " " << _uploadDuration.count() << " " << _nUsedBricks << " " << _nStreamedBricks << " " << _nDiskReads; ofs.close(); _gatheringStats = false; } if (_statsToFile) { // Start frame timer _frameStart = std::chrono::system_clock::now(); _statsFileName = _statsToFileName; _gatheringStats = true; _statsToFile = false; } int numTimesteps = _tsp->header().numTimesteps; int currentTimestep; bool visible = true; if (_loop) { currentTimestep = _timestep % numTimesteps; } else if (_useGlobalTime) { double t = (_time - _startTime) / (_endTime - _startTime); currentTimestep = static_cast(t * numTimesteps); visible = currentTimestep >= 0 && currentTimestep < numTimesteps; } else { currentTimestep = _currentTime; } if (visible) { std::chrono::system_clock::time_point selectionStart; if (_gatheringStats) { selectionStart = std::chrono::system_clock::now(); } switch (_selector) { case Selector::TF: if (_tfBrickSelector) { _tfBrickSelector->setMemoryBudget(_memoryBudget); _tfBrickSelector->setStreamingBudget(_streamingBudget); _tfBrickSelector->selectBricks(currentTimestep, _brickIndices); } break; case Selector::SIMPLE: if (_simpleTfBrickSelector) { _simpleTfBrickSelector->setMemoryBudget(_memoryBudget); _simpleTfBrickSelector->setStreamingBudget(_streamingBudget); _simpleTfBrickSelector->selectBricks(currentTimestep, _brickIndices); } break; case Selector::LOCAL: if (_localTfBrickSelector) { _localTfBrickSelector->setMemoryBudget(_memoryBudget); _localTfBrickSelector->setStreamingBudget(_streamingBudget); _localTfBrickSelector->selectBricks(currentTimestep, _brickIndices); } break; } std::chrono::system_clock::time_point uploadStart; if (_gatheringStats) { std::chrono::system_clock::time_point selectionEnd = std::chrono::system_clock::now(); _selectionDuration = selectionEnd - selectionStart; uploadStart = selectionEnd; } _atlasManager->updateAtlas(AtlasManager::EVEN, _brickIndices); if (_gatheringStats) { std::chrono::system_clock::time_point uploadEnd = std::chrono::system_clock::now(); _uploadDuration = uploadEnd - uploadStart; _nDiskReads = _atlasManager->numDiskReads(); _nUsedBricks = _atlasManager->numUsedBricks(); _nStreamedBricks = _atlasManager->numStreamedBricks(); } } if (_raycaster) { glm::mat4 transform = glm::translate( glm::mat4(1.f), static_cast(_translation) * std::pow(10.f, static_cast(_scalingExponent)) ); glm::vec3 eulerRotation = static_cast(_rotation); transform = glm::rotate(transform, eulerRotation.x, glm::vec3(1.f, 0.f, 0.f)); transform = glm::rotate(transform, eulerRotation.y, glm::vec3(0.f, 1.f, 0.f)); transform = glm::rotate(transform, eulerRotation.z, glm::vec3(0.f, 0.f, 1.f)); transform = glm::scale( transform, static_cast(_scaling) * std::pow(10.f, static_cast(_scalingExponent)) ); _raycaster->setStepSizeCoefficient(_stepSizeCoefficient); _raycaster->setModelTransform(transform); //_raycaster->setTime(data.time); } } void RenderableMultiresVolume::render(const RenderData& data, RendererTasks& tasks) { RaycasterTask task { _raycaster.get(), data }; tasks.raycasterTasks.push_back(task); } } // namespace openspace