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
VolumeData.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 <queue>
#include <utility>
#include <cstring>
#include <boost/algorithm/string/case_conv.hpp>
#ifdef USE_TBB
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <tbb/blocked_range.h>
#include <Utils/Parallel/Reduction.hpp>
#endif
#include <Utils/AppSettings.hpp>
#include <Utils/File/FileUtils.hpp>
#include <Utils/Events/EventManager.hpp>
#include <Utils/Parallel/Reduction.hpp>
#include <Graphics/Vulkan/Render/Data.hpp>
#include <Graphics/Vulkan/Render/Renderer.hpp>
#include <ImGui/Widgets/PropertyEditor.hpp>
#include <ImGui/imgui_custom.h>
#ifdef SUPPORT_CUDA_INTEROP
#include <Graphics/Vulkan/Utils/InteropCuda.hpp>
#endif
#include "Loaders/DataSet.hpp"
#include "Loaders/half/half.hpp"
#include "Loaders/VolumeLoader.hpp"
#include "Loaders/AmiraMeshLoader.hpp"
#include "Loaders/DatRawFileLoader.hpp"
#include "Loaders/FieldFileLoader.hpp"
#include "Loaders/CvolLoader.hpp"
#include "Loaders/NiftiLoader.hpp"
#ifdef USE_ECCODES
#include "Loaders/GribLoader.hpp"
#endif
#include "Loaders/NetCdfLoader.hpp"
#include "Loaders/RbcBinFileLoader.hpp"
#include "Loaders/StructuredGridVtkLoader.hpp"
#include "Loaders/VtkXmlLoader.hpp"
#ifdef USE_ZARR
#include "Loaders/ZarrLoader.hpp"
#endif
#include "Export/NetCdfWriter.hpp"
#include "Export/CvolWriter.hpp"
#include "Calculators/Calculator.hpp"
#include "Calculators/VelocityCalculator.hpp"
#include "Calculators/BinaryOperatorCalculator.hpp"
#include "Calculators/NoiseReductionCalculator.hpp"
#include "Calculators/EnsembleVarianceCalculator.hpp"
#include "Calculators/ResidualColorCalculator.hpp"
#include "Calculators/CorrelationCalculator.hpp"
#ifdef SUPPORT_PYTORCH
#include "Calculators/PyTorchCorrelationCalculator.hpp"
#endif
#ifdef SUPPORT_TINY_CUDA_NN
#include "Calculators/TinyCudaNNCorrelationCalculator.hpp"
#endif
#ifdef SUPPORT_QUICK_MLP
#include "Calculators/QuickMLPCorrelationCalculator.hpp"
#endif
#include "Calculators/VMLPCorrelationCalculator.hpp"
#include "Renderers/RenderingModes.hpp"
#include "Renderers/Renderer.hpp"
#include "Renderers/SceneData.hpp"
#include "Widgets/ViewManager.hpp"
#include "VolumeData.hpp"
template <typename T>
static std::pair<std::vector<std::string>, std::function<VolumeLoader*()>> registerVolumeLoader() {
return { T::getSupportedExtensions(), []() { return new T{}; }};
}
VolumeLoader* VolumeData::createVolumeLoaderByExtension(const std::string& fileExtension) {
auto it = factoriesLoader.find(fileExtension);
if (it == factoriesLoader.end()) {
sgl::Logfile::get()->writeError(
"Error in VolumeData::createVolumeLoaderByExtension: Unsupported file extension '."
+ fileExtension + "'.", true);
return nullptr;
} else {
return it->second();
}
}
template <typename T>
static std::pair<std::vector<std::string>, std::function<VolumeWriter*()>> registerVolumeWriter() {
return { T::getSupportedExtensions(), []() { return new T{}; }};
}
VolumeWriter* VolumeData::createVolumeWriterByExtension(const std::string& fileExtension) {
auto it = factoriesWriter.find(fileExtension);
if (it == factoriesWriter.end()) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::createVolumeWriterByExtension: Unsupported file extension '."
+ fileExtension + "'.");
return nullptr;
} else {
return it->second();
}
}
VolumeData::VolumeData(sgl::vk::Renderer* renderer) : renderer(renderer), multiVarTransferFunctionWindow() {
device = renderer->getDevice();
hostFieldCache = std::make_unique<HostFieldCache>();
deviceFieldCache = std::make_unique<DeviceFieldCache>(device);
fieldMinMaxCache = std::make_unique<FieldMinMaxCache>();
typeToFieldNamesMap.insert(std::make_pair(FieldType::SCALAR, std::vector<std::string>()));
typeToFieldNamesMap.insert(std::make_pair(FieldType::VECTOR, std::vector<std::string>()));
typeToFieldNamesMapBase.insert(std::make_pair(FieldType::SCALAR, std::vector<std::string>()));
typeToFieldNamesMapBase.insert(std::make_pair(FieldType::VECTOR, std::vector<std::string>()));
// Create the list of volume loaders.
std::map<std::vector<std::string>, std::function<VolumeLoader*()>> factoriesLoaderMap = {
registerVolumeLoader<AmiraMeshLoader>(),
registerVolumeLoader<DatRawFileLoader>(),
registerVolumeLoader<FieldFileLoader>(),
registerVolumeLoader<CvolLoader>(),
registerVolumeLoader<NiftiLoader>(),
#ifdef USE_ECCODES
registerVolumeLoader<GribLoader>(),
#endif
registerVolumeLoader<NetCdfLoader>(),
registerVolumeLoader<RbcBinFileLoader>(),
registerVolumeLoader<StructuredGridVtkLoader>(),
registerVolumeLoader<VtkXmlLoader>(),
#ifdef USE_ZARR
registerVolumeLoader<ZarrLoader>(),
#endif
};
for (auto& factory : factoriesLoaderMap) {
for (const std::string& extension : factory.first) {
factoriesLoader.insert(std::make_pair(extension, factory.second));
}
}
// Create the list of volume writers.
std::map<std::vector<std::string>, std::function<VolumeWriter*()>> factoriesWriterMap = {
registerVolumeWriter<NetCdfWriter>(),
registerVolumeWriter<CvolWriter>(),
};
for (auto& factory : factoriesWriterMap) {
for (const std::string& extension : factory.first) {
factoriesWriter.insert(std::make_pair(extension, factory.second));
}
}
// Create the list of calculators.
factoriesCalculator.emplace_back(
"Binary Operator", [renderer]() { return new BinaryOperatorCalculator(renderer); });
factoriesCalculator.emplace_back(
"Noise Reduction", [renderer]() { return new NoiseReductionCalculator(renderer); });
factoriesCalculator.emplace_back(
"Ensemble Variance", [renderer]() { return new EnsembleVarianceCalculator(renderer); });
factoriesCalculator.emplace_back(
"Residual Color Calculator", [renderer]() { return new ResidualColorCalculator(renderer); });
factoriesCalculator.emplace_back(
"Correlation Calculator", [renderer]() { return new CorrelationCalculator(renderer); });
#ifdef SUPPORT_PYTORCH
factoriesCalculator.emplace_back(
"PyTorch Similarity Calculator", [renderer]() { return new PyTorchCorrelationCalculator(renderer); });
#endif
#ifdef SUPPORT_CUDA_INTEROP
if (device->getDeviceDriverId() == VK_DRIVER_ID_NVIDIA_PROPRIETARY
&& sgl::vk::getIsCudaDeviceApiFunctionTableInitialized()) {
#ifdef SUPPORT_TINY_CUDA_NN
factoriesCalculator.emplace_back(
"tiny-cuda-nn Similarity Calculator", [renderer]() { return new TinyCudaNNCorrelationCalculator(renderer); });
#endif
#ifdef SUPPORT_QUICK_MLP
factoriesCalculator.emplace_back(
"QuickMLP Similarity Calculator", [renderer]() { return new QuickMLPCorrelationCalculator(renderer); });
#endif
}
#endif
factoriesCalculator.emplace_back(
"VMLP Similarity Calculator", [renderer]() { return new VMLPCorrelationCalculator(renderer); });
sgl::vk::ImageSamplerSettings samplerSettings{};
imageSampler = std::make_shared<sgl::vk::ImageSampler>(device, samplerSettings, 0.0f);
}
VolumeData::~VolumeData() {
for (VolumeLoader* volumeLoader : volumeLoaders) {
delete volumeLoader;
}
volumeLoaders.clear();
if (latData) {
delete[] latData;
}
if (lonData) {
delete[] lonData;
}
}
void VolumeData::setTransposeAxes(const glm::ivec3& axes) {
this->transposeAxes = axes;
transpose = true;
}
void VolumeData::setGridSubsamplingFactor(int factor) {
subsamplingFactor = factor;
}
void VolumeData::setGridExtent(int _xs, int _ys, int _zs, float _dx, float _dy, float _dz) {
xs = _xs;
ys = _ys;
zs = _zs;
dx = _dx;
dy = _dy;
dz = _dz;
if (transpose) {
int dimensions[3] = { xs, ys, zs };
float spacing[3] = { dx, dy, dz };
xs = dimensions[transposeAxes[0]];
ys = dimensions[transposeAxes[1]];
zs = dimensions[transposeAxes[2]];
dx = spacing[transposeAxes[0]];
dy = spacing[transposeAxes[1]];
dz = spacing[transposeAxes[2]];
}
ssxs = xs;
ssys = ys;
sszs = zs;
if (subsamplingFactor > 1) {
xs /= subsamplingFactor;
ys /= subsamplingFactor;
zs /= subsamplingFactor;
dx *= float(subsamplingFactor);
dy *= float(subsamplingFactor);
dz *= float(subsamplingFactor);
}
box = sgl::AABB3(
glm::vec3(0.0f),
glm::vec3(float(xs - 1) * dx, float(ys - 1) * dy, float(zs - 1) * dz));
glm::vec3 dimensions = box.getDimensions();
float maxDimension = std::max(dimensions.x, std::max(dimensions.y, dimensions.z));
glm::vec3 normalizedDimensions = dimensions / maxDimension;
boxRendering.min = -normalizedDimensions * 0.25f;
boxRendering.max = normalizedDimensions * 0.25f;
}
void VolumeData::setNumTimeSteps(int _ts) {
ts = _ts;
}
void VolumeData::setTimeSteps(const std::vector<int>& timeSteps) {
ts = int(timeSteps.size());
}
void VolumeData::setTimeSteps(const std::vector<float>& timeSteps) {
ts = int(timeSteps.size());
}
void VolumeData::setTimeSteps(const std::vector<double>& timeSteps) {
ts = int(timeSteps.size());
}
void VolumeData::setTimeSteps(const std::vector<std::string>& timeSteps) {
ts = int(timeSteps.size());
}
void VolumeData::setEnsembleMemberCount(int _es) {
es = _es;
}
void VolumeData::setFieldNames(const std::unordered_map<FieldType, std::vector<std::string>>& fieldNamesMap) {
if (separateFilesPerAttribute) {
typeToFieldNamesMap[FieldType::SCALAR] = dataSetInformation.attributeNames;
typeToFieldNamesMapBase[FieldType::SCALAR] = dataSetInformation.attributeNames;
} else {
typeToFieldNamesMap = fieldNamesMap;
typeToFieldNamesMapBase = fieldNamesMap;
}
}
template<class T>
static void addFieldGlobal(
T* fieldData, FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx,
int xs, int ys, int zs, int ssxs, int ssys, int sszs, int subsamplingFactor,
bool transpose, const glm::ivec3& transposeAxes, VolumeData* volumeData, HostFieldCache* hostFieldCache) {
if (transpose) {
if (transposeAxes != glm::ivec3(0, 2, 1)) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::addScalarField: At the moment, only transposing the "
"Y and Z axis is supported.");
}
if (fieldType == FieldType::SCALAR) {
auto* scalarFieldCopy = new T[ssxs * ssys * sszs];
if constexpr(std::is_same<T, HalfFloat>()) {
size_t bufferSize = ssxs * ssys * sszs;
for (size_t i = 0; i < bufferSize; i++) {
scalarFieldCopy[i] = fieldData[i];
}
} else {
memcpy(scalarFieldCopy, fieldData, sizeof(T) * ssxs * ssys * sszs);
}
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<int>(0, sszs), [&](auto const& r) {
for (auto z = r.begin(); z != r.end(); z++) {
#else
#if _OPENMP >= 201107
#pragma omp parallel for shared(ssxs, ssys, sszs, fieldData, scalarFieldCopy) default(none)
#endif
for (int z = 0; z < sszs; z++) {
#endif
for (int y = 0; y < ssys; y++) {
for (int x = 0; x < ssxs; x++) {
int readPos = ((y)*ssxs*sszs + (z)*ssxs + (x));
int writePos = ((z)*ssxs*ssys + (y)*ssxs + (x));
fieldData[writePos] = scalarFieldCopy[readPos];
}
}
}
#ifdef USE_TBB
});
#endif
delete[] scalarFieldCopy;
} else {
auto* vectorFieldCopy = new T[3 * ssxs * ssys * sszs];
if constexpr(std::is_same<T, HalfFloat>()) {
size_t bufferSize = 3 * ssxs * ssys * sszs;
for (size_t i = 0; i < bufferSize; i++) {
vectorFieldCopy[i] = fieldData[i];
}
} else {
memcpy(vectorFieldCopy, fieldData, sizeof(T) * 3 * ssxs * ssys * sszs);
}
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<int>(0, sszs), [&](auto const& r) {
for (auto z = r.begin(); z != r.end(); z++) {
#else
#if _OPENMP >= 201107
#pragma omp parallel for shared(ssxs, ssys, sszs, fieldData, vectorFieldCopy) default(none)
#endif
for (int z = 0; z < sszs; z++) {
#endif
for (int y = 0; y < ssys; y++) {
for (int x = 0; x < ssxs; x++) {
int readPos = ((y)*ssxs*sszs*3 + (z)*ssxs*3 + (x)*3);
int writePos = ((z)*ssxs*ssys*3 + (y)*ssxs*3 + (x)*3);
fieldData[writePos] = vectorFieldCopy[readPos];
fieldData[writePos + 1] = vectorFieldCopy[readPos + 2];
fieldData[writePos + 2] = vectorFieldCopy[readPos + 1];
}
}
}
#ifdef USE_TBB
});
#endif
delete[] vectorFieldCopy;
}
}
if (subsamplingFactor > 1) {
if (fieldType == FieldType::SCALAR) {
T* scalarFieldOld = fieldData;
fieldData = new T[xs * ys * zs];
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<int>(0, zs), [&](auto const& r) {
for (auto z = r.begin(); z != r.end(); z++) {
#else
#if _OPENMP >= 201107
#pragma omp parallel for shared(xs, ys, zs, ssxs, ssys, subsamplingFactor, fieldData, scalarFieldOld) default(none)
#endif
for (int z = 0; z < zs; z++) {
#endif
for (int y = 0; y < ys; y++) {
for (int x = 0; x < xs; x++) {
int readPos =
((z * subsamplingFactor)*ssxs*ssys
+ (y * subsamplingFactor)*ssxs
+ (x * subsamplingFactor));
int writePos = ((z)*xs*ys + (y)*xs + (x));
fieldData[writePos] = scalarFieldOld[readPos];
}
}
}
#ifdef USE_TBB
});
#endif
delete[] scalarFieldOld;
} else {
T* vectorFieldOld = fieldData;
fieldData = new T[3 * xs * ys * zs];
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<int>(0, zs), [&](auto const& r) {
for (auto z = r.begin(); z != r.end(); z++) {
#else
#if _OPENMP >= 201107
#pragma omp parallel for shared(xs, ys, zs, ssxs, ssys, subsamplingFactor, fieldData, vectorFieldOld) \
default(none)
#endif
for (int z = 0; z < zs; z++) {
#endif
for (int y = 0; y < ys; y++) {
for (int x = 0; x < xs; x++) {
int readPos =
((z * subsamplingFactor)*ssxs*ssys*3
+ (y * subsamplingFactor)*ssxs*3
+ (x * subsamplingFactor)*3);
int writePos = ((z)*xs*ys*3 + (y)*xs*3 + (x)*3);
fieldData[writePos] = vectorFieldOld[readPos];
fieldData[writePos + 1] = vectorFieldOld[readPos + 1];
fieldData[writePos + 2] = vectorFieldOld[readPos + 2];
}
}
}
#ifdef USE_TBB
});
#endif
delete[] vectorFieldOld;
}
}
FieldAccess access = volumeData->createFieldAccessStruct(fieldType, fieldName, timeStepIdx, ensembleIdx);
if (hostFieldCache->exists(access)) {
delete[] fieldData;
return;
}
if constexpr(std::is_same<T, uint8_t>()) {
access.sizeInBytes /= 4;
} else if constexpr(std::is_same<T, uint16_t>()) {
access.sizeInBytes /= 2;
} else if constexpr(std::is_same<T, HalfFloat>()) {
access.sizeInBytes /= 2;
}
size_t numEntries = 0;
if (fieldType == FieldType::SCALAR) {
numEntries = xs * ys * zs;
} else if (fieldType == FieldType::VECTOR) {
numEntries = 3 * size_t(xs * ys * zs);
} else {
sgl::Logfile::get()->throwError("Error in VolumeData::addField: Invalid field type.");
}
hostFieldCache->push(access, HostCacheEntry(new HostCacheEntryType(numEntries, fieldData)));
}
void VolumeData::addField(
float* fieldData, FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
addFieldGlobal(
fieldData, fieldType, fieldName, timeStepIdx, ensembleIdx,
xs, ys, zs, ssxs, ssys, sszs, subsamplingFactor, transpose, transposeAxes, this, hostFieldCache.get());
}
void VolumeData::addField(
uint8_t* fieldData, FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
addFieldGlobal(
fieldData, fieldType, fieldName, timeStepIdx, ensembleIdx,
xs, ys, zs, ssxs, ssys, sszs, subsamplingFactor, transpose, transposeAxes, this, hostFieldCache.get());
}
void VolumeData::addField(
uint16_t* fieldData, FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
addFieldGlobal(
fieldData, fieldType, fieldName, timeStepIdx, ensembleIdx,
xs, ys, zs, ssxs, ssys, sszs, subsamplingFactor, transpose, transposeAxes, this, hostFieldCache.get());
}
void VolumeData::addField(
HalfFloat* fieldData, FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
addFieldGlobal(
fieldData, fieldType, fieldName, timeStepIdx, ensembleIdx,
xs, ys, zs, ssxs, ssys, sszs, subsamplingFactor, transpose, transposeAxes, this, hostFieldCache.get());
}
void VolumeData::addField(
void* fieldData, ScalarDataFormat dataFormat, FieldType fieldType,
const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
if (separateFilesPerAttribute) {
std::string attributeName = typeToFieldNamesMapBase[FieldType::SCALAR][currentLoaderAttributeIdx];
if (dataFormat == ScalarDataFormat::FLOAT) {
addField(static_cast<float*>(fieldData), fieldType, attributeName, timeStepIdx, ensembleIdx);
} else if (dataFormat == ScalarDataFormat::BYTE) {
addField(static_cast<uint8_t*>(fieldData), fieldType, attributeName, timeStepIdx, ensembleIdx);
} else if (dataFormat == ScalarDataFormat::SHORT) {
addField(static_cast<uint16_t*>(fieldData), fieldType, attributeName, timeStepIdx, ensembleIdx);
} else if (dataFormat == ScalarDataFormat::FLOAT16) {
addField(static_cast<HalfFloat*>(fieldData), fieldType, attributeName, timeStepIdx, ensembleIdx);
}
} else {
if (dataFormat == ScalarDataFormat::FLOAT) {
addField(static_cast<float*>(fieldData), fieldType, fieldName, timeStepIdx, ensembleIdx);
} else if (dataFormat == ScalarDataFormat::BYTE) {
addField(static_cast<uint8_t*>(fieldData), fieldType, fieldName, timeStepIdx, ensembleIdx);
} else if (dataFormat == ScalarDataFormat::SHORT) {
addField(static_cast<uint16_t*>(fieldData), fieldType, fieldName, timeStepIdx, ensembleIdx);
} else if (dataFormat == ScalarDataFormat::FLOAT16) {
addField(static_cast<HalfFloat*>(fieldData), fieldType, fieldName, timeStepIdx, ensembleIdx);
}
}
}
const std::vector<std::string>& VolumeData::getFieldNames(FieldType fieldType) {
return typeToFieldNamesMap[fieldType];
}
const std::vector<std::string>& VolumeData::getFieldNamesBase(FieldType fieldType) {
return typeToFieldNamesMapBase[fieldType];
}
bool VolumeData::getFieldExists(FieldType fieldType, const std::string& fieldName) const {
const std::vector<std::string>& names = typeToFieldNamesMap.find(fieldType)->second;
auto it = std::find(names.begin(), names.end(), fieldName);
return it != names.end();
}
bool VolumeData::getIsScalarFieldDivergent(const std::string& fieldName) const {
if (fieldName == "Helicity") {
return true;
}
return false;
}
void VolumeData::setLatLonData(float* _latData, float* _lonData) {
if (latData) {
delete[] latData;
latData = nullptr;
}
latData = _latData;
if (lonData) {
delete[] lonData;
lonData = nullptr;
}
lonData = _lonData;
}
bool VolumeData::setInputFiles(
const std::vector<std::string>& _filePaths, DataSetInformation _dataSetInformation,
glm::mat4* transformationMatrixPtr) {
filePaths = _filePaths;
dataSetInformation = std::move(_dataSetInformation);
if (dataSetInformation.subsamplingFactorSet) {
subsamplingFactor = dataSetInformation.subsamplingFactor;
}
if (dataSetInformation.separateFilesPerAttribute) {
separateFilesPerAttribute = dataSetInformation.separateFilesPerAttribute;
}
if (dataSetInformation.timeSteps.empty()) {
ts = 1;
} else {
setTimeSteps(dataSetInformation.timeSteps);
}
tsFileCount = ts;
if (separateFilesPerAttribute) {
esFileCount = es = 1;
} else {
esFileCount = es = int(filePaths.size()) / tsFileCount;
}
for (size_t i = 0; i < filePaths.size(); i++) {
std::string filePath = filePaths.at(i);
std::string fileExtension = sgl::FileUtils::get()->getFileExtensionLower(filePath);
VolumeLoader* volumeLoader = createVolumeLoaderByExtension(fileExtension);
if (!volumeLoader) {
return false;
}
if (!volumeLoader->setInputFiles(this, filePath, dataSetInformation)) {
delete volumeLoader;
return false;
}
volumeLoaders.push_back(volumeLoader);
}
currentTimeStepIdx = std::clamp(dataSetInformation.standardTimeStepIdx, 0, std::max(ts - 1, 0));
if (!dataSetInformation.standardScalarFieldName.empty()) {
const std::vector<std::string>& scalarFieldNames = getFieldNames(FieldType::SCALAR);
auto it = std::find(
scalarFieldNames.begin(), scalarFieldNames.end(), dataSetInformation.standardScalarFieldName);
if (it != scalarFieldNames.end()) {
standardScalarFieldIdx = int(it - scalarFieldNames.begin());
} else {
sgl::Logfile::get()->throwError(
"Error in VolumeData::setInputFiles: Scalar field name \""
+ dataSetInformation.standardScalarFieldName + "\" could not be found.");
}
} else {
standardScalarFieldIdx = std::clamp(
dataSetInformation.standardScalarFieldIdx, 0, int(getFieldNames(FieldType::SCALAR).size()) - 1);
}
// Automatically add calculators for velocity, vorticity and helicity if possible.
bool uLowerCaseVariableExists = getFieldExists(FieldType::SCALAR, "u");
bool vLowerCaseVariableExists = getFieldExists(FieldType::SCALAR, "v");
bool wLowerCaseVariableExists = getFieldExists(FieldType::SCALAR, "w");
bool uUpperCaseVariableExists = getFieldExists(FieldType::SCALAR, "U");
bool vUpperCaseVariableExists = getFieldExists(FieldType::SCALAR, "V");
bool wUpperCaseVariableExists = getFieldExists(FieldType::SCALAR, "W");
bool velocityVariableExists = getFieldExists(FieldType::SCALAR, "Velocity");
bool velocityMagnitudeVariableExists = getFieldExists(FieldType::SCALAR, "Velocity Magnitude");
bool vorticityVariableExists = getFieldExists(FieldType::SCALAR, "Vorticity");
bool vorticityMagnitudeVariableExists = getFieldExists(FieldType::SCALAR, "Vorticity Magnitude");
bool helicityVariableExists = getFieldExists(FieldType::SCALAR, "Helicity");
if (!velocityVariableExists && ((uLowerCaseVariableExists && vLowerCaseVariableExists && wLowerCaseVariableExists)
|| (uUpperCaseVariableExists && vUpperCaseVariableExists && wUpperCaseVariableExists))) {
addCalculator(std::make_shared<VelocityCalculator>(renderer));
velocityVariableExists = true;
}
if (!velocityMagnitudeVariableExists && velocityVariableExists) {
addCalculator(std::make_shared<VectorMagnitudeCalculator>(renderer, "Velocity"));
velocityMagnitudeVariableExists = true;
}
if (!vorticityVariableExists && velocityVariableExists) {
addCalculator(std::make_shared<VorticityCalculator>(renderer));
vorticityVariableExists = true;
}
if (!vorticityMagnitudeVariableExists && vorticityVariableExists) {
addCalculator(std::make_shared<VectorMagnitudeCalculator>(renderer, "Vorticity"));
vorticityMagnitudeVariableExists = true;
}
if (!helicityVariableExists && velocityVariableExists && vorticityVariableExists) {
addCalculator(std::make_shared<HelicityCalculator>(renderer));
helicityVariableExists = true;
}
if ((ts > 1 || es > 1) && viewManager) {
addCalculator(std::make_shared<CorrelationCalculator>(renderer));
/*#ifdef SUPPORT_PYTORCH
addCalculator(std::make_shared<PyTorchCorrelationCalculator>(renderer));
#endif
#ifdef SUPPORT_CUDA_INTEROP
if (device->getDeviceDriverId() == VK_DRIVER_ID_NVIDIA_PROPRIETARY
&& sgl::vk::getIsCudaDeviceApiFunctionTableInitialized()) {
#ifdef SUPPORT_TINY_CUDA_NN
addCalculator(std::make_shared<TinyCudaNNCorrelationCalculator>(renderer));
#endif
#ifdef SUPPORT_QUICK_MLP
addCalculator(std::make_shared<QuickMLPCorrelationCalculator>(renderer));
#endif
}
#endif*/
}
const auto& scalarFieldNames = typeToFieldNamesMap[FieldType::SCALAR];
multiVarTransferFunctionWindow.setRequestAttributeValuesCallback([this](
int varIdx, const void** values, ScalarDataFormat* fmt, size_t& numValues, float& minValue, float& maxValue) {
std::string fieldName = typeToFieldNamesMap[FieldType::SCALAR].at(varIdx);
if (values && fmt) {
HostCacheEntry cacheEntry = this->getFieldEntryCpu(FieldType::SCALAR, fieldName);
if (cacheEntry->getScalarDataFormatNative() == ScalarDataFormat::FLOAT) {
*values = cacheEntry->data<float>();
*fmt = ScalarDataFormat::FLOAT;
} else if (cacheEntry->getScalarDataFormatNative() == ScalarDataFormat::BYTE) {
*values = cacheEntry->data<uint8_t>();
*fmt = ScalarDataFormat::BYTE;
} else if (cacheEntry->getScalarDataFormatNative() == ScalarDataFormat::SHORT) {
*values = cacheEntry->data<uint16_t>();
*fmt = ScalarDataFormat::SHORT;
} else if (cacheEntry->getScalarDataFormatNative() == ScalarDataFormat::FLOAT16) {
// TODO: Change once support for float16 has been added to sgl.
*values = cacheEntry->data<float>();
*fmt = ScalarDataFormat::FLOAT;
}
}
numValues = this->getSlice3dEntryCount();
std::tie(minValue, maxValue) = getMinMaxScalarFieldValue(fieldName);
});
multiVarTransferFunctionWindow.setAttributeNames(scalarFieldNames);
colorLegendWidgets.clear();
colorLegendWidgets.resize(scalarFieldNames.size());
for (size_t i = 0; i < colorLegendWidgets.size(); i++) {
colorLegendWidgets.at(i).setPositionIndex(0, 1);
colorLegendWidgets.at(i).setAttributeDisplayName(scalarFieldNames.at(i));
}
recomputeColorLegend();
return true;
}
void VolumeData::addCalculator(const CalculatorPtr& calculator) {
calculator->initialize();
calculator->setCalculatorId(calculatorId++);
if (viewManager) {
calculator->setViewManager(viewManager);
}
calculator->setFileDialogInstance(fileDialogInstance);
calculator->setVolumeData(this, true);
calculators.push_back(calculator);
if (calculator->getFilterDevice() == FilterDevice::CPU) {
calculatorsHost.insert(std::make_pair(calculator->getOutputFieldName(), calculator));
} else {
calculatorsDevice.insert(std::make_pair(calculator->getOutputFieldName(), calculator));
}
typeToFieldNamesMap[calculator->getOutputFieldType()].push_back(calculator->getOutputFieldName());
if (!colorLegendWidgets.empty() && calculator->getOutputFieldType() == FieldType::SCALAR) {
multiVarTransferFunctionWindow.addAttributeName(calculator->getOutputFieldName());
colorLegendWidgets.emplace_back();
colorLegendWidgets.back().setAttributeDisplayName(calculator->getOutputFieldName());
std::vector<sgl::Color16> transferFunctionColorMap =
multiVarTransferFunctionWindow.getTransferFunctionMap_sRGB(int(colorLegendWidgets.size()) - 1);
colorLegendWidgets.back().setTransferFunctionColorMap(transferFunctionColorMap);
colorLegendWidgets.back().setClearColor(cachedClearColor);
for (auto& entry : scalarFieldToRendererMap) {
entry.second->dirty = true;
}
for (size_t viewIdx = 0; viewIdx < viewManager->getNumViews(); viewIdx++) {
auto calculatorRenderer = calculator->getCalculatorRenderer();
if (!calculatorRenderer) {
continue;
}
calculatorRenderer->addView(uint32_t(viewIdx));
SceneData* viewSceneData = viewManager->getViewSceneData(uint32_t(viewIdx));
if (*viewSceneData->sceneTexture) {
calculatorRenderer->recreateSwapchainView(
uint32_t(viewIdx), *viewSceneData->viewportWidthVirtual, *viewSceneData->viewportHeightVirtual);
}
}
}
}
FieldAccess VolumeData::createFieldAccessStruct(
FieldType fieldType, const std::string& fieldName, int& timeStepIdx, int& ensembleIdx) const {
if (timeStepIdx < 0) {
timeStepIdx = currentTimeStepIdx;
}
if (ensembleIdx < 0) {
ensembleIdx = currentEnsembleIdx;
}
FieldAccess access;
access.fieldType = fieldType;
access.fieldName = fieldName;
access.timeStepIdx = timeStepIdx;
access.ensembleIdx = ensembleIdx;
access.sizeInBytes = getSlice3dSizeInBytes(fieldType);
return access;
}
bool VolumeData::getScalarFieldSupportsBufferMode(int scalarFieldIdx) {
const auto& scalarFieldNames = getFieldNames(FieldType::SCALAR);
const auto& scalarFieldNamesBase = getFieldNamesBase(FieldType::SCALAR);
if (scalarFieldIdx < int(scalarFieldNames.size()) && scalarFieldIdx >= int(scalarFieldNamesBase.size())) {
const std::string& fieldName = scalarFieldNames.at(scalarFieldIdx);
auto itCalc = calculatorsDevice.find(fieldName);
return itCalc != calculatorsDevice.end();
}
return volumeLoaders.front()->getHasFloat32Data();
}
template<class T>
static void transposeScalarField(T* fieldEntryBuffer, int ssxs, int ssys, int sszs) {
auto* scalarFieldCopy = new T[ssxs * ssys * sszs];
if constexpr(std::is_same<T, HalfFloat>()) {
size_t bufferSize = ssxs * ssys * sszs;
for (size_t i = 0; i < bufferSize; i++) {
scalarFieldCopy[i] = fieldEntryBuffer[i];
}
} else {
memcpy(scalarFieldCopy, fieldEntryBuffer, sizeof(T) * ssxs * ssys * sszs);
}
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<int>(0, sszs), [&](auto const& r) {
for (auto z = r.begin(); z != r.end(); z++) {
#else
#if _OPENMP >= 201107
#pragma omp parallel for shared(ssxs, ssys, sszs, fieldEntryBuffer, scalarFieldCopy) default(none)
#endif
for (int z = 0; z < sszs; z++) {
#endif
for (int y = 0; y < ssys; y++) {
for (int x = 0; x < ssxs; x++) {
int readPos = ((y)*ssxs*sszs + (z)*ssxs + (x));
int writePos = ((z)*ssxs*ssys + (y)*ssxs + (x));
fieldEntryBuffer[writePos] = scalarFieldCopy[readPos];
}
}
}
#ifdef USE_TBB
});
#endif
delete[] scalarFieldCopy;
}
template<class T>
static void transposeVectorField(T* fieldEntryBuffer, int ssxs, int ssys, int sszs) {
auto* vectorFieldCopy = new T[3 * ssxs * ssys * sszs];
if constexpr(std::is_same<T, HalfFloat>()) {
size_t bufferSize = 3 * ssxs * ssys * sszs;
for (size_t i = 0; i < bufferSize; i++) {
vectorFieldCopy[i] = fieldEntryBuffer[i];
}
} else {
memcpy(vectorFieldCopy, fieldEntryBuffer, sizeof(T) * 3 * ssxs * ssys * sszs);
}
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<int>(0, sszs), [&](auto const& r) {
for (auto z = r.begin(); z != r.end(); z++) {
#else
#if _OPENMP >= 201107
#pragma omp parallel for shared(ssxs, ssys, sszs, fieldEntryBuffer, vectorFieldCopy) default(none)
#endif
for (int z = 0; z < sszs; z++) {
#endif
for (int y = 0; y < ssys; y++) {
for (int x = 0; x < ssxs; x++) {
int readPos = ((y)*ssxs*sszs*3 + (z)*ssxs*3 + (x)*3);
int writePos = ((z)*ssxs*ssys*3 + (y)*ssxs*3 + (x)*3);
fieldEntryBuffer[writePos] = vectorFieldCopy[readPos];
fieldEntryBuffer[writePos + 1] = vectorFieldCopy[readPos + 2];
fieldEntryBuffer[writePos + 2] = vectorFieldCopy[readPos + 1];
}
}
}
#ifdef USE_TBB
});
#endif
delete[] vectorFieldCopy;
}
VolumeData::HostCacheEntry VolumeData::getFieldEntryCpu(
FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
FieldAccess access = createFieldAccessStruct(fieldType, fieldName, timeStepIdx, ensembleIdx);
if (hostFieldCache->exists(access)) {
return hostFieldCache->reaccess(access);
}
size_t bufferSize = getSlice3dSizeInBytes(fieldType);
hostFieldCache->ensureSufficientMemory(bufferSize);
HostCacheEntryType* fieldEntry = nullptr;
auto itCalc = calculatorsHost.find(fieldName);
if (itCalc != calculatorsHost.end()) {
Calculator* calculator = itCalc->second.get();
if (calculator->getOutputFieldType() != fieldType) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::getFieldEntryCpu: Mismatch between field type and calculator output for "
"field \"" + fieldName + "\".");
}
size_t numComponents = fieldType == FieldType::SCALAR ? 1 : 3;
size_t numEntries = size_t(xs) * size_t(ys) * size_t(zs) * numComponents;
auto* fieldEntryBuffer = new float[numEntries];
calculator->calculateCpu(timeStepIdx, ensembleIdx, fieldEntryBuffer);
fieldEntry = new HostCacheEntryType(numEntries, fieldEntryBuffer);
} else if (calculatorsDevice.find(fieldName) != calculatorsDevice.end()) {
auto deviceEntry = getFieldEntryDevice(fieldType, fieldName, timeStepIdx, ensembleIdx);
size_t numComponents = fieldType == FieldType::SCALAR ? 1 : 3;
size_t numEntries = numComponents * size_t(xs) * size_t(ys) * size_t(zs);
size_t sizeInBytes = numEntries * sizeof(float);
if (!stagingBuffer) {
stagingBuffer = std::make_shared<sgl::vk::Buffer>(
device, sizeInBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU);
}
deviceEntry->getVulkanImage()->transitionImageLayout(
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, renderer->getVkCommandBuffer());
deviceEntry->getVulkanImage()->copyToBuffer(stagingBuffer, renderer->getVkCommandBuffer());
renderer->syncWithCpu();
auto* fieldEntryBuffer = new float[numEntries];
void* data = stagingBuffer->mapMemory();
memcpy(fieldEntryBuffer, data, sizeInBytes);
stagingBuffer->unmapMemory();
deviceEntry->getVulkanImage()->transitionImageLayout(
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, renderer->getVkCommandBuffer());
fieldEntry = new HostCacheEntryType(numEntries, fieldEntryBuffer);
} else {
VolumeLoader* volumeLoader = nullptr;
if (separateFilesPerAttribute) {
auto& names = typeToFieldNamesMapBase[fieldType];
auto itName = std::find(names.begin(), names.end(), fieldName);
currentLoaderAttributeIdx = int(itName - names.begin());
volumeLoader = volumeLoaders.at(currentLoaderAttributeIdx);
} else if (tsFileCount == 1 && esFileCount == 1) {
volumeLoader = volumeLoaders.front();
} else if (tsFileCount > 1 && esFileCount == 1) {
volumeLoader = volumeLoaders.at(timeStepIdx);
} else if (tsFileCount == 1 && esFileCount > 1) {
volumeLoader = volumeLoaders.at(ensembleIdx);
} else if (tsFileCount > 1 && esFileCount > 1 && !dataSetInformation.useTimeStepRange) {
volumeLoader = volumeLoaders.at(timeStepIdx * esFileCount + ensembleIdx);
} else if (tsFileCount > 1 && esFileCount > 1 && dataSetInformation.useTimeStepRange) {
volumeLoader = volumeLoaders.at(ensembleIdx * tsFileCount + timeStepIdx);
} else {
return {};
}
if (!volumeLoader->getFieldEntry(this, fieldType, fieldName, timeStepIdx, ensembleIdx, fieldEntry)) {
sgl::Logfile::get()->throwError(
"Fatal error in VolumeData::getFieldEntryCpu: Volume loader failed to load the data entry.");
return {};
}
}
// Loaders may load multiple fields at once and leave fieldEntryBuffer empty.
if (!fieldEntry) {
if (!hostFieldCache->exists(access)) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::getFieldEntryCpu: Loader supporting loading multiple fields at once "
"did not load a field called \"" + fieldName + "\".");
}
return hostFieldCache->reaccess(access);
} else {
if (transpose) {
if (transposeAxes != glm::ivec3(0, 2, 1)) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::addScalarField: At the moment, only transposing the "
"Y and Z axis is supported.");
}
if (fieldType == FieldType::SCALAR) {
if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::FLOAT) {
transposeScalarField(fieldEntry->dataFloat, ssxs, ssys, sszs);
} else if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::BYTE) {
transposeScalarField(fieldEntry->dataByte, ssxs, ssys, sszs);
} else if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::SHORT) {
transposeScalarField(fieldEntry->dataShort, ssxs, ssys, sszs);
} else if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::FLOAT16) {
transposeScalarField(fieldEntry->dataFloat16, ssxs, ssys, sszs);
}
} else {
if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::FLOAT) {
transposeVectorField(fieldEntry->dataFloat, ssxs, ssys, sszs);
} else if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::BYTE) {
transposeVectorField(fieldEntry->dataByte, ssxs, ssys, sszs);
} else if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::SHORT) {
transposeVectorField(fieldEntry->dataShort, ssxs, ssys, sszs);
} else if (fieldEntry->getScalarDataFormatNative() == ScalarDataFormat::FLOAT16) {
transposeVectorField(fieldEntry->dataFloat16, ssxs, ssys, sszs);
}
}
}
}
auto buffer = HostCacheEntry(fieldEntry);
hostFieldCache->push(access, buffer);
return buffer;
}
VolumeData::DeviceCacheEntry VolumeData::getFieldEntryDevice(
FieldType fieldType, const std::string& fieldName, int timeStepIdx, int ensembleIdx,
bool wantsImageData, const glm::uvec3& bufferTileSize) {
FieldAccess access = createFieldAccessStruct(fieldType, fieldName, timeStepIdx, ensembleIdx);
access.isImageData = wantsImageData;
access.bufferTileSize = bufferTileSize;
if (deviceFieldCache->exists(access)) {
return deviceFieldCache->reaccess(access);
}
auto itCalc = calculatorsDevice.find(fieldName);
HostCacheEntry bufferCpu;
ScalarDataFormat scalarDataFormat = ScalarDataFormat::FLOAT;
if (itCalc == calculatorsDevice.end()) {
bufferCpu = getFieldEntryCpu(fieldType, fieldName, timeStepIdx, ensembleIdx);
scalarDataFormat = bufferCpu->getScalarDataFormatNative();
if (scalarDataFormat == ScalarDataFormat::BYTE) {
access.sizeInBytes /= 4;
} else if (scalarDataFormat == ScalarDataFormat::SHORT || scalarDataFormat == ScalarDataFormat::FLOAT16) {
access.sizeInBytes /= 2;
}
}
size_t bufferSize = getSlice3dSizeInBytes(fieldType, scalarDataFormat);
deviceFieldCache->ensureSufficientMemory(bufferSize);
#ifdef SUPPORT_CUDA_INTEROP
bool canUseCuda = sgl::vk::getIsCudaDeviceApiFunctionTableInitialized();
#else
bool canUseCuda = false;
#endif
bool tileBufferMemory = bufferTileSize.x > 1 || bufferTileSize.y > 1 || bufferTileSize.z > 1;
const uint32_t tileSizeX = bufferTileSize.x;
const uint32_t tileSizeY = bufferTileSize.y;
const uint32_t tileSizeZ = bufferTileSize.z;
DeviceCacheEntry deviceCacheEntry;
if (wantsImageData) {
sgl::vk::ImageSettings imageSettings{};
imageSettings.width = uint32_t(xs);
imageSettings.height = uint32_t(ys);
imageSettings.depth = uint32_t(zs);
imageSettings.imageType = VK_IMAGE_TYPE_3D;
if (scalarDataFormat == ScalarDataFormat::FLOAT) {
imageSettings.format = fieldType == FieldType::SCALAR ? VK_FORMAT_R32_SFLOAT : VK_FORMAT_R32G32B32A32_SFLOAT;
} else if (scalarDataFormat == ScalarDataFormat::BYTE) {
imageSettings.format = fieldType == FieldType::SCALAR ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8G8B8A8_UNORM;
} else if (scalarDataFormat == ScalarDataFormat::SHORT) {
imageSettings.format = fieldType == FieldType::SCALAR ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16G16B16A16_UNORM;
} else if (scalarDataFormat == ScalarDataFormat::FLOAT16) {
imageSettings.format = fieldType == FieldType::SCALAR ? VK_FORMAT_R16_SFLOAT : VK_FORMAT_R16G16B16A16_SFLOAT;
}
imageSettings.tiling = VK_IMAGE_TILING_OPTIMAL;
imageSettings.usage =
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT
| VK_IMAGE_USAGE_STORAGE_BIT;
imageSettings.memoryUsage = VMA_MEMORY_USAGE_GPU_ONLY;
imageSettings.exportMemory = canUseCuda;
/*
* https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkMemoryGetWin32HandleInfoKHR.html
* "If handleType is defined as an NT handle, vkGetMemoryWin32HandleKHR must be called no more than once for each valid
* unique combination of memory and handleType"
* TODO: On Windows the application can only use non-dedicated allocations when it has been figured out that the handle
* is only exported once.
*/
#ifdef _WIN32
imageSettings.useDedicatedAllocationForExportedMemory = true;
#else
imageSettings.useDedicatedAllocationForExportedMemory = false;
#endif
auto image = std::make_shared<sgl::vk::Image>(device, imageSettings);
deviceCacheEntry = std::make_shared<DeviceCacheEntry::element_type>(image, imageSampler);
} else {
if (tileBufferMemory) {
bufferSize =
4 * size_t(sgl::uiceil(uint32_t(xs), tileSizeX) * tileSizeX)
* size_t(sgl::uiceil(uint32_t(ys), tileSizeY) * tileSizeY)
* size_t(sgl::uiceil(uint32_t(zs), tileSizeZ) * tileSizeZ);
access.sizeInBytes = bufferSize;
}
VkBufferUsageFlags bufferUsage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
auto buffer = std::make_shared<sgl::vk::Buffer>(
device, bufferSize, bufferUsage, VMA_MEMORY_USAGE_GPU_ONLY, true, canUseCuda, false);
deviceCacheEntry = std::make_shared<DeviceCacheEntry::element_type>(buffer);
}
if (itCalc != calculatorsDevice.end()) {
Calculator* calculator = itCalc->second.get();
if (calculator->getOutputFieldType() != fieldType) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::getFieldEntryDevice: Mismatch between field type and calculator output for "
"field \"" + fieldName + "\".");
}
calculator->calculateDevice(timeStepIdx, ensembleIdx, deviceCacheEntry);
} else if (wantsImageData) {
auto& image = deviceCacheEntry->getVulkanImage();
if (fieldType == FieldType::SCALAR) {
image->uploadData(access.sizeInBytes, bufferCpu->getDataNative());
} else {
size_t bufferEntriesCount = size_t(xs) * size_t(ys) * size_t(zs);
if (scalarDataFormat == ScalarDataFormat::FLOAT) {
const float* bufferIn = bufferCpu->data<float>();
auto* bufferPadded = new float[bufferEntriesCount * 4];
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<size_t>(0, bufferEntriesCount), [&](auto const& r) {
for (auto i = r.begin(); i != r.end(); i++) {
#else
#if _OPENMP >= 200805
#pragma omp parallel for shared(bufferEntriesCount, bufferPadded, bufferIn) default(none)
#endif
for (size_t i = 0; i < bufferEntriesCount; i++) {
#endif
size_t iPadded = i * 4;
size_t iIn = i * 3;
bufferPadded[iPadded] = bufferIn[iIn];
bufferPadded[iPadded + 1] = bufferIn[iIn + 1];
bufferPadded[iPadded + 2] = bufferIn[iIn + 2];
bufferPadded[iPadded + 3] = 0.0f;
}
#ifdef USE_TBB
});
#endif
image->uploadData(bufferEntriesCount * 4 * sizeof(float), bufferPadded);
delete[] bufferPadded;
} else if (scalarDataFormat == ScalarDataFormat::BYTE) {
const uint8_t* bufferIn = bufferCpu->data<uint8_t>();
auto* bufferPadded = new uint8_t[bufferEntriesCount * 4];
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<size_t>(0, bufferEntriesCount), [&](auto const& r) {
for (auto i = r.begin(); i != r.end(); i++) {
#else
#if _OPENMP >= 200805
#pragma omp parallel for shared(bufferEntriesCount, bufferPadded, bufferIn) default(none)
#endif
for (size_t i = 0; i < bufferEntriesCount; i++) {
#endif
size_t iPadded = i * 4;
size_t iIn = i * 3;
bufferPadded[iPadded] = bufferIn[iIn];
bufferPadded[iPadded + 1] = bufferIn[iIn + 1];
bufferPadded[iPadded + 2] = bufferIn[iIn + 2];
bufferPadded[iPadded + 3] = 0;
}
#ifdef USE_TBB
});
#endif
image->uploadData(bufferEntriesCount * 4 * sizeof(uint8_t), bufferPadded);
delete[] bufferPadded;
} else if (scalarDataFormat == ScalarDataFormat::SHORT) {
const uint16_t* bufferIn = bufferCpu->data<uint16_t>();
auto* bufferPadded = new uint16_t[bufferEntriesCount * 4];
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<size_t>(0, bufferEntriesCount), [&](auto const& r) {
for (auto i = r.begin(); i != r.end(); i++) {
#else
#if _OPENMP >= 200805
#pragma omp parallel for shared(bufferEntriesCount, bufferPadded, bufferIn) default(none)
#endif
for (size_t i = 0; i < bufferEntriesCount; i++) {
#endif
size_t iPadded = i * 4;
size_t iIn = i * 3;
bufferPadded[iPadded] = bufferIn[iIn];
bufferPadded[iPadded + 1] = bufferIn[iIn + 1];
bufferPadded[iPadded + 2] = bufferIn[iIn + 2];
bufferPadded[iPadded + 3] = 0;
}
#ifdef USE_TBB
});
#endif
image->uploadData(bufferEntriesCount * 4 * sizeof(uint16_t), bufferPadded);
delete[] bufferPadded;
} else if (scalarDataFormat == ScalarDataFormat::FLOAT16) {
const HalfFloat* bufferIn = bufferCpu->data<HalfFloat>();
auto* bufferPadded = new HalfFloat[bufferEntriesCount * 4];
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<size_t>(0, bufferEntriesCount), [&](auto const& r) {
for (auto i = r.begin(); i != r.end(); i++) {
#else
#if _OPENMP >= 200805
#pragma omp parallel for shared(bufferEntriesCount, bufferPadded, bufferIn) default(none)
#endif
for (size_t i = 0; i < bufferEntriesCount; i++) {
#endif
size_t iPadded = i * 4;
size_t iIn = i * 3;
bufferPadded[iPadded] = bufferIn[iIn];
bufferPadded[iPadded + 1] = bufferIn[iIn + 1];
bufferPadded[iPadded + 2] = bufferIn[iIn + 2];
bufferPadded[iPadded + 3] = HalfFloat(0.0f);
}
#ifdef USE_TBB
});
#endif
image->uploadData(bufferEntriesCount * 4 * sizeof(HalfFloat), bufferPadded);
delete[] bufferPadded;
}
}
access.sizeInBytes = image->getDeviceMemoryAllocationSize();
} else {
auto& buffer = deviceCacheEntry->getVulkanBuffer();
if (tileBufferMemory) {
size_t numEntries = bufferSize / 4;
auto* linearBufferData = bufferCpu->getDataFloat();
auto* tiledBufferData = new float[numEntries];
auto tileNumVoxels = tileSizeX * tileSizeY * tileSizeZ;
uint32_t xst = sgl::uiceil(xs, tileSizeX);
uint32_t yst = sgl::uiceil(ys, tileSizeY);
uint32_t zst = sgl::uiceil(zs, tileSizeZ);
uint32_t numTilesTotal = xst * yst * zst;
#ifdef USE_TBB
tbb::parallel_for(tbb::blocked_range<uint32_t>(0, numTilesTotal), [&](auto const& r) {
for (auto tileIdx = r.begin(); tileIdx != r.end(); tileIdx++) {
#else
#if _OPENMP >= 200805
#pragma omp parallel for default(none) shared(numTilesTotal, tileNumVoxels, xst, yst, zst) \
shared(tileSizeX, tileSizeY, tileSizeZ, linearBufferData, tiledBufferData)
#endif
for (uint32_t tileIdx = 0; tileIdx < numTilesTotal; tileIdx++) {
#endif
uint32_t xt = tileIdx % xst;
uint32_t yt = (tileIdx / xst) % yst;
uint32_t zt = tileIdx / (xst * yst);
for (uint32_t voxelIdx = 0; voxelIdx < tileNumVoxels; voxelIdx++) {
uint32_t vx = voxelIdx % tileSizeX;
uint32_t vy = (voxelIdx / tileSizeX) % tileSizeY;
uint32_t vz = voxelIdx / (tileSizeX * tileSizeY);
uint32_t x = vx + xt * tileSizeX;
uint32_t y = vy + yt * tileSizeY;
uint32_t z = vz + zt * tileSizeZ;
float value = 0.0f;
if (x < uint32_t(xs) && y < uint32_t(ys) && z < uint32_t(zs)) {
value = linearBufferData[IDXS(x, y, z)];
}
tiledBufferData[tileIdx * tileNumVoxels + voxelIdx] = value;
}
}
#ifdef USE_TBB
});
#endif
buffer->uploadData(access.sizeInBytes, tiledBufferData);
delete[] tiledBufferData;
} else {
buffer->uploadData(access.sizeInBytes, bufferCpu->getDataNative());
}
access.sizeInBytes = buffer->getDeviceMemoryAllocationSize();
}
deviceFieldCache->push(access, deviceCacheEntry);
return deviceCacheEntry;
}
std::pair<float, float> VolumeData::getMinMaxScalarFieldValue(
const std::string& fieldName, int timeStepIdx, int ensembleIdx) {
FieldAccess access = createFieldAccessStruct(FieldType::SCALAR, fieldName, timeStepIdx, ensembleIdx);
auto itHost = calculatorsHost.find(fieldName);
if (itHost != calculatorsHost.end() && itHost->second->getHasFixedRange()) {
return itHost->second->getFixedRange();
}
auto itDevice = calculatorsDevice.find(fieldName);
if (itDevice != calculatorsDevice.end() && itDevice->second->getHasFixedRange()) {
return itDevice->second->getFixedRange();
}
if (fieldMinMaxCache->exists(access)) {
return fieldMinMaxCache->get(access);
}
std::pair<float, float> minMaxVal;
HostCacheEntry scalarValues = getFieldEntryCpu(FieldType::SCALAR, fieldName, timeStepIdx, ensembleIdx);
if (scalarValues->getScalarDataFormatNative() == ScalarDataFormat::FLOAT) {
minMaxVal = sgl::reduceFloatArrayMinMax(scalarValues->data<float>(), getSlice3dEntryCount());
} else if (scalarValues->getScalarDataFormatNative() == ScalarDataFormat::BYTE) {
minMaxVal = sgl::reduceUnormByteArrayMinMax(scalarValues->data<uint8_t>(), getSlice3dEntryCount());
} else if (scalarValues->getScalarDataFormatNative() == ScalarDataFormat::SHORT) {
minMaxVal = sgl::reduceUnormShortArrayMinMax(scalarValues->data<uint16_t>(), getSlice3dEntryCount());
} else if (scalarValues->getScalarDataFormatNative() == ScalarDataFormat::FLOAT16) {
// TODO: Change once support for float16 has been added to sgl.
minMaxVal = sgl::reduceFloatArrayMinMax(scalarValues->data<float>(), getSlice3dEntryCount());
}
// Is this a divergent scalar field? If yes, the transfer function etc. should be centered at zero.
if (getIsScalarFieldDivergent(fieldName)) {
float maxAbs = std::max(std::abs(minMaxVal.first), std::abs(minMaxVal.second));
minMaxVal.first = -maxAbs;
minMaxVal.second = maxAbs;
}
fieldMinMaxCache->push(access, minMaxVal);
return minMaxVal;
}
AuxiliaryMemoryToken VolumeData::pushAuxiliaryMemoryCpu(size_t sizeInBytes) {
return hostFieldCache->pushAuxiliaryMemory(sizeInBytes);
}
void VolumeData::popAuxiliaryMemoryCpu(AuxiliaryMemoryToken token) {
hostFieldCache->popAuxiliaryMemory(token);
}
AuxiliaryMemoryToken VolumeData::pushAuxiliaryMemoryDevice(size_t sizeInBytes) {
return deviceFieldCache->pushAuxiliaryMemory(sizeInBytes);
}
void VolumeData::popAuxiliaryMemoryDevice(AuxiliaryMemoryToken token) {
deviceFieldCache->popAuxiliaryMemory(token);
}
bool VolumeData::getHasLatLonData() {
return latData && lonData;
}
void VolumeData::getLatLonData(const float*& _latData, const float*& _lonData) {
_latData = latData;
_lonData = lonData;
}
void VolumeData::update(float dtFrame) {
for (CalculatorPtr& calculator : calculators) {
calculator->update(dtFrame);
}
multiVarTransferFunctionWindow.update(dt);
hostFieldCache->updateEvictionWaitList();
deviceFieldCache->updateEvictionWaitList();
}
void VolumeData::resetDirty() {
if (dirty) {
const auto& scalarFieldNamesBase = typeToFieldNamesMapBase[FieldType::SCALAR];
const auto& scalarFieldNames = typeToFieldNamesMap[FieldType::SCALAR];
// 2023-03-30: Make sure no data is reset for base fields to avoid recomputing on every calculator change.
int startIdx = isFirstDirty ? int(0) : int(scalarFieldNamesBase.size());
int numScalarFields = int(scalarFieldNames.size());
for (int varIdx = startIdx; varIdx < numScalarFields; varIdx++) {
multiVarTransferFunctionWindow.setAttributeDataDirty(varIdx);
}
for (auto& calculator : calculators) {
calculator->setVolumeData(this, false);
}
}
dirty = false;
isFirstDirty = false;
}
void VolumeData::setRenderDataBindings(const sgl::vk::RenderDataPtr& renderData) {
if (renderData->getShaderStages()->hasDescriptorBinding(0, "transferFunctionTexture")) {
const sgl::vk::DescriptorInfo& descriptorInfo = renderData->getShaderStages()->getDescriptorInfoByName(
0, "transferFunctionTexture");
if (descriptorInfo.image.arrayed == 1) {
renderData->setStaticTexture(
multiVarTransferFunctionWindow.getTransferFunctionMapTextureVulkan(),
"transferFunctionTexture");
renderData->setStaticBuffer(
multiVarTransferFunctionWindow.getMinMaxSsboVulkan(), "MinMaxBuffer");
}
}
}
void VolumeData::getPreprocessorDefines(std::map<std::string, std::string>& preprocessorDefines) {
preprocessorDefines.insert(std::make_pair("USE_MULTI_VAR_TRANSFER_FUNCTION", ""));
}
void VolumeData::setBaseFieldsDirty() {
dirty = true;
reRender = true;
auto numFieldsBase = int(typeToFieldNamesMapBase[FieldType::SCALAR].size());
for (int fieldIdx = 0; fieldIdx < numFieldsBase; fieldIdx++) {
multiVarTransferFunctionWindow.setAttributeDataDirty(fieldIdx);
}
}
void VolumeData::setCurrentTimeStepIdx(int newTimeStepIdx) {
if (currentTimeStepIdx != newTimeStepIdx) {
setBaseFieldsDirty();
}
currentTimeStepIdx = std::clamp(newTimeStepIdx, 0, std::max(ts - 1, 0));
}
void VolumeData::setCurrentEnsembleIdx(int newEnsembleIdx) {
if (currentEnsembleIdx != newEnsembleIdx) {
setBaseFieldsDirty();
}
currentEnsembleIdx = std::clamp(newEnsembleIdx, 0, std::max(es - 1, 0));
}
void VolumeData::renderGui(sgl::PropertyEditor& propertyEditor) {
if (propertyEditor.beginNode("Volume Data")) {
propertyEditor.addText(
"Volume Size", std::to_string(xs) + "x" + std::to_string(ys) + "x" + std::to_string(zs));
if (ts > 1 && propertyEditor.addSliderIntEdit(
"Time Step", ¤tTimeStepIdx, 0, ts - 1) == ImGui::EditMode::INPUT_FINISHED) {
currentTimeStepIdx = std::clamp(currentTimeStepIdx, 0, std::max(ts - 1, 0));
setBaseFieldsDirty();
}
if (es > 1 && propertyEditor.addSliderIntEdit(
"Ensemble Member", ¤tEnsembleIdx, 0, es - 1) == ImGui::EditMode::INPUT_FINISHED) {
currentEnsembleIdx = std::clamp(currentEnsembleIdx, 0, std::max(es - 1, 0));
setBaseFieldsDirty();
}
propertyEditor.addCheckbox("Render Color Legend", &shallRenderColorLegendWidgets);
propertyEditor.endNode();
}
}
void VolumeData::renderGuiCalculators(sgl::PropertyEditor& propertyEditor) {
std::queue<Calculator*> dirtyCalculators;
for (auto& calculator : calculators) {
if (!calculator->getShouldRenderGui()) {
continue;
}
calculator->renderGui(propertyEditor);
if (calculator->getIsDirtyDontReset()) {
dirtyCalculators.push(calculator.get());
}
}
while (!dirtyCalculators.empty()) {
Calculator* calculator = dirtyCalculators.front();
dirtyCalculators.pop();
auto iterRange = calculatorUseMapRefToParent.equal_range(calculator);
auto it = iterRange.first;
while (it != iterRange.second) {
Calculator* calculatorRef = it->second;
if (!calculatorRef->getIsDirtyDontReset()) {
calculatorRef->setIsDirty();
dirtyCalculators.push(calculatorRef);
}
it++;
}
}
for (int calculatorIdx = 0; calculatorIdx < int(calculators.size()); calculatorIdx++) {
CalculatorPtr calculator = calculators.at(calculatorIdx);
if (!calculator->getShouldRenderGui()) {
continue;
}
if (calculator->getShallRemoveCalculator()) {
removeCalculator(calculator, calculatorIdx);
calculatorIdx--;
dirty = true;
reRender = true;
continue;
}
if (calculator->getHasNameChanged()) {
updateCalculatorName(calculator);
dirty = true;
reRender = true;
}
if (calculator->getHasFilterDeviceChanged()) {
updateCalculatorFilterDevice(calculator);
dirty = true;
reRender = true;
}
if (calculator->getIsDirty()) {
updateCalculator(calculator);
dirty = true;
reRender = true;
}
}
}
void VolumeData::renderGuiNewCalculators() {
for (auto& factory : factoriesCalculator) {
if (ImGui::MenuItem(factory.first.c_str())) {
addCalculator(CalculatorPtr(factory.second()));
dirty = true; //< Necessary for new transfer function map.
reRender = true;
}
}
}
void VolumeData::renderViewCalculator(uint32_t viewIdx) {
auto varIdx = uint32_t(typeToFieldNamesMapBase[FieldType::SCALAR].size());
for (const CalculatorPtr& calculator : calculators) {
auto calculatorRenderer = calculator->getCalculatorRenderer();
if (calculatorRenderer && getIsScalarFieldUsedInView(viewIdx, varIdx, calculator.get())) {
calculatorRenderer->renderViewImpl(viewIdx);
}
if (calculator->getOutputFieldType() == FieldType::SCALAR) {
varIdx++;
}
}
}
void VolumeData::renderViewCalculatorPostOpaque(uint32_t viewIdx) {
auto varIdx = uint32_t(typeToFieldNamesMapBase[FieldType::SCALAR].size());
for (const CalculatorPtr& calculator : calculators) {
auto calculatorRenderer = calculator->getCalculatorRenderer();
if (calculatorRenderer && getIsScalarFieldUsedInView(viewIdx, varIdx, calculator.get())) {
calculatorRenderer->renderViewPostOpaqueImpl(viewIdx);
}
if (calculator->getOutputFieldType() == FieldType::SCALAR) {
varIdx++;
}
}
}
void VolumeData::addView(uint32_t viewIdx) {
for (const CalculatorPtr& calculator : calculators) {
auto calculatorRenderer = calculator->getCalculatorRenderer();
if (!calculatorRenderer) {
continue;
}
calculatorRenderer->addView(viewIdx);
}
}
void VolumeData::removeView(uint32_t viewIdx) {
for (const CalculatorPtr& calculator : calculators) {
auto calculatorRenderer = calculator->getCalculatorRenderer();
if (!calculatorRenderer) {
continue;
}
calculatorRenderer->removeView(viewIdx);
}
}
void VolumeData::recreateSwapchainView(uint32_t viewIdx, uint32_t width, uint32_t height) {
for (const CalculatorPtr& calculator : calculators) {
auto calculatorRenderer = calculator->getCalculatorRenderer();
if (!calculatorRenderer) {
continue;
}
calculatorRenderer->recreateSwapchainView(viewIdx, width, height);
}
}
void VolumeData::renderGuiWindowSecondary() {
if (multiVarTransferFunctionWindow.renderGui()) {
reRender = true;
if (multiVarTransferFunctionWindow.getTransferFunctionMapRebuilt()) {
onTransferFunctionMapRebuilt();
sgl::EventManager::get()->triggerEvent(std::make_shared<sgl::Event>(
ON_TRANSFER_FUNCTION_MAP_REBUILT_EVENT));
}
}
}
void VolumeData::renderGuiOverlay(uint32_t viewIdx) {
if (shallRenderColorLegendWidgets) {
int numWidgetsVisible = 0;
for (size_t i = 0; i < colorLegendWidgets.size(); i++) {
if (getIsTransferFunctionVisible(viewIdx, uint32_t(i))) {
numWidgetsVisible++;
}
}
int positionCounter = 0;
for (size_t i = 0; i < colorLegendWidgets.size(); i++) {
colorLegendWidgets.at(i).setPositionIndex(positionCounter, numWidgetsVisible);
if (getIsTransferFunctionVisible(viewIdx, uint32_t(i))) {
positionCounter++;
colorLegendWidgets.at(i).setAttributeMinValue(
multiVarTransferFunctionWindow.getSelectedRangeMin(int(i)));
colorLegendWidgets.at(i).setAttributeMaxValue(
multiVarTransferFunctionWindow.getSelectedRangeMax(int(i)));
colorLegendWidgets.at(i).renderGui();
}
}
}
}
void VolumeData::acquireTf(Renderer* renderer, int varIdx) {
// 2023-03-30: We don't need a reload if the range is fixed (avoid recomputing on every calculator change).
if (!multiVarTransferFunctionWindow.getIsSelectedRangeFixed(varIdx)) {
multiVarTransferFunctionWindow.loadAttributeDataIfEmpty(varIdx);
}
transferFunctionToRendererMap.insert(std::make_pair(varIdx, renderer));
}
void VolumeData::releaseTf(Renderer* renderer, int varIdx) {
auto iterRange = transferFunctionToRendererMap.equal_range(varIdx);
auto it = iterRange.first;
while (it != iterRange.second) {
if (it->second == renderer) {
transferFunctionToRendererMap.erase(it);
break;
}
it++;
}
}
void VolumeData::acquireTf(Calculator* renderer, int varIdx) {
if (!multiVarTransferFunctionWindow.getIsSelectedRangeFixed(varIdx)) {
multiVarTransferFunctionWindow.loadAttributeDataIfEmpty(varIdx);
}
// Only load the TF on calling acquireTf for now.
}
void VolumeData::releaseTf(Calculator* renderer, int varIdx) {
// Only load the TF on calling acquireTf for now.
}
bool VolumeData::getIsTransferFunctionVisible(uint32_t viewIdx, uint32_t varIdx) {
auto iterRange = transferFunctionToRendererMap.equal_range(int(varIdx));
auto it = iterRange.first;
while (it != iterRange.second) {
if (it->second->isVisibleInView(viewIdx)) {
return true;
}
it++;
}
return false;
}
void VolumeData::acquireScalarField(Renderer* renderer, int varIdx) {
scalarFieldToRendererMap.insert(std::make_pair(varIdx, renderer));
}
void VolumeData::releaseScalarField(Renderer* renderer, int varIdx) {
auto iterRange = scalarFieldToRendererMap.equal_range(varIdx);
auto it = iterRange.first;
while (it != iterRange.second) {
if (it->second == renderer) {
scalarFieldToRendererMap.erase(it);
break;
}
it++;
}
}
void VolumeData::acquireScalarField(Calculator* calculator, int varIdx) {
if (varIdx >= int(typeToFieldNamesMapBase[calculator->getOutputFieldType()].size())) {
std::string calculatorRefName = typeToFieldNamesMap[calculator->getOutputFieldType()][varIdx];
for (CalculatorPtr& calculatorRef : calculators) {
if (calculatorRef->getOutputFieldName() == calculatorRefName) {
calculatorUseMapRefToParent.insert(std::make_pair(calculatorRef.get(), calculator));
calculatorUseMapParentToRef.insert(std::make_pair(calculator, calculatorRef.get()));
break;
}
}
}
}
void VolumeData::releaseScalarField(Calculator* calculator, int varIdx) {
if (varIdx < int(typeToFieldNamesMapBase[calculator->getOutputFieldType()].size())) {
return;
}
std::string calculatorRefName = typeToFieldNamesMap[calculator->getOutputFieldType()][varIdx];
CalculatorPtr calculatorRef;
for (CalculatorPtr& calcIt : calculators) {
if (calcIt->getOutputFieldName() == calculatorRefName) {
calculatorRef = calcIt;
break;
}
}
auto iterRangeParentToRef = calculatorUseMapParentToRef.equal_range(calculator);
auto itParentToRef = iterRangeParentToRef.first;
while (itParentToRef != iterRangeParentToRef.second) {
if (itParentToRef->second == calculatorRef.get()) {
calculatorUseMapParentToRef.erase(itParentToRef);
break;
}
itParentToRef++;
}
auto iterRange = calculatorUseMapRefToParent.equal_range(calculatorRef.get());
auto it = iterRange.first;
while (it != iterRange.second) {
if (it->second == calculator) {
calculatorUseMapRefToParent.erase(it);
break;
}
it++;
}
}
bool VolumeData::getIsScalarFieldUsedInView(uint32_t viewIdx, uint32_t varIdx, Calculator* calculator) {
auto iterRange = scalarFieldToRendererMap.equal_range(int(varIdx));
auto itRend = iterRange.first;
while (itRend != iterRange.second) {
if (itRend->second->isVisibleInView(viewIdx)) {
return true;
}
itRend++;
}
auto fieldBaseCount = int(typeToFieldNamesMapBase[calculator->getOutputFieldType()].size());
auto fieldCount = int(typeToFieldNamesMap[calculator->getOutputFieldType()].size());
auto& fieldNames = typeToFieldNamesMap[calculator->getOutputFieldType()];
if (int(varIdx) >= fieldBaseCount) {
auto iterRangeToRef = calculatorUseMapRefToParent.equal_range(calculator);
auto it = iterRangeToRef.first;
while (it != iterRangeToRef.second) {
for (int varIdxNew = fieldBaseCount; varIdxNew < fieldCount; varIdxNew++) {
if (it->second->getOutputFieldName() == fieldNames.at(varIdxNew)) {
bool isScalarFieldUsed = getIsScalarFieldUsedInView(viewIdx, varIdxNew, it->second);
if (isScalarFieldUsed) {
return true;
}
}
}
std::string calculatorRefName = typeToFieldNamesMap[calculator->getOutputFieldType()][varIdx];
it++;
}
}
return false;
}
bool VolumeData::getIsScalarFieldUsedInAnyView(uint32_t varIdx, Calculator* calculator) {
auto iterRange = scalarFieldToRendererMap.equal_range(int(varIdx));
auto itRend = iterRange.first;
while (itRend != iterRange.second) {
if (itRend->second->isVisibleInAnyView()) {
return true;
}
itRend++;
}
auto fieldBaseCount = int(typeToFieldNamesMapBase[calculator->getOutputFieldType()].size());
auto fieldCount = int(typeToFieldNamesMap[calculator->getOutputFieldType()].size());
auto& fieldNames = typeToFieldNamesMap[calculator->getOutputFieldType()];
if (int(varIdx) >= fieldBaseCount) {
auto iterRangeToRef = calculatorUseMapRefToParent.equal_range(calculator);
auto it = iterRangeToRef.first;
while (it != iterRangeToRef.second) {
for (int varIdxNew = fieldBaseCount; varIdxNew < fieldCount; varIdxNew++) {
if (it->second->getOutputFieldName() == fieldNames.at(varIdxNew)) {
bool isScalarFieldUsed = getIsScalarFieldUsedInAnyView(varIdxNew, it->second);
if (isScalarFieldUsed) {
return true;
}
}
}
std::string calculatorRefName = typeToFieldNamesMap[calculator->getOutputFieldType()][varIdx];
it++;
}
}
return false;
}
uint32_t VolumeData::getVarIdxForCalculator(Calculator* calculator) {
recomputeColorLegend();
auto varIdx = uint32_t(typeToFieldNamesMapBase[calculator->getOutputFieldType()].size());
for (CalculatorPtr& calculatorIt : calculators) {
if (calculatorIt.get() == calculator) {
return varIdx;
}
if (calculatorIt->getOutputFieldType() == calculator->getOutputFieldType()) {
varIdx++;
}
}
sgl::Logfile::get()->throwError("Error in VolumeData::getVarIdxForCalculator: Encountered unknown calculator.");
return varIdx;
}
const std::vector<CalculatorPtr>& VolumeData::getCalculators() {
return calculators;
}
std::vector<std::shared_ptr<ICorrelationCalculator>> VolumeData::getCorrelationCalculatorsUsed() {
std::vector<std::shared_ptr<ICorrelationCalculator>> correlationCalculators;
auto varIdx = uint32_t(typeToFieldNamesMapBase[FieldType::SCALAR].size());
for (CalculatorPtr& calculator : calculators) {
if (calculator->getComputesCorrelation() && getIsScalarFieldUsedInAnyView(varIdx, calculator.get())) {
correlationCalculators.push_back(std::static_pointer_cast<ICorrelationCalculator>(calculator));
}
if (calculator->getOutputFieldType() == FieldType::SCALAR) {
varIdx++;
}
}
return correlationCalculators;
}
void VolumeData::onTransferFunctionMapRebuilt() {
recomputeColorLegend();
for (auto& calculator : calculators) {
if (calculator->getUseTransferFunction()) {
auto fieldCount = int(typeToFieldNamesMap[FieldType::SCALAR].size());
for (int varIdx = 0; varIdx < fieldCount; varIdx++) {
if (calculator->getUsesScalarFieldIdx(varIdx)
&& multiVarTransferFunctionWindow.getIsVariableDirty(varIdx)) {
calculator->setIsDirty();
break;
}
}
}
}
multiVarTransferFunctionWindow.resetDirty();
}
void VolumeData::recomputeColorLegend() {
for (size_t i = 0; i < colorLegendWidgets.size(); i++) {
std::vector<sgl::Color16> transferFunctionColorMap =
multiVarTransferFunctionWindow.getTransferFunctionMap_sRGB(int(i));
colorLegendWidgets.at(i).setTransferFunctionColorMap(transferFunctionColorMap);
}
}
void VolumeData::setClearColor(const sgl::Color& clearColor) {
cachedClearColor = clearColor;
multiVarTransferFunctionWindow.setClearColor(clearColor);
for (auto& colorLegendWidget : colorLegendWidgets) {
colorLegendWidget.setClearColor(clearColor);
}
}
void VolumeData::setUseLinearRGB(bool useLinearRGB) {
multiVarTransferFunctionWindow.setUseLinearRGB(useLinearRGB);
}
size_t VolumeData::getNewCalculatorUseCount(CalculatorType calculatorType) {
return ++calculatorTypeUseCounts[calculatorType];
}
void VolumeData::updateCalculatorName(const CalculatorPtr& calculator) {
std::string oldFieldName;
for (auto it = calculatorsHost.begin(); it != calculatorsHost.end(); it++) {
if (it->second == calculator) {
oldFieldName = it->first;
calculatorsHost.erase(it);
calculatorsHost.insert(std::make_pair(calculator->getOutputFieldName(), calculator));
break;
}
}
for (auto it = calculatorsDevice.begin(); it != calculatorsDevice.end(); it++) {
if (it->second == calculator) {
oldFieldName = it->first;
calculatorsDevice.erase(it);
calculatorsDevice.insert(std::make_pair(calculator->getOutputFieldName(), calculator));
break;
}
}
auto& fieldNames = typeToFieldNamesMap[calculator->getOutputFieldType()];
int fieldNameIdx = 0;
for (auto& fieldName : fieldNames) {
if (fieldName == oldFieldName) {
fieldName = calculator->getOutputFieldName();
break;
}
fieldNameIdx++;
}
if (calculator->getOutputFieldType() == FieldType::SCALAR) {
multiVarTransferFunctionWindow.updateAttributeName(fieldNameIdx, calculator->getOutputFieldName());
colorLegendWidgets.at(fieldNameIdx).setAttributeDisplayName(calculator->getOutputFieldName());
}
}
void VolumeData::updateCalculatorFilterDevice(const CalculatorPtr& calculator) {
auto itHost = calculatorsHost.find(calculator->getOutputFieldName());
auto itDevice = calculatorsDevice.find(calculator->getOutputFieldName());
if (itHost != calculatorsHost.end()) {
calculatorsDevice.insert(std::make_pair(calculator->getOutputFieldName(), calculator));
calculatorsHost.erase(itHost);
} else if (itDevice != calculatorsDevice.end()) {
calculatorsHost.insert(std::make_pair(calculator->getOutputFieldName(), calculator));
calculatorsDevice.erase(itDevice);
}
}
void VolumeData::updateCalculator(const CalculatorPtr& calculator) {
hostFieldCache->removeEntriesForFieldName(calculator->getOutputFieldName());
deviceFieldCache->removeEntriesForFieldName(calculator->getOutputFieldName());
fieldMinMaxCache->removeEntriesForFieldName(calculator->getOutputFieldName());
if (calculator->getOutputFieldType() == FieldType::SCALAR) {
auto& fieldNames = typeToFieldNamesMap[calculator->getOutputFieldType()];
int fieldNameIdx = 0;
for (auto& fieldName : fieldNames) {
if (fieldName == calculator->getOutputFieldName()) {
break;
}
fieldNameIdx++;
}
if (fieldNameIdx == int(fieldNames.size())) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::updateCalculator: Invalid field name \""
+ calculator->getOutputFieldName() + "\".");
}
multiVarTransferFunctionWindow.setAttributeDataDirty(fieldNameIdx);
}
}
void VolumeData::removeCalculator(const CalculatorPtr& calculator, int calculatorIdx) {
for (auto it = calculatorsHost.begin(); it != calculatorsHost.end(); it++) {
if (it->second == calculator) {
calculatorsHost.erase(it);
break;
}
}
for (auto it = calculatorsDevice.begin(); it != calculatorsDevice.end(); it++) {
if (it->second == calculator) {
calculatorsDevice.erase(it);
break;
}
}
auto& fieldNames = typeToFieldNamesMap[calculator->getOutputFieldType()];
int fieldNameIdx = 0;
for (auto& fieldName : fieldNames) {
if (fieldName == calculator->getOutputFieldName()) {
fieldName = calculator->getOutputFieldName();
break;
}
fieldNameIdx++;
}
if (fieldNameIdx == int(fieldNames.size())) {
sgl::Logfile::get()->throwError(
"Error in VolumeData::removeCalculator: Invalid field name \""
+ calculator->getOutputFieldName() + "\".");
}
calculators.erase(calculators.begin() + calculatorIdx);
fieldNames.erase(fieldNames.begin() + fieldNameIdx);
auto oldCalculatorUseMapRefToParent = calculatorUseMapRefToParent;
calculatorUseMapRefToParent.clear();
for (auto& entry : oldCalculatorUseMapRefToParent) {
if (entry.first != calculator.get() && entry.second != calculator.get()) {
calculatorUseMapRefToParent.insert(entry);
}
}
auto oldCalculatorUseMapParentToRef = calculatorUseMapParentToRef;
calculatorUseMapParentToRef.clear();
for (auto& entry : oldCalculatorUseMapParentToRef) {
if (entry.first != calculator.get() && entry.second != calculator.get()) {
calculatorUseMapParentToRef.insert(entry);
}
}
if (calculator->getOutputFieldType() == FieldType::SCALAR) {
multiVarTransferFunctionWindow.removeAttribute(fieldNameIdx);
colorLegendWidgets.erase(colorLegendWidgets.begin() + fieldNameIdx);
auto oldScalarFieldToRendererMap = scalarFieldToRendererMap;
scalarFieldToRendererMap.clear();
for (auto& entry : oldScalarFieldToRendererMap) {
entry.second->dirty = true;
entry.second->onFieldRemoved(calculator->getOutputFieldType(), fieldNameIdx);
if (entry.first < fieldNameIdx) {
scalarFieldToRendererMap.insert(entry);
} else if (entry.first > fieldNameIdx) {
scalarFieldToRendererMap.insert(std::make_pair(entry.first - 1, entry.second));
}
}
auto oldTransferFunctionToRendererMap = transferFunctionToRendererMap;
transferFunctionToRendererMap.clear();
for (auto& entry : oldTransferFunctionToRendererMap) {
if (entry.first < fieldNameIdx) {
transferFunctionToRendererMap.insert(entry);
} else if (entry.first > fieldNameIdx) {
transferFunctionToRendererMap.insert(std::make_pair(entry.first - 1, entry.second));
}
}
for (auto& calculatorIt : calculators) {
calculatorIt->onFieldRemoved(calculator->getOutputFieldType(), fieldNameIdx);
}
}
}
void VolumeData::setFileDialogInstance(ImGuiFileDialog* _fileDialogInstance) {
this->fileDialogInstance = _fileDialogInstance;
}
bool VolumeData::saveFieldToFile(const std::string& filePath, FieldType fieldType, int fieldIndex) {
std::string filenameLower = boost::to_lower_copy(filePath);
if (fieldType != FieldType::SCALAR) {
sgl::Logfile::get()->writeError(
"Error in VolumeData::saveFieldToFile: Currently, only the export of scalar fields is supported.");
return false;
}
auto fieldData = getFieldEntryCpu(fieldType, typeToFieldNamesMap[fieldType].at(fieldIndex));
std::string fileExtension = sgl::FileUtils::get()->getFileExtensionLower(filePath);
VolumeWriter* volumeWriter = createVolumeWriterByExtension(fileExtension);
volumeWriter->writeFieldToFile(
filePath, this, fieldType, typeToFieldNamesMap[fieldType].at(fieldIndex),
currentTimeStepIdx, currentEnsembleIdx);
delete volumeWriter;
return false;
}
glm::vec3 VolumeData::screenPosToRayDir(SceneData* sceneData, int globalX, int globalY) {
sgl::CameraPtr camera = sceneData->camera;
uint32_t viewportWidth = *sceneData->viewportWidth;
uint32_t viewportHeight = *sceneData->viewportHeight;
int x = globalX - *sceneData->viewportPositionX;
int y = globalY - *sceneData->viewportPositionY;
glm::mat4 inverseViewMatrix = glm::inverse(camera->getViewMatrix());
float scale = std::tan(camera->getFOVy() * 0.5f);
glm::vec2 rayDirCameraSpace;
rayDirCameraSpace.x = (2.0f * (float(x) + 0.5f) / float(viewportWidth) - 1.0f) * camera->getAspectRatio() * scale;
rayDirCameraSpace.y = (2.0f * (float(viewportHeight - y - 1) + 0.5f) / float(viewportHeight) - 1.0f) * scale;
glm::vec4 rayDirectionVec4 = inverseViewMatrix * glm::vec4(rayDirCameraSpace, -1.0, 0.0);
glm::vec3 rayDirection = normalize(glm::vec3(rayDirectionVec4.x, rayDirectionVec4.y, rayDirectionVec4.z));
return rayDirection;
}
bool VolumeData::pickPointScreen(
SceneData* sceneData, int globalX, int globalY, glm::vec3& firstHit, glm::vec3& lastHit) const {
glm::vec3 rayDirection = screenPosToRayDir(sceneData, globalX, globalY);
return pickPointWorld(sceneData->camera->getPosition(), rayDirection, firstHit, lastHit);
}
bool VolumeData::pickPointScreenAtZ(
SceneData* sceneData, int globalX, int globalY, int z, glm::vec3& hit) const {
glm::vec3 rayDirection = screenPosToRayDir(sceneData, globalX, globalY);
return pickPointWorldAtZ(sceneData->camera->getPosition(), rayDirection, z, hit);
}
bool VolumeData::pickPointWorld(
const glm::vec3& cameraPosition, const glm::vec3& rayDirection, glm::vec3& firstHit, glm::vec3& lastHit) const {
glm::vec3 rayOrigin = cameraPosition;
float tNear, tFar;
if (_rayBoxIntersection(rayOrigin, rayDirection, boxRendering.min, boxRendering.max, tNear, tFar)) {
firstHit = rayOrigin + tNear * rayDirection;
lastHit = rayOrigin + tFar * rayDirection;
return true;
}
return false;
}
bool VolumeData::pickPointWorldAtZ(
const glm::vec3& cameraPosition, const glm::vec3& rayDirection, int z, glm::vec3& hit) const {
glm::vec3 rayOrigin = cameraPosition;
float tHit;
if (_rayZPlaneIntersection(
rayOrigin, rayDirection,
float(z) / float(zs > 1 ? zs - 1 : 1) * (boxRendering.max.z - boxRendering.min.z) + boxRendering.min.z,
glm::vec2(boxRendering.min.x, boxRendering.min.y),
glm::vec2(boxRendering.max.x, boxRendering.max.y),
tHit)) {
hit = rayOrigin + tHit * rayDirection;
return true;
}
return false;
}
/**
* Helper function for StreamlineTracingGrid::rayBoxIntersection (see below).
*/
bool VolumeData::_rayBoxPlaneIntersection(
float rayOriginX, float rayDirectionX, float lowerX, float upperX, float& tNear, float& tFar) {
if (std::abs(rayDirectionX) < 0.00001f) {
// Ray is parallel to the x planes.
if (rayOriginX < lowerX || rayOriginX > upperX) {
return false;
}
} else {
// Not parallel to the x planes. Compute the intersection distance to the planes.
float t0 = (lowerX - rayOriginX) / rayDirectionX;
float t1 = (upperX - rayOriginX) / rayDirectionX;
if (t0 > t1) {
// Since t0 intersection with near plane.
float tmp = t0;
t0 = t1;
t1 = tmp;
}
if (t0 > tNear) {
// We want the largest tNear.
tNear = t0;
}
if (t1 < tFar) {
// We want the smallest tFar.
tFar = t1;
}
if (tNear > tFar) {
// Box is missed.
return false;
}
if (tFar < 0) {
// Box is behind ray.
return false;
}
}
return true;
}
/**
* Implementation of ray-box intersection (idea from A. Glassner et al., "An Introduction to Ray Tracing").
* For more details see: https://www.siggraph.org//education/materials/HyperGraph/raytrace/rtinter3.htm
*/
bool VolumeData::_rayBoxIntersection(
const glm::vec3& rayOrigin, const glm::vec3& rayDirection, const glm::vec3& lower, const glm::vec3& upper,
float& tNear, float& tFar) {
#ifdef TRACY_PROFILE_TRACING
ZoneScoped;
#endif
tNear = std::numeric_limits<float>::lowest();
tFar = std::numeric_limits<float>::max();
for (int i = 0; i < 3; i++) {
if (!_rayBoxPlaneIntersection(
rayOrigin[i], rayDirection[i], lower[i], upper[i], tNear, tFar)) {
return false;
}
}
return true;
}
bool VolumeData::_rayZPlaneIntersection(
const glm::vec3& rayOrigin, const glm::vec3& rayDirection, float z, glm::vec2 lowerXY, glm::vec2 upperXY,
float& tHit) {
if (std::abs(rayDirection.z) < 1e-5f) {
// Plane and ray are parallel.
return false;
} else {
tHit = (z - rayOrigin.z) / rayDirection.z;
glm::vec3 hitPos = rayOrigin + tHit * rayDirection;
if (hitPos.x >= lowerXY.x && hitPos.y >= lowerXY.y && hitPos.x <= upperXY.x && hitPos.y <= upperXY.y) {
return true;
}
}
return true;
}
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...