/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2023 * * * * 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 namespace { constexpr std::string_view _loggerCat = "RenderableGaiaStars"; constexpr size_t PositionSize = 3; constexpr size_t ColorSize = 2; constexpr size_t VelocitySize = 3; constexpr openspace::properties::Property::PropertyInfo FilePathInfo = { "File", "File Path", "The path to the file with data for the stars to be rendered" }; constexpr openspace::properties::Property::PropertyInfo FileReaderOptionInfo = { "FileReaderOption", "File Reader Option", "This value tells the renderable what format the input data file has. " "'Fits' will read a FITS file, construct an Octree from it and render full " "data. 'Speck' will read a SPECK file, construct an Octree from it and render " "full data. 'BinaryRaw' will read a preprocessed binary file with ordered star " "data, construct an Octree and render it. 'BinaryOctree' will read a constructed " "Octree from binary file and render full data. 'StreamOctree' will read an index " "file with full Octree structure and then stream nodes during runtime. (This " "option is suited for bigger datasets)" }; constexpr openspace::properties::Property::PropertyInfo RenderModeInfo = { "RenderMode", "Render Mode", "This value determines which predefined columns to use in rendering. If " "'Static' only the position of the stars is used. 'Color' uses position + color " "parameters and 'Motion' uses pos, color as well as velocity for the stars" }; constexpr openspace::properties::Property::PropertyInfo ShaderOptionInfo = { "ShaderOption", "Shader Option", "This value determines which shaders to use while rendering. If 'Point_*' is " "chosen then gl_Points will be rendered and then spread out with a bloom " "filter. If 'Billboard_*' is chosen then the geometry shaders will generate " "screen-faced billboards for all stars. For '*_SSBO' the data will be stored in " "Shader Storage Buffer Objects while '*_VBO' uses Vertex Buffer Objects for the " "streaming. OBS! SSBO won't work on Apple" }; constexpr openspace::properties::Property::PropertyInfo PsfTextureInfo = { "Texture", "Point Spread Function Texture", "The path to the texture that should be used as a point spread function for the " "stars" }; constexpr openspace::properties::Property::PropertyInfo LuminosityMultiplierInfo = { "LuminosityMultiplier", "Luminosity Multiplier", "Factor by which to multiply the luminosity with. [Works in Color and Motion " "modes]" }; constexpr openspace::properties::Property::PropertyInfo MagnitudeBoostInfo = { "MagnitudeBoost", "Magnitude Boost", "Sets what percent of the star magnitude that will be used as boost to star " "size. [Works only with billboards in Color and Motion modes]" }; constexpr openspace::properties::Property::PropertyInfo CutOffThresholdInfo = { "CutOffThreshold", "Cut Off Threshold", "Set threshold for when to cut off star rendering. Stars closer than this " "threshold are given full opacity. Farther away, stars dim proportionally to the " "4-logarithm of their distance" }; constexpr openspace::properties::Property::PropertyInfo SharpnessInfo = { "Sharpness", "Sharpness", "Adjust star sharpness. [Works only with billboards]" }; constexpr openspace::properties::Property::PropertyInfo BillboardSizeInfo = { "BillboardSize", "Billboard Size", "Set the billboard size of all stars. [Works only with billboards]" }; constexpr openspace::properties::Property::PropertyInfo CloseUpBoostDistInfo = { "CloseUpBoostDist", "Close-Up Boost Distance [pc]", "Set the distance where stars starts to increase in size. Unit is Parsec [Works " "only with billboards]" }; constexpr openspace::properties::Property::PropertyInfo TmPointFilterSizeInfo = { "FilterSize", "Filter Size [px]", "Set the filter size in pixels used in tonemapping for point splatting rendering" "[Works only with points]" }; constexpr openspace::properties::Property::PropertyInfo TmPointSigmaInfo = { "Sigma", "Normal Distribution Sigma", "Set the normal distribution sigma used in tonemapping for point splatting " "rendering. [Works only with points]" }; constexpr openspace::properties::Property::PropertyInfo AdditionalNodesInfo = { "AdditionalNodes", "Additional Nodes", "Determines how many additional nodes around the camera that will be fetched " "from disk. The first value determines how many additional layers of parents " "that will be fetched. The second value determines how many layers of descendant " "that will be fetched from the found parents" }; constexpr openspace::properties::Property::PropertyInfo TmPointPxThresholdInfo = { "PixelWeightThreshold", "Pixel Weight Threshold", "Set the threshold for how big the elliptic weight of a pixel has to be to " "contribute to the final elliptic shape. A smaller value gives a more visually " "pleasing result while a bigger value will speed up the rendering on skewed " "frustums (aka Domes)" }; constexpr openspace::properties::Property::PropertyInfo ColorTextureInfo = { "ColorMap", "Color Texture", "The path to the texture that is used to convert from the magnitude of the star " "to its color. The texture is used as a one dimensional lookup function" }; constexpr openspace::properties::Property::PropertyInfo FirstRowInfo = { "FirstRow", "First Row to Read", "Defines the first row that will be read from the specified FITS file No need to " "define if data already has been processed. [Works only with " "FileReaderOption::Fits]" }; constexpr openspace::properties::Property::PropertyInfo LastRowInfo = { "LastRow", "Last Row to Read", "Defines the last row that will be read from the specified FITS file; has to be " "equal to or greater than FirstRow. No need to define if data already has been " "processed. [Works only with FileReaderOption::Fits]" }; constexpr openspace::properties::Property::PropertyInfo ColumnNamesInfo = { "ColumnNames", "Column Names", "A list of strings with the names of all the columns that are to be read from " "the specified FITS file. No need to define if data already has been processed. " "[Works only with FileReaderOption::Fits]" }; constexpr openspace::properties::Property::PropertyInfo NumRenderedStarsInfo = { "NumRenderedStars", "Rendered Stars", "The number of rendered stars in the current frame" }; constexpr openspace::properties::Property::PropertyInfo CpuRamBudgetInfo = { "CpuRamBudget", "CPU RAM Budget", "Current remaining budget (bytes) on the CPU RAM for loading more node data files" }; constexpr openspace::properties::Property::PropertyInfo GpuStreamBudgetInfo = { "GpuStreamBudget", "GPU Stream Budget", "Current remaining memory budget [in number of chunks] on the GPU for streaming " "additional stars" }; constexpr openspace::properties::Property::PropertyInfo LodPixelThresholdInfo = { "LodPixelThreshold", "LOD Pixel Threshold", "The number of total pixels a nodes AABB can have in clipping space before its " "parent is fetched as LOD cache" }; constexpr openspace::properties::Property::PropertyInfo MaxGpuMemoryPercentInfo = { "MaxGpuMemoryPercent", "Max GPU Memory", "Sets the max percent of existing GPU memory budget that the streaming will use" }; constexpr openspace::properties::Property::PropertyInfo MaxCpuMemoryPercentInfo = { "MaxCpuMemoryPercent", "Max CPU Memory", "Sets the max percent of existing CPU memory budget that the streaming of files " "will use" }; constexpr openspace::properties::Property::PropertyInfo FilterPosXInfo = { "FilterPosX", "PosX Threshold", "If defined then only stars with Position X values between [min, max] will be " "rendered (if min is set to 0.0 it is read as -Inf, if max is set to 0.0 it is " "read as +Inf). Measured in kiloParsec" }; constexpr openspace::properties::Property::PropertyInfo FilterPosYInfo = { "FilterPosY", "PosY Threshold", "If defined then only stars with Position Y values between [min, max] will be " "rendered (if min is set to 0.0 it is read as -Inf, if max is set to 0.0 it is " "read as +Inf). Measured in kiloParsec" }; constexpr openspace::properties::Property::PropertyInfo FilterPosZInfo = { "FilterPosZ", "PosZ Threshold", "If defined then only stars with Position Z values between [min, max] will be " "rendered (if min is set to 0.0 it is read as -Inf, if max is set to 0.0 it is " "read as +Inf). Measured in kiloParsec" }; constexpr openspace::properties::Property::PropertyInfo FilterGMagInfo = { "FilterGMag", "GMag Threshold", "If defined then only stars with G mean magnitude values between [min, max] will " "be rendered (if min is set to 20.0 it is read as -Inf, if max is set to 20.0 it " "is read as +Inf). If min = max then all values equal min|max will be filtered " "away" }; constexpr openspace::properties::Property::PropertyInfo FilterBpRpInfo = { "FilterBpRp", "Bp-Rp Threshold", "If defined then only stars with Bp-Rp color values between [min, max] will be " "rendered (if min is set to 0.0 it is read as -Inf, if max is set to 0.0 it is " "read as +Inf). If min = max then all values equal min|max will be filtered away" }; constexpr openspace::properties::Property::PropertyInfo FilterDistInfo = { "FilterDist", "Dist Threshold", "If defined then only stars with Distances values between [min, max] will be " "rendered (if min is set to 0.0 it is read as -Inf, if max is set to 0.0 it is " "read as +Inf). Measured in kParsec" }; constexpr openspace::properties::Property::PropertyInfo ReportGlErrorsInfo = { "ReportGlErrors", "Report GL Errors", "If set to true, any OpenGL errors will be reported if encountered" }; struct [[codegen::Dictionary(RenderableGaiaStars)]] Parameters { // [[codegen::verbatim(FilePathInfo.description)]] std::string file; enum class [[codegen::map(openspace::gaia::FileReaderOption)]] FileReader { Fits, Speck, BinaryRaw, BinaryOctree, StreamOctree }; // [[codegen::verbatim(FileReaderOptionInfo.description)]] FileReader fileReaderOption; enum class [[codegen::map(openspace::gaia::RenderMode)]] RenderMode { Static, Color, Motion }; // [[codegen::verbatim(RenderModeInfo.description)]] std::optional renderMode; enum class [[codegen::map(openspace::gaia::ShaderOption)]] ShaderOption { PointSSBO [[codegen::key("Point_SSBO")]], PointVBO [[codegen::key("Point_VBO")]], BillboardSSBO [[codegen::key("Billboard_SSBO")]], BillboardVBO [[codegen::key("Billboard_VBO")]], BillboardSSBONoFBO [[codegen::key("Billboard_SSBO_noFBO")]] }; // [codegen::verbatim(ShaderOptionInfo.description)]] std::optional shaderOption; // [codegen::verbatim(PsfTextureInfo.description)]] std::string texture; // [codegen::verbatim(ColorTextureInfo.description)]] std::string colorMap; // [codegen::verbatim(LuminosityMultiplierInfo.description)]] std::optional luminosityMultiplier; // [codegen::verbatim(MagnitudeBoostInfo.description)]] std::optional magnitudeBoost; // [codegen::verbatim(CutOffThresholdInfo.description)]] std::optional cutOffThreshold; // [codegen::verbatim(SharpnessInfo.description)]] std::optional sharpness; // [codegen::verbatim(BillboardSizeInfo.description)]] std::optional billboardSize; // [codegen::verbatim(CloseUpBoostDistInfo.description)]] std::optional closeUpBoostDist; // [codegen::verbatim(TmPointFilterSizeInfo.description)]] std::optional filterSize; // [codegen::verbatim(TmPointSigmaInfo.description)]] std::optional sigma; // [codegen::verbatim(AdditionalNodesInfo.description)]] std::optional additionalNodes; // [codegen::verbatim(TmPointPxThresholdInfo.description)]] std::optional pixelWeightThreshold; // [codegen::verbatim(FirstRowInfo.description)]] std::optional firstRow; // [codegen::verbatim(LastRowInfo.description)]] std::optional lastRow; // [codegen::verbatim(ColumnNamesInfo.description)]] std::optional> columnNames; // [codegen::verbatim(LodPixelThresholdInfo.description)]] std::optional lodPixelThreshold; // [codegen::verbatim(MaxGpuMemoryPercentInfo.description)]] std::optional maxGpuMemoryPercent; // [codegen::verbatim(MaxCpuMemoryPercentInfo.description)]] std::optional maxCpuMemoryPercent; // [codegen::verbatim(FilterPosXInfo.description)]] std::optional filterPosX; // [codegen::verbatim(FilterPosYInfo.description)]] std::optional filterPosY; // [codegen::verbatim(FilterPosZInfo.description)]] std::optional filterPosZ; // [codegen::verbatim(FilterGMagInfo.description)]] std::optional filterGMag; // [codegen::verbatim(FilterBpRpInfo.description)]] std::optional filterBpRp; // [codegen::verbatim(FilterDistInfo.description)]] std::optional filterDist; // [codegen::verbatim(ReportGlErrorsInfo.description)]] std::optional reportGlErrors; }; #include "renderablegaiastars_codegen.cpp" } // namespace namespace openspace { documentation::Documentation RenderableGaiaStars::Documentation() { return codegen::doc("gaiamission_renderablegaiastars"); } RenderableGaiaStars::RenderableGaiaStars(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _filePath(FilePathInfo) , _pointSpreadFunctionTexturePath(PsfTextureInfo) , _colorTexturePath(ColorTextureInfo) , _luminosityMultiplier(LuminosityMultiplierInfo, 35.f, 1.f, 1000.f) , _magnitudeBoost(MagnitudeBoostInfo, 25.f, 0.f, 100.f) , _cutOffThreshold(CutOffThresholdInfo, 38.f, 0.f, 50.f) , _sharpness(SharpnessInfo, 1.45f, 0.f, 5.f) , _billboardSize(BillboardSizeInfo, 10.f, 1.f, 100.f) , _closeUpBoostDist(CloseUpBoostDistInfo, 300.f, 1.f, 1000.f) , _tmPointFilterSize(TmPointFilterSizeInfo, 7, 1, 19) , _tmPointSigma(TmPointSigmaInfo, 0.7f, 0.1f, 3.f) , _additionalNodes(AdditionalNodesInfo, glm::ivec2(1), glm::ivec2(0), glm::ivec2(4)) , _tmPointPixelWeightThreshold(TmPointPxThresholdInfo, 0.001f, 0.000001f, 0.01f) , _lodPixelThreshold(LodPixelThresholdInfo, 250.f, 0.f, 5000.f) , _posXThreshold(FilterPosXInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(10.f)) , _posYThreshold(FilterPosYInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(10.f)) , _posZThreshold(FilterPosZInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(10.f)) , _gMagThreshold(FilterGMagInfo, glm::vec2(20.f), glm::vec2(-10.f), glm::vec2(30.f)) , _bpRpThreshold(FilterBpRpInfo, glm::vec2(0.f), glm::vec2(-10.f), glm::vec2(30.f)) , _distThreshold(FilterDistInfo, glm::vec2(0.f), glm::vec2(0.f), glm::vec2(100.f)) , _firstRow(FirstRowInfo, 0, 0, 2539913) // DR1-max: 2539913 , _lastRow(LastRowInfo, 0, 0, 2539913) , _columnNamesList(ColumnNamesInfo) , _fileReaderOption( FileReaderOptionInfo, properties::OptionProperty::DisplayType::Dropdown ) , _renderMode(RenderModeInfo, properties::OptionProperty::DisplayType::Dropdown) , _shaderOption(ShaderOptionInfo, properties::OptionProperty::DisplayType::Dropdown) , _nRenderedStars(NumRenderedStarsInfo, 0, 0, 2000000000) // 2 Billion stars , _cpuRamBudgetProperty(CpuRamBudgetInfo, 0.f, 0.f, 1.f) , _gpuStreamBudgetProperty(GpuStreamBudgetInfo, 0.f, 0.f, 1.f) , _maxGpuMemoryPercent(MaxGpuMemoryPercentInfo, 0.45f, 0.f, 1.f) , _maxCpuMemoryPercent(MaxCpuMemoryPercentInfo, 0.5f, 0.f, 1.f) , _reportGlErrors(ReportGlErrorsInfo, false) , _accumulatedIndices(1, 0) { using File = ghoul::filesystem::File; const Parameters p = codegen::bake(dictionary); _filePath = absPath(p.file).string(); _dataFile = std::make_unique(_filePath.value()); _dataFile->setCallback([this]() { _dataIsDirty = true; }); _filePath.onChange([this]() { if (std::filesystem::exists(_filePath.value())) { _dataIsDirty = true; } else { LWARNING(fmt::format("File not found: {}", _filePath)); } }); addProperty(_filePath); _fileReaderOption.addOptions({ { gaia::FileReaderOption::Fits, "Fits" }, { gaia::FileReaderOption::Speck, "Speck" }, { gaia::FileReaderOption::BinaryRaw, "BinaryRaw" }, { gaia::FileReaderOption::BinaryOctree, "BinaryOctree" }, { gaia::FileReaderOption::StreamOctree, "StreamOctree" } }); _fileReaderOption = codegen::map(p.fileReaderOption); _renderMode.addOptions({ { gaia::RenderMode::Static, "Static" }, { gaia::RenderMode::Color, "Color" }, { gaia::RenderMode::Motion, "Motion" } }); if (p.renderMode.has_value()) { _renderMode = codegen::map(*p.renderMode); } _renderMode.onChange([&]() { _buffersAreDirty = true; }); addProperty(_renderMode); _shaderOption.addOptions({ { gaia::ShaderOption::PointSSBO, "Point_SSBO" }, { gaia::ShaderOption::PointVBO, "Point_VBO" }, { gaia::ShaderOption::BillboardSSBO, "Billboard_SSBO" }, { gaia::ShaderOption::BillboardVBO, "Billboard_VBO" }, { gaia::ShaderOption::BillboardSSBONoFBO, "Billboard_SSBO_noFBO" } }); if (p.shaderOption.has_value()) { _shaderOption = codegen::map(*p.shaderOption); #ifdef __APPLE__ switch (_shaderOption) { case gaia::ShaderOption::PointSSBO: case gaia::ShaderOption::BillboardSSBO: case gaia::ShaderOption::BillboardSSBONoFBO: LWARNING("Shader option unsupported, changing to Point VBO"); _shaderOption = gaia::ShaderOption::PointVBO; } #endif // __APPLE__ } _shaderOption.onChange([&]() { _buffersAreDirty = true; _shadersAreDirty = true; }); addProperty(_shaderOption); _pointSpreadFunctionTexturePath = absPath(p.texture).string(); _pointSpreadFunctionTexturePath.onChange( [this](){ _pointSpreadFunctionTextureIsDirty = true; } ); _pointSpreadFunctionFile = std::make_unique( _pointSpreadFunctionTexturePath.value() ); _pointSpreadFunctionFile->setCallback( [this]() { _pointSpreadFunctionTextureIsDirty = true; } ); _colorTexturePath = absPath(p.colorMap).string(); _colorTextureFile = std::make_unique(_colorTexturePath.value()); _colorTexturePath.onChange([this]() { _colorTextureIsDirty = true; }); _colorTextureFile->setCallback([this]() { _colorTextureIsDirty = true; }); _luminosityMultiplier = p.luminosityMultiplier.value_or(_luminosityMultiplier); _magnitudeBoost = p.magnitudeBoost.value_or(_magnitudeBoost); _cutOffThreshold = p.cutOffThreshold.value_or(_cutOffThreshold); _sharpness = p.sharpness.value_or(_sharpness); _billboardSize = p.billboardSize.value_or(_billboardSize); _closeUpBoostDist = p.closeUpBoostDist.value_or(_closeUpBoostDist); _tmPointFilterSize = p.filterSize.value_or(_tmPointFilterSize); _tmPointSigma = p.sigma.value_or(_tmPointSigma); _tmPointPixelWeightThreshold = p.pixelWeightThreshold.value_or(_tmPointPixelWeightThreshold); _additionalNodes = p.additionalNodes.value_or(_additionalNodes); _lodPixelThreshold = p.lodPixelThreshold.value_or(_lodPixelThreshold); _maxGpuMemoryPercent = p.maxGpuMemoryPercent.value_or(_maxGpuMemoryPercent); _maxGpuMemoryPercent.onChange([&]() { if (_ssboData != 0) { glDeleteBuffers(1, &_ssboData); glGenBuffers(1, &_ssboData); LDEBUG(fmt::format( "Re-generating Data Shader Storage Buffer Object id '{}'", _ssboData )); } // Find out our new budget. Use dedicated video memory instead of current // available to always be consistant with previous call(s). GLint nDedicatedVidMemoryInKB = 0; glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nDedicatedVidMemoryInKB); float dedicatedVidMem = static_cast( static_cast(nDedicatedVidMemoryInKB) * 1024 ); // TODO: Need to fix what happens if we can't query! For now use 2 GB by default. _gpuMemoryBudgetInBytes = dedicatedVidMem > 0 ? static_cast(dedicatedVidMem * _maxGpuMemoryPercent) : 2147483648; _buffersAreDirty = true; _maxStreamingBudgetInBytes = 0; }); _maxCpuMemoryPercent = p.maxCpuMemoryPercent.value_or(_maxCpuMemoryPercent); _posXThreshold = p.filterPosX.value_or(_posXThreshold); addProperty(_posXThreshold); _posYThreshold = p.filterPosY.value_or(_posYThreshold); addProperty(_posYThreshold); _posZThreshold = p.filterPosZ.value_or(_posZThreshold); addProperty(_posZThreshold); _gMagThreshold = p.filterGMag.value_or(_gMagThreshold); addProperty(_gMagThreshold); _bpRpThreshold = p.filterBpRp.value_or(_bpRpThreshold); addProperty(_bpRpThreshold); _distThreshold = p.filterDist.value_or(_distThreshold); addProperty(_distThreshold); // Only add properties correlated to fits files if we're reading from a fits file. if (_fileReaderOption == gaia::FileReaderOption::Fits) { _firstRow = p.firstRow.value_or(_firstRow); _firstRow.onChange([&]() { _dataIsDirty = true; }); addProperty(_firstRow); _lastRow = p.lastRow.value_or(_lastRow); _lastRow.onChange([&]() { _dataIsDirty = true; }); addProperty(_lastRow); if (p.columnNames.has_value()) { ghoul::Dictionary tmpDict = dictionary.value( ColumnNamesInfo.identifier ); // Ugly fix for ASCII sorting when there are more columns read than 10. std::set intKeys; for (std::string_view key : tmpDict.keys()) { intKeys.insert(std::stoi(std::string(key))); } for (int key : intKeys) { _columnNames.push_back(tmpDict.value(std::to_string(key))); } // Copy values to the StringListproperty to be shown in the Property list. _columnNamesList = _columnNames; // OBS - This is not used atm! } if (_firstRow > _lastRow) { throw ghoul::RuntimeError("User defined FirstRow is bigger than LastRow"); } } _reportGlErrors = p.reportGlErrors.value_or(_reportGlErrors); addProperty(_reportGlErrors); // Add a read-only property for the number of rendered stars per frame. _nRenderedStars.setReadOnly(true); addProperty(_nRenderedStars); // Add CPU RAM Budget Property and GPU Stream Budget Property to menu. _cpuRamBudgetProperty.setReadOnly(true); addProperty(_cpuRamBudgetProperty); _gpuStreamBudgetProperty.setReadOnly(true); addProperty(_gpuStreamBudgetProperty); } bool RenderableGaiaStars::isReady() const { return _program && _programTM; } void RenderableGaiaStars::initializeGL() { //using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; //_program->setIgnoreUniformLocationError(IgnoreError::Yes); // Add common properties to menu. addProperty(_colorTexturePath); addProperty(_luminosityMultiplier); addProperty(_cutOffThreshold); addProperty(_lodPixelThreshold); addProperty(_maxGpuMemoryPercent); // Construct shader program depending on user-defined shader option. const int option = _shaderOption; switch (option) { case gaia::ShaderOption::PointSSBO: _program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") ); _uniformCache.maxStarsPerNode = _program->uniformLocation("maxStarsPerNode"); _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); _uniformCache.nChunksToRender = _program->uniformLocation("nChunksToRender"); _programTM = global::renderEngine->buildRenderProgram( "ToneMapping", absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_point_fs.glsl") ); _uniformCacheTM.screenSize = _programTM->uniformLocation("screenSize"); _uniformCacheTM.filterSize = _programTM->uniformLocation("filterSize"); _uniformCacheTM.sigma = _programTM->uniformLocation("sigma"); _uniformCacheTM.projection = _programTM->uniformLocation("projection"); _uniformCacheTM.pixelWeightThreshold = _programTM->uniformLocation("pixelWeightThreshold"); addProperty(_tmPointFilterSize); addProperty(_tmPointSigma); addProperty(_tmPointPixelWeightThreshold); break; case gaia::ShaderOption::PointVBO: _program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") ); _programTM = global::renderEngine->buildRenderProgram("ToneMapping", absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_point_fs.glsl") ); _uniformCacheTM.screenSize = _programTM->uniformLocation("screenSize"); _uniformCacheTM.filterSize = _programTM->uniformLocation("filterSize"); _uniformCacheTM.sigma = _programTM->uniformLocation("sigma"); _uniformCacheTM.projection = _programTM->uniformLocation("projection"); _uniformCacheTM.pixelWeightThreshold = _programTM->uniformLocation("pixelWeightThreshold"); addProperty(_tmPointFilterSize); addProperty(_tmPointSigma); addProperty(_tmPointPixelWeightThreshold); break; case gaia::ShaderOption::BillboardSSBO: _program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") ); _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); _uniformCache.magnitudeBoost = _program->uniformLocation("magnitudeBoost"); _uniformCache.sharpness = _program->uniformLocation("sharpness"); _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); _uniformCache.closeUpBoostDist = _program->uniformLocation( "closeUpBoostDist" ); _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); _uniformCache.maxStarsPerNode = _program->uniformLocation("maxStarsPerNode"); _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); _uniformCache.nChunksToRender = _program->uniformLocation("nChunksToRender"); _programTM = global::renderEngine->buildRenderProgram( "ToneMapping", absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_billboard_fs.glsl") ); addProperty(_magnitudeBoost); addProperty(_sharpness); addProperty(_billboardSize); addProperty(_closeUpBoostDist); //addProperty(_pointSpreadFunctionTexturePath); break; case gaia::ShaderOption::BillboardSSBONoFBO: _program = global::renderEngine->buildRenderProgram("GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_nofbo_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") ); _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); _uniformCache.magnitudeBoost = _program->uniformLocation("magnitudeBoost"); _uniformCache.sharpness = _program->uniformLocation("sharpness"); _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); _uniformCache.closeUpBoostDist = _program->uniformLocation( "closeUpBoostDist" ); _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); _uniformCache.maxStarsPerNode = _program->uniformLocation("maxStarsPerNode"); _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); _uniformCache.nChunksToRender = _program->uniformLocation("nChunksToRender"); addProperty(_magnitudeBoost); addProperty(_sharpness); addProperty(_billboardSize); addProperty(_closeUpBoostDist); break; case gaia::ShaderOption::BillboardVBO: _program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") ); _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); _uniformCache.magnitudeBoost = _program->uniformLocation("magnitudeBoost"); _uniformCache.sharpness = _program->uniformLocation("sharpness"); _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); _uniformCache.closeUpBoostDist = _program->uniformLocation( "closeUpBoostDist" ); _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); _programTM = global::renderEngine->buildRenderProgram("ToneMapping", absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_billboard_fs.glsl") ); addProperty(_magnitudeBoost); addProperty(_sharpness); addProperty(_billboardSize); addProperty(_closeUpBoostDist); //addProperty(_pointSpreadFunctionTexturePath); break; } // Common uniforms for all shaders: _uniformCache.model = _program->uniformLocation("model"); _uniformCache.view = _program->uniformLocation("view"); _uniformCache.projection = _program->uniformLocation("projection"); _uniformCache.time = _program->uniformLocation("time"); _uniformCache.renderOption = _program->uniformLocation("renderOption"); _uniformCache.viewScaling = _program->uniformLocation("viewScaling"); _uniformCache.cutOffThreshold = _program->uniformLocation("cutOffThreshold"); _uniformCache.luminosityMultiplier = _program->uniformLocation( "luminosityMultiplier" ); _uniformCache.colorTexture = _program->uniformLocation("colorTexture"); _uniformFilterCache.posXThreshold = _program->uniformLocation("posXThreshold"); _uniformFilterCache.posYThreshold = _program->uniformLocation("posYThreshold"); _uniformFilterCache.posZThreshold = _program->uniformLocation("posZThreshold"); _uniformFilterCache.gMagThreshold = _program->uniformLocation("gMagThreshold"); _uniformFilterCache.bpRpThreshold = _program->uniformLocation("bpRpThreshold"); _uniformFilterCache.distThreshold = _program->uniformLocation("distThreshold"); _uniformCacheTM.renderedTexture = _programTM->uniformLocation("renderedTexture"); // Find out how much GPU memory this computer has (Nvidia cards). GLint nDedicatedVidMemoryInKB = 0; glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nDedicatedVidMemoryInKB); GLint nTotalMemoryInKB = 0; glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &nTotalMemoryInKB); GLint nCurrentAvailMemoryInKB = 0; glGetIntegerv( GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &nCurrentAvailMemoryInKB ); LDEBUG(fmt::format( "nDedicatedVidMemoryKB: {} - nTotalMemoryKB: {} - nCurrentAvailMemoryKB: {}", nDedicatedVidMemoryInKB, nTotalMemoryInKB, nCurrentAvailMemoryInKB )); // Set ceiling for video memory to use in streaming. float dedicatedVidMem = static_cast( static_cast(nDedicatedVidMemoryInKB) * 1024 ); // TODO: Need to fix what happens if we can't query! For now use 2 GB by default. _gpuMemoryBudgetInBytes = dedicatedVidMem > 0 ? static_cast(dedicatedVidMem * _maxGpuMemoryPercent) : 2147483648; // Set ceiling for how much of the installed CPU RAM to use for streaming long long installedRam = static_cast(CpuCap.installedMainMemory()) * 1024 * 1024; // TODO: What to do if we can't query? As for now we use 4 GB by default. _cpuRamBudgetInBytes = installedRam > 0 ? static_cast(static_cast(installedRam) * _maxCpuMemoryPercent) : 4294967296; _cpuRamBudgetProperty.setMaxValue(static_cast(_cpuRamBudgetInBytes)); LDEBUG(fmt::format( "GPU Memory Budget (bytes): {} - CPU RAM Budget (bytes): {}", _gpuMemoryBudgetInBytes, _cpuRamBudgetInBytes )); } void RenderableGaiaStars::deinitializeGL() { if (_vboPos != 0) { glDeleteBuffers(1, &_vboPos); _vboPos = 0; } if (_vboCol != 0) { glDeleteBuffers(1, &_vboCol); _vboCol = 0; } if (_vboVel != 0) { glDeleteBuffers(1, &_vboVel); _vboVel = 0; } if (_ssboIdx != 0) { glDeleteBuffers(1, &_ssboIdx); _ssboIdx = 0; glDeleteBuffers(1, &_ssboData); _ssboData = 0; } if (_vao != 0) { glDeleteVertexArrays(1, &_vao); _vao = 0; } if (_vaoEmpty != 0) { glDeleteVertexArrays(1, &_vaoEmpty); _vaoEmpty = 0; } glDeleteBuffers(1, &_vboQuad); _vboQuad = 0; glDeleteVertexArrays(1, &_vaoQuad); _vaoQuad = 0; glDeleteFramebuffers(1, &_fbo); _fbo = 0; _dataFile = nullptr; _pointSpreadFunctionTexture = nullptr; _colorTexture = nullptr; _fboTexture = nullptr; if (_program) { global::renderEngine->removeRenderProgram(_program.get()); _program = nullptr; } if (_programTM) { global::renderEngine->removeRenderProgram(_programTM.get()); _programTM = nullptr; } } void RenderableGaiaStars::render(const RenderData& data, RendererTasks&) { checkGlErrors("Before render"); // Save current FBO. GLint defaultFbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFbo); glm::dmat4 model = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * glm::dmat4(data.modelTransform.rotation) * glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)); float viewScaling = data.camera.scaling(); glm::dmat4 view = data.camera.combinedViewMatrix(); glm::dmat4 projection = data.camera.projectionMatrix(); glm::dmat4 modelViewProjMat = projection * view * model; glm::vec2 screenSize = glm::vec2(global::renderEngine->renderingResolution()); // Wait until camera has stabilized before we traverse the Octree/stream from files. const double rotationDiff = abs(length(_previousCameraRotation) - length(data.camera.rotationQuaternion())); if (_firstDrawCalls && rotationDiff > 1e-10) { _previousCameraRotation = data.camera.rotationQuaternion(); return; } else { _firstDrawCalls = false; } // Update which nodes that are stored in memory as the camera moves around // (if streaming) if (_fileReaderOption == gaia::FileReaderOption::StreamOctree) { glm::dvec3 cameraPos = data.camera.positionVec3(); size_t chunkSizeBytes = _chunkSize * sizeof(GLfloat); _octreeManager.fetchSurroundingNodes(cameraPos, chunkSizeBytes, _additionalNodes); // Update CPU Budget property. _cpuRamBudgetProperty = static_cast(_octreeManager.cpuRamBudget()); } // Traverse Octree and build a map with new nodes to render, uses mvp matrix to decide const int renderOption = _renderMode; int deltaStars = 0; std::map> updateData = _octreeManager.traverseData( modelViewProjMat, screenSize, deltaStars, gaia::RenderMode(renderOption), _lodPixelThreshold ); // Update number of rendered stars. _nStarsToRender += deltaStars; _nRenderedStars = _nStarsToRender; // Update GPU Stream Budget property. _gpuStreamBudgetProperty = static_cast(_octreeManager.numFreeSpotsInBuffer()); int nChunksToRender = static_cast(_octreeManager.biggestChunkIndexInUse()); int maxStarsPerNode = static_cast(_octreeManager.maxStarsPerNode()); int valuesPerStar = static_cast(_nRenderValuesPerStar); // Switch rendering technique depending on user-defined shader option. const int shaderOption = _shaderOption; if (shaderOption == gaia::ShaderOption::BillboardSSBO || shaderOption == gaia::ShaderOption::PointSSBO || shaderOption == gaia::ShaderOption::BillboardSSBONoFBO) { #ifndef __APPLE__ //------------------------ RENDER WITH SSBO --------------------------- // Update SSBO Index array with accumulated stars in all chunks. glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboIdx); int lastValue = _accumulatedIndices.back(); _accumulatedIndices.resize(nChunksToRender + 1, lastValue); // Update vector with accumulated indices. for (const auto& [offset, subData] : updateData) { int newValue = static_cast(subData.size() / _nRenderValuesPerStar) + _accumulatedIndices[offset]; int changeInValue = newValue - _accumulatedIndices[offset + 1]; _accumulatedIndices[offset + 1] = newValue; // Propagate change. for (int i = offset + 1; i < nChunksToRender; ++i) { _accumulatedIndices[i + 1] += changeInValue; } } // Fix number of stars rendered if it doesn't correspond to our buffers. if (_accumulatedIndices.back() != _nStarsToRender) { _nStarsToRender = _accumulatedIndices.back(); _nRenderedStars = _nStarsToRender; } size_t indexBufferSize = _accumulatedIndices.size() * sizeof(GLint); // Update SSBO Index (stars per chunk). glBufferData( GL_SHADER_STORAGE_BUFFER, indexBufferSize, _accumulatedIndices.data(), GL_STREAM_DRAW ); // Use orphaning strategy for data SSBO. glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboData); glBufferData( GL_SHADER_STORAGE_BUFFER, _maxStreamingBudgetInBytes, nullptr, GL_STREAM_DRAW ); // Update SSBO with one insert per chunk/node. // The key in map holds the offset index. for (const auto& [offset, subData] : updateData) { // We don't need to fill chunk with zeros for SSBOs! // Just check if we have any values to update. if (!subData.empty()) { glBufferSubData( GL_SHADER_STORAGE_BUFFER, offset * _chunkSize * sizeof(GLfloat), subData.size() * sizeof(GLfloat), subData.data() ); } } glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); #endif // !__APPLE__ } else { //---------------------- RENDER WITH VBO ----------------------------- // Update VBOs with new nodes. // This will overwrite old data that's not visible anymore as well. glBindVertexArray(_vao); // Always update Position VBO. glBindBuffer(GL_ARRAY_BUFFER, _vboPos); float posMemoryShare = static_cast(PositionSize) / _nRenderValuesPerStar; size_t posChunkSize = maxStarsPerNode * PositionSize; long long posStreamingBudget = static_cast( _maxStreamingBudgetInBytes * posMemoryShare ); // Use buffer orphaning to update a subset of total data. glBufferData( GL_ARRAY_BUFFER, posStreamingBudget, nullptr, GL_STREAM_DRAW ); // Update buffer with one insert per chunk/node. //The key in map holds the offset index. for (const auto& [offset, subData] : updateData) { // Fill chunk by appending zeroes so we overwrite possible earlier values. // Only required when removing nodes because chunks are filled up in octree // fetch on add. std::vector vectorData(subData.begin(), subData.end()); vectorData.resize(posChunkSize, 0.f); glBufferSubData( GL_ARRAY_BUFFER, offset * posChunkSize * sizeof(GLfloat), posChunkSize * sizeof(GLfloat), vectorData.data() ); } // Update Color VBO if render option is 'Color' or 'Motion'. if (renderOption != gaia::RenderMode::Static) { glBindBuffer(GL_ARRAY_BUFFER, _vboCol); float colMemoryShare = static_cast(ColorSize) / _nRenderValuesPerStar; size_t colChunkSize = maxStarsPerNode * ColorSize; long long colStreamingBudget = static_cast( _maxStreamingBudgetInBytes * colMemoryShare ); // Use buffer orphaning to update a subset of total data. glBufferData( GL_ARRAY_BUFFER, colStreamingBudget, nullptr, GL_STREAM_DRAW ); // Update buffer with one insert per chunk/node. //The key in map holds the offset index. for (const auto& [offset, subData] : updateData) { // Fill chunk by appending zeroes so we overwrite possible earlier values. std::vector vectorData(subData.begin(), subData.end()); vectorData.resize(posChunkSize + colChunkSize, 0.f); glBufferSubData( GL_ARRAY_BUFFER, offset * colChunkSize * sizeof(GLfloat), colChunkSize * sizeof(GLfloat), vectorData.data() + posChunkSize ); } // Update Velocity VBO if specified. if (renderOption == gaia::RenderMode::Motion) { glBindBuffer(GL_ARRAY_BUFFER, _vboVel); float velMemoryShare = static_cast(VelocitySize) / _nRenderValuesPerStar; size_t velChunkSize = maxStarsPerNode * VelocitySize; long long velStreamingBudget = static_cast( _maxStreamingBudgetInBytes * velMemoryShare ); // Use buffer orphaning to update a subset of total data. glBufferData( GL_ARRAY_BUFFER, velStreamingBudget, nullptr, GL_STREAM_DRAW ); // Update buffer with one insert per chunk/node. //The key in map holds the offset index. for (const auto& [offset, subData] : updateData) { // Fill chunk by appending zeroes. std::vector vectorData(subData.begin(), subData.end()); vectorData.resize(_chunkSize, 0.f); glBufferSubData( GL_ARRAY_BUFFER, offset * velChunkSize * sizeof(GLfloat), velChunkSize * sizeof(GLfloat), vectorData.data() + posChunkSize + colChunkSize ); } } } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } checkGlErrors("After buffer updates"); // Activate shader program and send uniforms. glBlendFunc(GL_SRC_ALPHA, GL_ONE); glDepthMask(false); _program->activate(); _program->setUniform(_uniformCache.model, model); _program->setUniform(_uniformCache.view, view); _program->setUniform(_uniformCache.projection, projection); _program->setUniform( _uniformCache.time, static_cast(data.time.j2000Seconds()) ); _program->setUniform(_uniformCache.renderOption, _renderMode); _program->setUniform(_uniformCache.viewScaling, viewScaling); _program->setUniform(_uniformCache.cutOffThreshold, _cutOffThreshold); _program->setUniform(_uniformCache.luminosityMultiplier, _luminosityMultiplier); // Send filterValues. _program->setUniform(_uniformFilterCache.posXThreshold, _posXThreshold); _program->setUniform(_uniformFilterCache.posYThreshold, _posYThreshold); _program->setUniform(_uniformFilterCache.posZThreshold, _posZThreshold); _program->setUniform(_uniformFilterCache.gMagThreshold, _gMagThreshold); _program->setUniform(_uniformFilterCache.bpRpThreshold, _bpRpThreshold); _program->setUniform(_uniformFilterCache.distThreshold, _distThreshold); ghoul::opengl::TextureUnit colorUnit; if (_colorTexture) { colorUnit.activate(); _colorTexture->bind(); _program->setUniform(_uniformCache.colorTexture, colorUnit); } // Specify how many stars we will render. Will be overwritten if rendering billboards GLsizei nShaderCalls = _nStarsToRender; ghoul::opengl::TextureUnit psfUnit; switch (shaderOption) { case gaia::ShaderOption::PointSSBO: _program->setUniform(_uniformCache.maxStarsPerNode, maxStarsPerNode); _program->setUniform(_uniformCache.valuesPerStar, valuesPerStar); _program->setUniform(_uniformCache.nChunksToRender, nChunksToRender); break; case gaia::ShaderOption::PointVBO: // Specify how many potential stars we have to render. nShaderCalls = maxStarsPerNode * nChunksToRender; break; case gaia::ShaderOption::BillboardSSBO: case gaia::ShaderOption::BillboardSSBONoFBO: _program->setUniform( _uniformCache.cameraPos, data.camera.positionVec3() ); _program->setUniform( _uniformCache.cameraLookUp, data.camera.lookUpVectorWorldSpace() ); _program->setUniform(_uniformCache.maxStarsPerNode, maxStarsPerNode); _program->setUniform(_uniformCache.valuesPerStar, valuesPerStar); _program->setUniform(_uniformCache.nChunksToRender, nChunksToRender); _program->setUniform(_uniformCache.closeUpBoostDist, _closeUpBoostDist * static_cast(distanceconstants::Parsec) ); _program->setUniform(_uniformCache.billboardSize, _billboardSize); _program->setUniform(_uniformCache.magnitudeBoost, _magnitudeBoost); _program->setUniform(_uniformCache.sharpness, _sharpness); psfUnit.activate(); _pointSpreadFunctionTexture->bind(); _program->setUniform(_uniformCache.psfTexture, psfUnit); break; case gaia::ShaderOption::BillboardVBO: _program->setUniform( _uniformCache.cameraPos, data.camera.positionVec3() ); _program->setUniform( _uniformCache.cameraLookUp, data.camera.lookUpVectorWorldSpace() ); _program->setUniform(_uniformCache.closeUpBoostDist, _closeUpBoostDist * static_cast(distanceconstants::Parsec) ); _program->setUniform(_uniformCache.billboardSize, _billboardSize); _program->setUniform(_uniformCache.magnitudeBoost, _magnitudeBoost); _program->setUniform(_uniformCache.sharpness, _sharpness); psfUnit.activate(); _pointSpreadFunctionTexture->bind(); _program->setUniform(_uniformCache.psfTexture, psfUnit); // Specify how many potential stars we have to render. nShaderCalls = maxStarsPerNode * nChunksToRender; break; } if (shaderOption != gaia::ShaderOption::BillboardSSBONoFBO) { // Render to FBO. glBindFramebuffer(GL_FRAMEBUFFER, _fbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } //glEnable(GL_PROGRAM_POINT_SIZE); // A non-zero named vao MUST ALWAYS be bound! if (_useVBO) { glBindVertexArray(_vao); } else { glBindVertexArray(_vaoEmpty); } glDrawArrays(GL_POINTS, 0, nShaderCalls); glBindVertexArray(0); //glDisable(GL_PROGRAM_POINT_SIZE); _program->deactivate(); if (shaderOption != gaia::ShaderOption::BillboardSSBONoFBO) { // Use ToneMapping shaders and render to default FBO again! _programTM->activate(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFbo); ghoul::opengl::TextureUnit fboTexUnit; if (_fboTexture) { fboTexUnit.activate(); _fboTexture->bind(); _programTM->setUniform(_uniformCacheTM.renderedTexture, fboTexUnit); } if (shaderOption == gaia::ShaderOption::PointSSBO || shaderOption == gaia::ShaderOption::PointVBO) { _programTM->setUniform(_uniformCacheTM.screenSize, screenSize); _programTM->setUniform(_uniformCacheTM.filterSize, _tmPointFilterSize); _programTM->setUniform(_uniformCacheTM.sigma, _tmPointSigma); _programTM->setUniform(_uniformCacheTM.projection, projection); _programTM->setUniform( _uniformCacheTM.pixelWeightThreshold, _tmPointPixelWeightThreshold ); } glBindVertexArray(_vaoQuad); glDrawArrays(GL_TRIANGLES, 0, 6); // 2 triangles glBindVertexArray(0); _programTM->deactivate(); } glDepthMask(true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); checkGlErrors("After render"); } void RenderableGaiaStars::checkGlErrors(const std::string& identifier) const { if (_reportGlErrors) { GLenum error = glGetError(); if (error != GL_NO_ERROR) { switch (error) { case GL_INVALID_ENUM: LINFO(identifier + " - GL_INVALID_ENUM"); break; case GL_INVALID_VALUE: LINFO(identifier + " - GL_INVALID_VALUE"); break; case GL_INVALID_OPERATION: LINFO(identifier + " - GL_INVALID_OPERATION"); break; case GL_INVALID_FRAMEBUFFER_OPERATION: LINFO(identifier + " - GL_INVALID_FRAMEBUFFER_OPERATION"); break; case GL_OUT_OF_MEMORY: LINFO(identifier + " - GL_OUT_OF_MEMORY"); break; default: LINFO(identifier + " - Unknown error"); break; } } } } void RenderableGaiaStars::update(const UpdateData&) { const int shaderOption = _shaderOption; const int renderOption = _renderMode; // Don't update anything if we are in the middle of a rebuild. if (_octreeManager.isRebuildOngoing()) { return; } if (_dataIsDirty) { LDEBUG("Regenerating data"); // Reload data file. This may reconstruct the Octree as well. bool success = readDataFile(); if (!success) { throw ghoul::RuntimeError("Error loading Gaia Star data"); } _dataIsDirty = false; // Make sure we regenerate buffers if data has reloaded! _buffersAreDirty = true; } if (_program->isDirty() || _shadersAreDirty) { switch (shaderOption) { case gaia::ShaderOption::PointSSBO: { #ifndef __APPLE__ std::unique_ptr program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") ); if (_program) { global::renderEngine->removeRenderProgram(_program.get()); } _program = std::move(program); _uniformCache.maxStarsPerNode = _program->uniformLocation( "maxStarsPerNode" ); _uniformCache.valuesPerStar = _program->uniformLocation( "valuesPerStar" ); _uniformCache.nChunksToRender = _program->uniformLocation( "nChunksToRender" ); // If rebuild was triggered by switching ShaderOption then ssboBinding may // not have been initialized yet. Binding will happen in later in // buffersAredirty. if (!_shadersAreDirty) { _program->setSsboBinding( "ssbo_idx_data", _ssboIdxBinding->bindingNumber() ); _program->setSsboBinding( "ssbo_comb_data", _ssboDataBinding->bindingNumber() ); } if (hasProperty(&_magnitudeBoost)) { removeProperty(_magnitudeBoost); } if (hasProperty(&_sharpness)) { removeProperty(_sharpness); } if (hasProperty(&_billboardSize)) { removeProperty(_billboardSize); } if (hasProperty(&_closeUpBoostDist)) { removeProperty(_closeUpBoostDist); } if (hasProperty(&_pointSpreadFunctionTexturePath)) { removeProperty(_pointSpreadFunctionTexturePath); } if (!hasProperty(&_tmPointFilterSize)) { addProperty(_tmPointFilterSize); } if (!hasProperty(&_tmPointSigma)) { addProperty(_tmPointSigma); } if (!hasProperty(&_tmPointPixelWeightThreshold)) { addProperty(_tmPointPixelWeightThreshold); } #endif // !__APPLE__ break; } case gaia::ShaderOption::PointVBO: { std::unique_ptr program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_point_ge.glsl") ); if (_program) { global::renderEngine->removeRenderProgram(_program.get()); } _program = std::move(program); if (hasProperty(&_magnitudeBoost)) { removeProperty(_magnitudeBoost); } if (hasProperty(&_sharpness)) { removeProperty(_sharpness); } if (hasProperty(&_billboardSize)) { removeProperty(_billboardSize); } if (hasProperty(&_closeUpBoostDist)) { removeProperty(_closeUpBoostDist); } if (hasProperty(&_pointSpreadFunctionTexturePath)) { removeProperty(_pointSpreadFunctionTexturePath); } if (!hasProperty(&_tmPointFilterSize)) { addProperty(_tmPointFilterSize); } if (!hasProperty(&_tmPointSigma)) { addProperty(_tmPointSigma); } if (!hasProperty(&_tmPointPixelWeightThreshold)) { addProperty(_tmPointPixelWeightThreshold); } break; } case gaia::ShaderOption::BillboardSSBO: case gaia::ShaderOption::BillboardSSBONoFBO: { #ifndef __APPLE__ std::unique_ptr program; if (shaderOption == gaia::ShaderOption::BillboardSSBO) { program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") ); } else { program = global::renderEngine->buildRenderProgram("GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_ssbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_nofbo_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") ); } if (_program) { global::renderEngine->removeRenderProgram(_program.get()); } _program = std::move(program); _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); _uniformCache.magnitudeBoost = _program->uniformLocation( "magnitudeBoost" ); _uniformCache.sharpness = _program->uniformLocation("sharpness"); _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); _uniformCache.closeUpBoostDist = _program->uniformLocation( "closeUpBoostDist" ); _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); _uniformCache.maxStarsPerNode = _program->uniformLocation( "maxStarsPerNode" ); _uniformCache.valuesPerStar = _program->uniformLocation("valuesPerStar"); _uniformCache.nChunksToRender = _program->uniformLocation( "nChunksToRender" ); // If rebuild was triggered by switching ShaderOption then ssboBinding // may not have been initialized yet. Binding will happen in later in // buffersAredirty. if (!_shadersAreDirty) { _program->setSsboBinding( "ssbo_idx_data", _ssboIdxBinding->bindingNumber() ); _program->setSsboBinding( "ssbo_comb_data", _ssboDataBinding->bindingNumber() ); } if (!hasProperty(&_magnitudeBoost)) { addProperty(_magnitudeBoost); } if (!hasProperty(&_sharpness)) { addProperty(_sharpness); } if (!hasProperty(&_billboardSize)) { addProperty(_billboardSize); } if (!hasProperty(&_closeUpBoostDist)) { addProperty(_closeUpBoostDist); } if (!hasProperty(&_pointSpreadFunctionTexturePath)) { addProperty(_pointSpreadFunctionTexturePath); } if (hasProperty(&_tmPointFilterSize)) { removeProperty(_tmPointFilterSize); } if (hasProperty(&_tmPointSigma)) { removeProperty(_tmPointSigma); } if (hasProperty(&_tmPointPixelWeightThreshold)) { removeProperty(_tmPointPixelWeightThreshold); } #endif // !__APPLE__ break; } case gaia::ShaderOption::BillboardVBO: { std::unique_ptr program = ghoul::opengl::ProgramObject::Build( "GaiaStar", absPath("${MODULE_GAIA}/shaders/gaia_vbo_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_fs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_billboard_ge.glsl") ); if (_program) { global::renderEngine->removeRenderProgram(_program.get()); } _program = std::move(program); _uniformCache.cameraPos = _program->uniformLocation("cameraPos"); _uniformCache.cameraLookUp = _program->uniformLocation("cameraLookUp"); _uniformCache.magnitudeBoost = _program->uniformLocation( "magnitudeBoost" ); _uniformCache.sharpness = _program->uniformLocation("sharpness"); _uniformCache.billboardSize = _program->uniformLocation("billboardSize"); _uniformCache.closeUpBoostDist = _program->uniformLocation( "closeUpBoostDist" ); _uniformCache.psfTexture = _program->uniformLocation("psfTexture"); if (!hasProperty(&_magnitudeBoost)) { addProperty(_magnitudeBoost); } if (!hasProperty(&_sharpness)) { addProperty(_sharpness); } if (!hasProperty(&_billboardSize)) { addProperty(_billboardSize); } if (!hasProperty(&_closeUpBoostDist)) { addProperty(_closeUpBoostDist); } if (!hasProperty(&_pointSpreadFunctionTexturePath)) { addProperty(_pointSpreadFunctionTexturePath); } if (hasProperty(&_tmPointFilterSize)) { removeProperty(_tmPointFilterSize); } if (hasProperty(&_tmPointSigma)) { removeProperty(_tmPointSigma); } if (hasProperty(&_tmPointPixelWeightThreshold)) { removeProperty(_tmPointPixelWeightThreshold); } break; } } // Common uniforms for all shaders: _uniformCache.model = _program->uniformLocation("model"); _uniformCache.view = _program->uniformLocation("view"); _uniformCache.projection = _program->uniformLocation("projection"); _uniformCache.time = _program->uniformLocation("time"); _uniformCache.renderOption = _program->uniformLocation("renderOption"); _uniformCache.viewScaling = _program->uniformLocation("viewScaling"); _uniformCache.cutOffThreshold = _program->uniformLocation("cutOffThreshold"); _uniformCache.luminosityMultiplier = _program->uniformLocation( "luminosityMultiplier" ); _uniformCache.colorTexture = _program->uniformLocation("colorTexture"); // Filter uniforms: _uniformFilterCache.posXThreshold = _program->uniformLocation("posXThreshold"); _uniformFilterCache.posYThreshold = _program->uniformLocation("posYThreshold"); _uniformFilterCache.posZThreshold = _program->uniformLocation("posZThreshold"); _uniformFilterCache.gMagThreshold = _program->uniformLocation("gMagThreshold"); _uniformFilterCache.bpRpThreshold = _program->uniformLocation("bpRpThreshold"); _uniformFilterCache.distThreshold = _program->uniformLocation("distThreshold"); } if (_programTM->isDirty() || _shadersAreDirty) { switch (shaderOption) { case gaia::ShaderOption::PointSSBO: case gaia::ShaderOption::PointVBO: { std::unique_ptr programTM = global::renderEngine->buildRenderProgram( "ToneMapping", absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl"), absPath("${MODULE_GAIA}/shaders/gaia_tonemapping_point_fs.glsl") ); if (_programTM) { global::renderEngine->removeRenderProgram(_programTM.get()); } _programTM = std::move(programTM); _uniformCacheTM.screenSize = _programTM->uniformLocation("screenSize"); _uniformCacheTM.filterSize = _programTM->uniformLocation("filterSize"); _uniformCacheTM.sigma = _programTM->uniformLocation("sigma"); _uniformCacheTM.projection = _programTM->uniformLocation("projection"); _uniformCacheTM.pixelWeightThreshold = _programTM->uniformLocation( "pixelWeightThreshold" ); break; } case gaia::ShaderOption::BillboardSSBO: case gaia::ShaderOption::BillboardVBO: { std::filesystem::path vs = absPath( "${MODULE_GAIA}/shaders/gaia_tonemapping_vs.glsl" ); std::filesystem::path fs = absPath( "${MODULE_GAIA}/shaders/gaia_tonemapping_billboard_fs.glsl" ); std::unique_ptr programTM = global::renderEngine->buildRenderProgram("ToneMapping", vs, fs); if (_programTM) { global::renderEngine->removeRenderProgram(_programTM.get()); } _programTM = std::move(programTM); break; } } // Common uniforms: _uniformCacheTM.renderedTexture = _programTM->uniformLocation("renderedTexture"); _shadersAreDirty = false; } if (_buffersAreDirty) { LDEBUG("Regenerating buffers"); // Set values per star slice depending on render option. if (renderOption == gaia::RenderMode::Static) { _nRenderValuesPerStar = PositionSize; } else if (renderOption == gaia::RenderMode::Color) { _nRenderValuesPerStar = PositionSize + ColorSize; } else { // (renderOption == gaia::RenderOption::Motion) _nRenderValuesPerStar = PositionSize + ColorSize + VelocitySize; } // Calculate memory budgets. _chunkSize = _octreeManager.maxStarsPerNode() * _nRenderValuesPerStar; long long totalChunkSizeInBytes = _octreeManager.totalNodes() * _chunkSize * sizeof(GLfloat); _maxStreamingBudgetInBytes = std::min( totalChunkSizeInBytes, _gpuMemoryBudgetInBytes ); long long maxNodesInStream = _maxStreamingBudgetInBytes / (_chunkSize * sizeof(GLfloat)); _gpuStreamBudgetProperty.setMaxValue(static_cast(maxNodesInStream)); bool datasetFitInMemory = static_cast(_totalDatasetSizeInBytes) < (_cpuRamBudgetInBytes * 0.9f); if (!datasetFitInMemory && !hasProperty(&_additionalNodes)) { addProperty(_additionalNodes); } else if (hasProperty(&_additionalNodes)) { removeProperty(_additionalNodes); } LDEBUG(fmt::format( "Chunk size: {} - Max streaming budget (bytes): {} - Max nodes in stream: {}", _chunkSize, _maxStreamingBudgetInBytes, maxNodesInStream )); // ------------------ RENDER WITH SSBO ----------------------- if (shaderOption == gaia::ShaderOption::BillboardSSBO || shaderOption == gaia::ShaderOption::PointSSBO || shaderOption == gaia::ShaderOption::BillboardSSBONoFBO) { #ifndef __APPLE__ _useVBO = false; // Trigger a rebuild of buffer data from octree. // With SSBO we won't fill the chunks. _octreeManager.initBufferIndexStack( maxNodesInStream, _useVBO, datasetFitInMemory ); _nStarsToRender = 0; // Generate SSBO Buffers and bind them. if (_vaoEmpty == 0) { glGenVertexArrays(1, &_vaoEmpty); LDEBUG(fmt::format("Generating Empty Vertex Array id '{}'", _vaoEmpty)); } if (_ssboIdx == 0) { glGenBuffers(1, &_ssboIdx); LDEBUG(fmt::format( "Generating Index Shader Storage Buffer Object id '{}'", _ssboIdx )); } if (_ssboData == 0) { glGenBuffers(1, &_ssboData); LDEBUG(fmt::format( "Generating Data Shader Storage Buffer Object id '{}'", _ssboData )); } // Bind SSBO blocks to our shader positions. // Number of stars per chunk (a.k.a. Index). glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboIdx); _ssboIdxBinding = std::make_unique >(); glBindBufferBase( GL_SHADER_STORAGE_BUFFER, _ssboIdxBinding->bindingNumber(), _ssboIdx ); _program->setSsboBinding("ssbo_idx_data", _ssboIdxBinding->bindingNumber()); // Combined SSBO with all data. glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboData); _ssboDataBinding = std::make_unique >(); glBindBufferBase( GL_SHADER_STORAGE_BUFFER, _ssboDataBinding->bindingNumber(), _ssboData ); _program->setSsboBinding("ssbo_comb_data", _ssboDataBinding->bindingNumber()); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Deallocate VBO Buffers if any existed. if (_vboPos != 0) { glBindBuffer(GL_ARRAY_BUFFER, _vboPos); glBufferData( GL_ARRAY_BUFFER, 0, nullptr, GL_STREAM_DRAW ); } if (_vboCol != 0) { glBindBuffer(GL_ARRAY_BUFFER, _vboCol); glBufferData( GL_ARRAY_BUFFER, 0, nullptr, GL_STREAM_DRAW ); } if (_vboVel != 0) { glBindBuffer(GL_ARRAY_BUFFER, _vboVel); glBufferData( GL_ARRAY_BUFFER, 0, nullptr, GL_STREAM_DRAW ); } glBindBuffer(GL_ARRAY_BUFFER, 0); #endif // !__APPLE__ } else { // ------------------ RENDER WITH VBO ----------------------- _useVBO = true; // Trigger a rebuild of buffer data from octree. // With VBO we will fill the chunks. _octreeManager.initBufferIndexStack( maxNodesInStream, _useVBO, datasetFitInMemory ); _nStarsToRender = 0; // Generate VAO and VBOs if (_vao == 0) { glGenVertexArrays(1, &_vao); LDEBUG(fmt::format("Generating Vertex Array id '{}'", _vao)); } if (_vboPos == 0) { glGenBuffers(1, &_vboPos); LDEBUG(fmt::format( "Generating Position Vertex Buffer Object id '{}'", _vboPos )); } if (_vboCol == 0) { glGenBuffers(1, &_vboCol); LDEBUG(fmt::format( "Generating Color Vertex Buffer Object id '{}'", _vboCol )); } if (_vboVel == 0) { glGenBuffers(1, &_vboVel); LDEBUG(fmt::format( "Generating Velocity Vertex Buffer Object id '{}'", _vboVel )); } // Bind our different VBOs to our vertex array layout. glBindVertexArray(_vao); switch (renderOption) { case gaia::RenderMode::Static: { glBindBuffer(GL_ARRAY_BUFFER, _vboPos); GLint positionAttrib = _program->attributeLocation("in_position"); glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer( positionAttrib, PositionSize, GL_FLOAT, GL_FALSE, 0, nullptr ); break; } case gaia::RenderMode::Color: { glBindBuffer(GL_ARRAY_BUFFER, _vboPos); GLint positionAttrib = _program->attributeLocation("in_position"); glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer( positionAttrib, PositionSize, GL_FLOAT, GL_FALSE, 0, nullptr ); glBindBuffer(GL_ARRAY_BUFFER, _vboCol); GLint brightnessAttrib = _program->attributeLocation("in_brightness"); glEnableVertexAttribArray(brightnessAttrib); glVertexAttribPointer( brightnessAttrib, ColorSize, GL_FLOAT, GL_FALSE, 0, nullptr ); break; } case gaia::RenderMode::Motion: { glBindBuffer(GL_ARRAY_BUFFER, _vboPos); GLint positionAttrib = _program->attributeLocation("in_position"); glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer( positionAttrib, PositionSize, GL_FLOAT, GL_FALSE, 0, nullptr ); glBindBuffer(GL_ARRAY_BUFFER, _vboCol); GLint brightnessAttrib = _program->attributeLocation("in_brightness"); glEnableVertexAttribArray(brightnessAttrib); glVertexAttribPointer( brightnessAttrib, ColorSize, GL_FLOAT, GL_FALSE, 0, nullptr ); glBindBuffer(GL_ARRAY_BUFFER, _vboVel); GLint velocityAttrib = _program->attributeLocation("in_velocity"); glEnableVertexAttribArray(velocityAttrib); glVertexAttribPointer( velocityAttrib, VelocitySize, GL_FLOAT, GL_FALSE, 0, nullptr ); break; } } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); #ifndef __APPLE__ // Deallocate SSBO buffers if they existed. if (_ssboIdx != 0) { glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboIdx); glBufferData( GL_SHADER_STORAGE_BUFFER, 0, nullptr, GL_STREAM_DRAW ); } if (_ssboData != 0) { glBindBuffer(GL_SHADER_STORAGE_BUFFER, _ssboData); glBufferData( GL_SHADER_STORAGE_BUFFER, 0, nullptr, GL_STREAM_DRAW ); } glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); #endif //!__APPLE__ } // Generate VAO and VBO for Quad. if (_vaoQuad == 0) { glGenVertexArrays(1, &_vaoQuad); LDEBUG(fmt::format("Generating Quad Vertex Array id '{}'", _vaoQuad)); } if (_vboQuad == 0) { glGenBuffers(1, &_vboQuad); LDEBUG(fmt::format("Generating Quad Vertex Buffer Object id '{}'", _vboQuad)); } // Bind VBO and VAO for Quad rendering. glBindVertexArray(_vaoQuad); glBindBuffer(GL_ARRAY_BUFFER, _vboQuad); // Quad for fullscreen. static constexpr GLfloat vbo_quad_data[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }; glBufferData( GL_ARRAY_BUFFER, sizeof(vbo_quad_data), vbo_quad_data, GL_STATIC_DRAW ); GLint tmPositionAttrib = _programTM->attributeLocation("in_position"); glEnableVertexAttribArray(tmPositionAttrib); glVertexAttribPointer( tmPositionAttrib, 3, GL_FLOAT, GL_FALSE, 0, nullptr ); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // Generate Framebuffer Object and Texture. if (_fbo == 0) { glGenFramebuffers(1, &_fbo); LDEBUG(fmt::format("Generating Framebuffer Object id '{}'", _fbo)); } if (!_fboTexture) { // Generate a new texture and attach it to our FBO. glm::vec2 screenSize = glm::vec2(global::renderEngine->renderingResolution()); _fboTexture = std::make_unique( glm::uvec3(screenSize, 1), GL_TEXTURE_2D, ghoul::opengl::Texture::Format::RGBA, GL_RGBA32F, GL_FLOAT ); _fboTexture->uploadTexture(); LDEBUG("Generating Framebuffer Texture"); } // Bind render texture to FBO. glBindFramebuffer(GL_FRAMEBUFFER, _fbo); glBindTexture(GL_TEXTURE_2D, *_fboTexture); glFramebufferTexture( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *_fboTexture, 0 ); GLenum textureBuffers[1] = { GL_COLOR_ATTACHMENT0 }; glDrawBuffers(1, textureBuffers); // Check that our framebuffer is ok. if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LERROR("Error when generating GaiaStar Framebuffer"); } glBindFramebuffer(GL_FRAMEBUFFER, 0); _buffersAreDirty = false; } if (_pointSpreadFunctionTextureIsDirty) { LDEBUG("Reloading Point Spread Function texture"); _pointSpreadFunctionTexture = nullptr; if (!_pointSpreadFunctionTexturePath.value().empty()) { _pointSpreadFunctionTexture = ghoul::io::TextureReader::ref().loadTexture( absPath(_pointSpreadFunctionTexturePath).string(), 2 ); if (_pointSpreadFunctionTexture) { LDEBUG(fmt::format( "Loaded texture from {}", absPath(_pointSpreadFunctionTexturePath) )); _pointSpreadFunctionTexture->uploadTexture(); } _pointSpreadFunctionTexture->setFilter( ghoul::opengl::Texture::FilterMode::AnisotropicMipMap ); _pointSpreadFunctionFile = std::make_unique( _pointSpreadFunctionTexturePath.value() ); _pointSpreadFunctionFile->setCallback( [this]() { _pointSpreadFunctionTextureIsDirty = true; } ); } _pointSpreadFunctionTextureIsDirty = false; } if (_colorTextureIsDirty) { LDEBUG("Reloading Color Texture"); _colorTexture = nullptr; if (!_colorTexturePath.value().empty()) { _colorTexture = ghoul::io::TextureReader::ref().loadTexture( absPath(_colorTexturePath).string(), 1 ); if (_colorTexture) { LDEBUG(fmt::format("Loaded texture from {}", absPath(_colorTexturePath))); _colorTexture->uploadTexture(); } _colorTextureFile = std::make_unique( _colorTexturePath.value() ); _colorTextureFile->setCallback([this]() { _colorTextureIsDirty = true; }); } _colorTextureIsDirty = false; } if (global::windowDelegate->windowHasResized()) { // Update FBO texture resolution if we haven't already. glm::vec2 screenSize = glm::vec2(global::renderEngine->renderingResolution()); const bool hasChanged = glm::any( glm::notEqual(_fboTexture->dimensions(), glm::uvec3(screenSize, 1)) ); if (hasChanged) { _fboTexture = std::make_unique( glm::uvec3(screenSize, 1), GL_TEXTURE_2D, ghoul::opengl::Texture::Format::RGBA, GL_RGBA32F, GL_FLOAT ); _fboTexture->uploadTexture(); LDEBUG("Re-Generating Gaia Framebuffer Texture"); glBindFramebuffer(GL_FRAMEBUFFER, _fbo); glBindTexture(GL_TEXTURE_2D, *_fboTexture); glFramebufferTexture( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *_fboTexture, 0 ); GLenum textureBuffers[1] = { GL_COLOR_ATTACHMENT0 }; glDrawBuffers(1, textureBuffers); // Check that our framebuffer is ok. if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LERROR("Error when re-generating GaiaStar Framebuffer"); } glBindFramebuffer(GL_FRAMEBUFFER, 0); } } } bool RenderableGaiaStars::readDataFile() { const int fileReaderOption = _fileReaderOption; int nReadStars = 0; _octreeManager.initOctree(_cpuRamBudgetInBytes); std::filesystem::path file = absPath(_filePath.value()); LINFO(fmt::format("Loading data file: {}", file)); switch (fileReaderOption) { case gaia::FileReaderOption::Fits: // Read raw fits file and construct Octree. nReadStars = readFitsFile(file); break; case gaia::FileReaderOption::Speck: // Read raw speck file and construct Octree. nReadStars = readSpeckFile(file); break; case gaia::FileReaderOption::BinaryRaw: // Stars are stored in an ordered binary file. nReadStars = readBinaryRawFile(file); break; case gaia::FileReaderOption::BinaryOctree: // Octree already constructed and stored as a binary file. nReadStars = readBinaryOctreeFile(file); break; case gaia::FileReaderOption::StreamOctree: // Read Octree structure from file, without data. nReadStars = readBinaryOctreeStructureFile(file.string()); break; default: LERROR("Wrong FileReaderOption - no data file loaded"); break; } //_octreeManager->printStarsPerNode(); _nRenderedStars.setMaxValue(nReadStars); LINFO(fmt::format("Dataset contains a total of {} stars", nReadStars)); _totalDatasetSizeInBytes = nReadStars * (PositionSize + ColorSize + VelocitySize) * 4; return nReadStars > 0; } int RenderableGaiaStars::readFitsFile(const std::filesystem::path& filePath) { int nReadValuesPerStar = 0; FitsFileReader fitsFileReader(false); std::vector fullData = fitsFileReader.readFitsFile( filePath, nReadValuesPerStar, _firstRow, _lastRow, _columnNames ); // Insert stars into octree. for (size_t i = 0; i < fullData.size(); i += nReadValuesPerStar) { auto first = fullData.begin() + i; auto last = fullData.begin() + i + nReadValuesPerStar; std::vector starValues(first, last); _octreeManager.insert(starValues); } _octreeManager.sliceLodData(); return static_cast(fullData.size() / nReadValuesPerStar); } int RenderableGaiaStars::readSpeckFile(const std::filesystem::path& filePath) { int nReadValuesPerStar = 0; FitsFileReader fileReader(false); std::vector fullData = fileReader.readSpeckFile(filePath, nReadValuesPerStar); // Insert stars into octree. for (size_t i = 0; i < fullData.size(); i += nReadValuesPerStar) { auto first = fullData.begin() + i; auto last = fullData.begin() + i + nReadValuesPerStar; std::vector starValues(first, last); _octreeManager.insert(starValues); } _octreeManager.sliceLodData(); return static_cast(fullData.size() / nReadValuesPerStar); } int RenderableGaiaStars::readBinaryRawFile(const std::filesystem::path& filePath) { std::vector fullData; int nReadStars = 0; std::ifstream fileStream(filePath, std::ifstream::binary); if (fileStream.good()) { int32_t nValues = 0; int32_t nReadValuesPerStar = 0; int renderValues = 8; fileStream.read(reinterpret_cast(&nValues), sizeof(int32_t)); fileStream.read(reinterpret_cast(&nReadValuesPerStar), sizeof(int32_t)); fullData.resize(nValues); fileStream.read( reinterpret_cast(fullData.data()), nValues * sizeof(fullData[0]) ); // Insert stars into octree. for (size_t i = 0; i < fullData.size(); i += nReadValuesPerStar) { auto first = fullData.begin() + i; auto last = fullData.begin() + i + renderValues; std::vector starValues(first, last); _octreeManager.insert(starValues); } _octreeManager.sliceLodData(); nReadStars = nValues / nReadValuesPerStar; fileStream.close(); } else { LERROR(fmt::format( "Error opening file '{}' for loading raw binary file", filePath )); return nReadStars; } return nReadStars; } int RenderableGaiaStars::readBinaryOctreeFile(const std::filesystem::path& filePath) { int nReadStars = 0; std::ifstream fileStream(filePath, std::ifstream::binary); if (fileStream.good()) { nReadStars = _octreeManager.readFromFile(fileStream, true); fileStream.close(); } else { LERROR(fmt::format( "Error opening file '{}' for loading binary Octree file", filePath )); return nReadStars; } return nReadStars; } int RenderableGaiaStars::readBinaryOctreeStructureFile( const std::filesystem::path& folderPath) { int nReadStars = 0; std::string indexFile = folderPath.string() + "index.bin"; std::ifstream fileStream(indexFile, std::ifstream::binary); if (fileStream.good()) { nReadStars = _octreeManager.readFromFile(fileStream, false, folderPath.string()); fileStream.close(); } else { LERROR(fmt::format( "Error opening file '{}' for loading binary Octree file", indexFile )); return nReadStars; } return nReadStars; } } // namespace openspace