mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-05-03 17:30:04 -05:00
Feature/offline rendering (#906)
* Added functionality of saving out screenshots (with fixed FPS) from a recorded session during playback. * Test of feature to request if all data is ready to be rendered, primary used in GlobeBrowsing. Need to go over how chunkTiles are set to OK (some are never OK..). * Estimated having working request of checking if chunks with correct level have their color and height data loaded and ready to be rendered. Will re-enable frames saving and try making a movie. * Created adaptive LOD factor based on available/unavailable tile data (such that we don't run a lot of iterations with asking for a pile of data that does not fit in the tile cache). * Made it able to specify tile cache size in configurastion file. Renamed other Cache value to WMSCache, as we have the TileCache as well. * Fix for when focus node has nor renderable, when checking for if dersired data has been loaded. Should probably check all renderable/planets anyway, not just a focus object.
This commit is contained in:
@@ -63,9 +63,9 @@ namespace {
|
||||
constexpr const char* _loggerCat = "GlobeBrowsingModule";
|
||||
constexpr const char* _factoryName = "TileProvider";
|
||||
|
||||
constexpr const openspace::properties::Property::PropertyInfo CacheEnabledInfo = {
|
||||
"CacheEnabled",
|
||||
"Cache Enabled",
|
||||
constexpr const openspace::properties::Property::PropertyInfo WMSCacheEnabledInfo = {
|
||||
"WMSCacheEnabled",
|
||||
"WMS Cache Enabled",
|
||||
"Determines whether automatic caching of WMS servers is enabled. Changing the "
|
||||
"value of this property will not affect already created WMS datasets."
|
||||
};
|
||||
@@ -80,20 +80,26 @@ namespace {
|
||||
"already created WMS datasets."
|
||||
};
|
||||
|
||||
constexpr const openspace::properties::Property::PropertyInfo CacheLocationInfo = {
|
||||
"CacheLocation",
|
||||
"Cache Location",
|
||||
constexpr const openspace::properties::Property::PropertyInfo WMSCacheLocationInfo = {
|
||||
"WMSCacheLocation",
|
||||
"WMS Cache Location",
|
||||
"The location of the cache folder for WMS servers. Changing the value of this "
|
||||
"property will not affect already created WMS datasets."
|
||||
};
|
||||
|
||||
constexpr const openspace::properties::Property::PropertyInfo CacheSizeInfo = {
|
||||
"CacheSize",
|
||||
"Cache Size",
|
||||
constexpr const openspace::properties::Property::PropertyInfo WMSCacheSizeInfo = {
|
||||
"WMSCacheSize",
|
||||
"WMS Cache Size",
|
||||
"The maximum size of the cache for each WMS server. Changing the value of this "
|
||||
"property will not affect already created WMS datasets."
|
||||
};
|
||||
|
||||
constexpr const openspace::properties::Property::PropertyInfo TileCacheSizeInfo = {
|
||||
"TileCacheSize",
|
||||
"Tile Cache Size",
|
||||
"The maximum size of the MemoryAwareTileCache, on the CPU and GPU."
|
||||
};
|
||||
|
||||
|
||||
openspace::GlobeBrowsingModule::Capabilities
|
||||
parseSubDatasets(char** subDatasets, int nSubdatasets)
|
||||
@@ -151,31 +157,36 @@ namespace openspace {
|
||||
|
||||
GlobeBrowsingModule::GlobeBrowsingModule()
|
||||
: OpenSpaceModule(Name)
|
||||
, _cacheEnabled(CacheEnabledInfo, false)
|
||||
, _wmsCacheEnabled(WMSCacheEnabledInfo, false)
|
||||
, _offlineMode(OfflineModeInfo, false)
|
||||
, _cacheLocation(CacheLocationInfo, "${BASE}/cache_gdal")
|
||||
, _cacheSizeMB(CacheSizeInfo, 1024)
|
||||
, _wmsCacheLocation(WMSCacheLocationInfo, "${BASE}/cache_gdal")
|
||||
, _wmsCacheSizeMB(WMSCacheSizeInfo, 1024)
|
||||
, _tileCacheSizeMB(TileCacheSizeInfo, 1024)
|
||||
{
|
||||
addProperty(_cacheEnabled);
|
||||
addProperty(_wmsCacheEnabled);
|
||||
addProperty(_offlineMode);
|
||||
addProperty(_cacheLocation);
|
||||
addProperty(_cacheSizeMB);
|
||||
addProperty(_wmsCacheLocation);
|
||||
addProperty(_wmsCacheSizeMB);
|
||||
addProperty(_tileCacheSizeMB);
|
||||
}
|
||||
|
||||
void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) {
|
||||
using namespace globebrowsing;
|
||||
|
||||
if (dict.hasKeyAndValue<bool>(CacheEnabledInfo.identifier)) {
|
||||
_cacheEnabled = dict.value<bool>(CacheEnabledInfo.identifier);
|
||||
if (dict.hasKeyAndValue<bool>(WMSCacheEnabledInfo.identifier)) {
|
||||
_wmsCacheEnabled = dict.value<bool>(WMSCacheEnabledInfo.identifier);
|
||||
}
|
||||
if (dict.hasKeyAndValue<bool>(OfflineModeInfo.identifier)) {
|
||||
_offlineMode = dict.value<bool>(OfflineModeInfo.identifier);
|
||||
}
|
||||
if (dict.hasKeyAndValue<std::string>(CacheLocationInfo.identifier)) {
|
||||
_cacheLocation = dict.value<std::string>(CacheLocationInfo.identifier);
|
||||
if (dict.hasKeyAndValue<std::string>(WMSCacheLocationInfo.identifier)) {
|
||||
_wmsCacheLocation = dict.value<std::string>(WMSCacheLocationInfo.identifier);
|
||||
}
|
||||
if (dict.hasKeyAndValue<double>(CacheSizeInfo.identifier)) {
|
||||
_cacheSizeMB = static_cast<int>(dict.value<double>(CacheSizeInfo.identifier));
|
||||
if (dict.hasKeyAndValue<double>(WMSCacheSizeInfo.identifier)) {
|
||||
_wmsCacheSizeMB = static_cast<int>(dict.value<double>(WMSCacheSizeInfo.identifier));
|
||||
}
|
||||
if (dict.hasKeyAndValue<double>(TileCacheSizeInfo.identifier)) {
|
||||
_tileCacheSizeMB = static_cast<int>(dict.value<double>(TileCacheSizeInfo.identifier));
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
@@ -183,7 +194,7 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) {
|
||||
dict.value<bool>("NoWarning") :
|
||||
false;
|
||||
|
||||
if (!_cacheEnabled && _offlineMode && !noWarning) {
|
||||
if (!_wmsCacheEnabled && _offlineMode && !noWarning) {
|
||||
LWARNINGC(
|
||||
"GlobeBrowsingModule",
|
||||
"WMS caching is disabled, but offline mode is enabled. Unless you know "
|
||||
@@ -196,7 +207,7 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) {
|
||||
|
||||
// Initialize
|
||||
global::callback::initializeGL.emplace_back([&]() {
|
||||
_tileCache = std::make_unique<globebrowsing::cache::MemoryAwareTileCache>();
|
||||
_tileCache = std::make_unique<globebrowsing::cache::MemoryAwareTileCache>(_tileCacheSizeMB);
|
||||
addPropertySubOwner(*_tileCache);
|
||||
|
||||
tileprovider::initializeDefaultTile();
|
||||
@@ -697,20 +708,20 @@ bool GlobeBrowsingModule::hasUrlInfo(const std::string& globe) const {
|
||||
return _urlList.find(globe) != _urlList.end();
|
||||
}
|
||||
|
||||
bool GlobeBrowsingModule::isCachingEnabled() const {
|
||||
return _cacheEnabled;
|
||||
bool GlobeBrowsingModule::isWMSCachingEnabled() const {
|
||||
return _wmsCacheEnabled;
|
||||
}
|
||||
|
||||
bool GlobeBrowsingModule::isInOfflineMode() const {
|
||||
return _offlineMode;
|
||||
}
|
||||
|
||||
std::string GlobeBrowsingModule::cacheLocation() const {
|
||||
return _cacheLocation;
|
||||
std::string GlobeBrowsingModule::wmsCacheLocation() const {
|
||||
return _wmsCacheLocation;
|
||||
}
|
||||
|
||||
uint64_t GlobeBrowsingModule::cacheSize() const {
|
||||
uint64_t size = _cacheSizeMB;
|
||||
uint64_t GlobeBrowsingModule::wmsCacheSize() const {
|
||||
uint64_t size = _wmsCacheSizeMB;
|
||||
return size * 1024 * 1024;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,10 +85,10 @@ public:
|
||||
|
||||
void removeWMSServer(const std::string& name);
|
||||
|
||||
bool isCachingEnabled() const;
|
||||
bool isWMSCachingEnabled() const;
|
||||
bool isInOfflineMode() const;
|
||||
std::string cacheLocation() const;
|
||||
uint64_t cacheSize() const; // bytes
|
||||
std::string wmsCacheLocation() const;
|
||||
uint64_t wmsCacheSize() const; // bytes
|
||||
|
||||
protected:
|
||||
void internalInitialize(const ghoul::Dictionary&) override;
|
||||
@@ -113,10 +113,11 @@ private:
|
||||
static std::string layerTypeNamesList();
|
||||
|
||||
|
||||
properties::BoolProperty _cacheEnabled;
|
||||
properties::BoolProperty _wmsCacheEnabled;
|
||||
properties::BoolProperty _offlineMode;
|
||||
properties::StringProperty _cacheLocation;
|
||||
properties::UIntProperty _cacheSizeMB;
|
||||
properties::StringProperty _wmsCacheLocation;
|
||||
properties::UIntProperty _wmsCacheSizeMB;
|
||||
properties::UIntProperty _tileCacheSizeMB;
|
||||
|
||||
std::unique_ptr<globebrowsing::cache::MemoryAwareTileCache> _tileCache;
|
||||
|
||||
|
||||
@@ -251,12 +251,12 @@ size_t MemoryAwareTileCache::TextureContainer::size() const {
|
||||
// MemoryAwareTileCache
|
||||
//
|
||||
|
||||
MemoryAwareTileCache::MemoryAwareTileCache()
|
||||
MemoryAwareTileCache::MemoryAwareTileCache(int tileCacheSize)
|
||||
: PropertyOwner({ "TileCache" })
|
||||
, _numTextureBytesAllocatedOnCPU(0)
|
||||
, _cpuAllocatedTileData(CpuAllocatedDataInfo, 1024, 128, 16384, 1)
|
||||
, _gpuAllocatedTileData(GpuAllocatedDataInfo, 1024, 128, 16384, 1)
|
||||
, _tileCacheSize(TileCacheSizeInfo, 2048, 128, 16384, 1)
|
||||
, _cpuAllocatedTileData(CpuAllocatedDataInfo, tileCacheSize, 128, 16384, 1)
|
||||
, _gpuAllocatedTileData(GpuAllocatedDataInfo, tileCacheSize, 128, 16384, 1)
|
||||
, _tileCacheSize(TileCacheSizeInfo, tileCacheSize, 128, 16384, 1)
|
||||
, _applyTileCacheSize(ApplyTileCacheInfo)
|
||||
, _clearTileCache(ClearTileCacheInfo)
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ struct ProviderTileHasher {
|
||||
|
||||
class MemoryAwareTileCache : public properties::PropertyOwner {
|
||||
public:
|
||||
MemoryAwareTileCache();
|
||||
MemoryAwareTileCache(int tileCacheSize = 1024);
|
||||
|
||||
void clear();
|
||||
void setSizeEstimated(size_t estimatedSize);
|
||||
|
||||
@@ -445,7 +445,7 @@ void RawTileDataReader::initialize() {
|
||||
GlobeBrowsingModule& module = *global::moduleEngine.module<GlobeBrowsingModule>();
|
||||
|
||||
std::string content = _datasetFilePath;
|
||||
if (module.isCachingEnabled()) {
|
||||
if (module.isWMSCachingEnabled()) {
|
||||
std::string c;
|
||||
if (FileSys.fileExists(_datasetFilePath)) {
|
||||
// Only replace the 'content' if the dataset is an XML file and we want to do
|
||||
@@ -483,14 +483,14 @@ void RawTileDataReader::initialize() {
|
||||
CPLCreateXMLElementAndValue(
|
||||
cache,
|
||||
"Path",
|
||||
absPath(module.cacheLocation()).c_str()
|
||||
absPath(module.wmsCacheLocation()).c_str()
|
||||
);
|
||||
CPLCreateXMLElementAndValue(cache, "Depth", "4");
|
||||
CPLCreateXMLElementAndValue(cache, "Expires", "315576000"); // 10 years
|
||||
CPLCreateXMLElementAndValue(
|
||||
cache,
|
||||
"MaxSize",
|
||||
std::to_string(module.cacheSize()).c_str()
|
||||
std::to_string(module.wmsCacheSize()).c_str()
|
||||
);
|
||||
|
||||
// The serialization only needs to be one if the cache didn't exist
|
||||
|
||||
@@ -144,6 +144,13 @@ namespace {
|
||||
"" // @TODO Missing documentation
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DynamicLodIterationCountInfo =
|
||||
{
|
||||
"DynamicLodIterationCount",
|
||||
"Data availability checks before LOD factor impact",
|
||||
"" // @TODO Missing documentation
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo PerformShadingInfo = {
|
||||
"PerformShading",
|
||||
"Perform shading",
|
||||
@@ -168,9 +175,15 @@ namespace {
|
||||
"Enables the rendering of eclipse shadows using hard shadows"
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo LodScaleFactorInfo = {
|
||||
"LodScaleFactor",
|
||||
"Level of Detail Scale Factor",
|
||||
constexpr openspace::properties::Property::PropertyInfo TargetLodScaleFactorInfo = {
|
||||
"TargetLodScaleFactorInfo",
|
||||
"Target Level of Detail Scale Factor",
|
||||
"" // @TODO Missing documentation
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo CurrentLodScaleFactorInfo = {
|
||||
"CurrentLodScaleFactor",
|
||||
"Current Level of Detail Scale Factor (Read Only)",
|
||||
"" // @TODO Missing documentation
|
||||
};
|
||||
|
||||
@@ -198,12 +211,6 @@ using namespace openspace::properties;
|
||||
|
||||
namespace openspace::globebrowsing {
|
||||
|
||||
struct BoundingHeights {
|
||||
float min;
|
||||
float max;
|
||||
bool available;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
bool isLeaf(const Chunk& cn) {
|
||||
@@ -247,7 +254,7 @@ tilesAndSettingsUnsorted(const LayerGroup& layerGroup, const TileIndex& tileInde
|
||||
BoundingHeights boundingHeightsForChunk(const Chunk& chunk, const LayerManager& lm) {
|
||||
using ChunkTileSettingsPair = std::pair<ChunkTile, const LayerRenderSettings*>;
|
||||
|
||||
BoundingHeights boundingHeights { 0.f, 0.f, false };
|
||||
BoundingHeights boundingHeights { 0.f, 0.f, false, true };
|
||||
|
||||
// The raster of a height map is the first one. We assume that the height map is
|
||||
// a single raster image. If it is not we will just use the first raster
|
||||
@@ -293,6 +300,9 @@ BoundingHeights boundingHeightsForChunk(const Chunk& chunk, const LayerManager&
|
||||
}
|
||||
lastHadMissingData = tileMetaData.hasMissingData[HeightChannel];
|
||||
}
|
||||
else if(chunkTile.tile.status == Tile::Status::Unavailable) {
|
||||
boundingHeights.tileOK = false;
|
||||
}
|
||||
|
||||
// Allow for early termination
|
||||
if (!lastHadMissingData) {
|
||||
@@ -303,20 +313,37 @@ BoundingHeights boundingHeightsForChunk(const Chunk& chunk, const LayerManager&
|
||||
return boundingHeights;
|
||||
}
|
||||
|
||||
std::array<glm::dvec4, 8> boundingCornersForChunk(const Chunk& chunk,
|
||||
const LayerManager& lm,
|
||||
const Ellipsoid& ellipsoid)
|
||||
{
|
||||
const BoundingHeights& boundingHeight = boundingHeightsForChunk(chunk, lm);
|
||||
bool colorAvailableForChunk(const Chunk& chunk, const LayerManager& lm) {
|
||||
using ChunkTileSettingsPair = std::pair<ChunkTile, const LayerRenderSettings*>;
|
||||
const LayerGroup& colormaps = lm.layerGroup(layergroupid::GroupID::ColorLayers);
|
||||
std::vector<ChunkTileSettingsPair> chunkTileSettingPairs = tilesAndSettingsUnsorted(
|
||||
colormaps,
|
||||
chunk.tileIndex
|
||||
);
|
||||
|
||||
for (const ChunkTileSettingsPair& chunkTileSettingsPair : chunkTileSettingPairs) {
|
||||
const ChunkTile& chunkTile = chunkTileSettingsPair.first;
|
||||
|
||||
if (chunkTile.tile.status == Tile::Status::Unavailable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<glm::dvec4, 8> boundingCornersForChunk(const Chunk& chunk,
|
||||
const Ellipsoid& ellipsoid,
|
||||
const BoundingHeights& heights)
|
||||
{
|
||||
// assume worst case
|
||||
const double patchCenterRadius = ellipsoid.maximumRadius();
|
||||
|
||||
const double maxCenterRadius = patchCenterRadius + boundingHeight.max;
|
||||
const double maxCenterRadius = patchCenterRadius + heights.max;
|
||||
Geodetic2 halfSize = chunk.surfacePatch.halfSize();
|
||||
|
||||
// As the patch is curved, the maximum height offsets at the corners must be long
|
||||
// enough to cover large enough to cover a boundingHeight.max at the center of the
|
||||
// enough to cover large enough to cover a heights.max at the center of the
|
||||
// patch.
|
||||
// Approximating scaleToCoverCenter by assuming the latitude and longitude angles
|
||||
// of "halfSize" are equal to the angles they create from the center of the
|
||||
@@ -335,7 +362,7 @@ std::array<glm::dvec4, 8> boundingCornersForChunk(const Chunk& chunk,
|
||||
const bool chunkIsNorthOfEquator = chunk.surfacePatch.isNorthern();
|
||||
|
||||
// The minimum height offset, however, we can simply
|
||||
const double minCornerHeight = boundingHeight.min;
|
||||
const double minCornerHeight = heights.min;
|
||||
std::array<glm::dvec4, 8> corners;
|
||||
|
||||
const double latCloseToEquator = chunk.surfacePatch.edgeLatitudeNearestEquator();
|
||||
@@ -400,14 +427,16 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
|
||||
BoolProperty(HeightIntensityInfo, false),
|
||||
BoolProperty(LevelProjectedAreaInfo, true),
|
||||
BoolProperty(ResetTileProviderInfo, false),
|
||||
IntProperty(ModelSpaceRenderingInfo, 14, 1, 22)
|
||||
IntProperty(ModelSpaceRenderingInfo, 14, 1, 22),
|
||||
IntProperty(DynamicLodIterationCountInfo, 16, 4, 128)
|
||||
})
|
||||
, _generalProperties({
|
||||
BoolProperty(PerformShadingInfo, true),
|
||||
BoolProperty(AccurateNormalsInfo, false),
|
||||
BoolProperty(EclipseInfo, false),
|
||||
BoolProperty(EclipseHardShadowsInfo, false),
|
||||
FloatProperty(LodScaleFactorInfo, 15.f, 1.f, 50.f),
|
||||
FloatProperty(TargetLodScaleFactorInfo, 15.f, 1.f, 50.f),
|
||||
FloatProperty(CurrentLodScaleFactorInfo, 15.f, 1.f, 50.f),
|
||||
FloatProperty(CameraMinHeightInfo, 100.f, 0.f, 1000.f),
|
||||
FloatProperty(OrenNayarRoughnessInfo, 0.f, 0.f, 1.f),
|
||||
IntProperty(NActiveLayersInfo, 0, 0, OpenGLCap.maxTextureUnits() / 3)
|
||||
@@ -417,6 +446,8 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
|
||||
, _leftRoot(Chunk(LeftHemisphereIndex))
|
||||
, _rightRoot(Chunk(RightHemisphereIndex))
|
||||
{
|
||||
_generalProperties.currentLodScaleFactor.setReadOnly(true);
|
||||
|
||||
// Read the radii in to its own dictionary
|
||||
if (dictionary.hasKeyAndValue<glm::dvec3>(KeyRadii)) {
|
||||
_ellipsoid = Ellipsoid(dictionary.value<glm::vec3>(KeyRadii));
|
||||
@@ -444,8 +475,13 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
|
||||
addProperty(_generalProperties.useAccurateNormals);
|
||||
addProperty(_generalProperties.eclipseShadowsEnabled);
|
||||
addProperty(_generalProperties.eclipseHardShadows);
|
||||
_generalProperties.lodScaleFactor.onChange([this]() { _lodScaleFactorDirty = true; });
|
||||
addProperty(_generalProperties.lodScaleFactor);
|
||||
_generalProperties.targetLodScaleFactor.onChange([this]() {
|
||||
float sf = _generalProperties.targetLodScaleFactor;
|
||||
_generalProperties.currentLodScaleFactor = sf;
|
||||
_lodScaleFactorDirty = true;
|
||||
});
|
||||
addProperty(_generalProperties.targetLodScaleFactor);
|
||||
addProperty(_generalProperties.currentLodScaleFactor);
|
||||
addProperty(_generalProperties.cameraMinHeight);
|
||||
addProperty(_generalProperties.orenNayarRoughness);
|
||||
_generalProperties.nActiveLayers.setReadOnly(true);
|
||||
@@ -456,11 +492,10 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
|
||||
_debugPropertyOwner.addProperty(_debugProperties.showChunkAABB);
|
||||
_debugPropertyOwner.addProperty(_debugProperties.showHeightResolution);
|
||||
_debugPropertyOwner.addProperty(_debugProperties.showHeightIntensities);
|
||||
_debugPropertyOwner.addProperty(
|
||||
_debugProperties.levelByProjectedAreaElseDistance
|
||||
);
|
||||
_debugPropertyOwner.addProperty(_debugProperties.levelByProjectedAreaElseDistance);
|
||||
_debugPropertyOwner.addProperty(_debugProperties.resetTileProviders);
|
||||
_debugPropertyOwner.addProperty(_debugProperties.modelSpaceRenderingCutoffLevel);
|
||||
_debugPropertyOwner.addProperty(_debugProperties.dynamicLodIterationCount);
|
||||
|
||||
auto notifyShaderRecompilation = [&]() {
|
||||
_shadersNeedRecompilation = true;
|
||||
@@ -727,6 +762,10 @@ void RenderableGlobe::update(const UpdateData& data) {
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableGlobe::renderedWithDesiredData() const {
|
||||
return _allChunksAvailable;
|
||||
}
|
||||
|
||||
const LayerManager& RenderableGlobe::layerManager() const {
|
||||
return _layerManager;
|
||||
}
|
||||
@@ -758,7 +797,7 @@ void RenderableGlobe::renderChunks(const RenderData& data, RendererTasks&) {
|
||||
if (_layerManager.hasAnyBlendingLayersEnabled()) {
|
||||
if (_lodScaleFactorDirty) {
|
||||
const float dsf = static_cast<float>(
|
||||
_generalProperties.lodScaleFactor * _ellipsoid.minimumRadius()
|
||||
_generalProperties.currentLodScaleFactor * _ellipsoid.minimumRadius()
|
||||
);
|
||||
_globalRenderer.program->setUniform("distanceScaleFactor", dsf);
|
||||
_localRenderer.program->setUniform("distanceScaleFactor", dsf);
|
||||
@@ -787,7 +826,7 @@ void RenderableGlobe::renderChunks(const RenderData& data, RendererTasks&) {
|
||||
}
|
||||
|
||||
const float dsf = static_cast<float>(
|
||||
_generalProperties.lodScaleFactor * _ellipsoid.minimumRadius()
|
||||
_generalProperties.currentLodScaleFactor * _ellipsoid.minimumRadius()
|
||||
);
|
||||
_globalRenderer.program->setUniform("distanceScaleFactor", dsf);
|
||||
|
||||
@@ -809,16 +848,21 @@ void RenderableGlobe::renderChunks(const RenderData& data, RendererTasks&) {
|
||||
}
|
||||
|
||||
const float dsf = static_cast<float>(
|
||||
_generalProperties.lodScaleFactor * _ellipsoid.minimumRadius()
|
||||
_generalProperties.currentLodScaleFactor * _ellipsoid.minimumRadius()
|
||||
);
|
||||
_localRenderer.program->setUniform("distanceScaleFactor", dsf);
|
||||
|
||||
_localRenderer.updatedSinceLastCall = false;
|
||||
}
|
||||
|
||||
_allChunksAvailable = true;
|
||||
updateChunkTree(_leftRoot, data);
|
||||
updateChunkTree(_rightRoot, data);
|
||||
_chunkCornersDirty = false;
|
||||
_iterationsOfAvailableData =
|
||||
(_allChunksAvailable ? _iterationsOfAvailableData + 1 : 0);
|
||||
_iterationsOfUnavailableData =
|
||||
(_allChunksAvailable ? 0 : _iterationsOfUnavailableData + 1);
|
||||
|
||||
// Calculate the MVP matrix
|
||||
const glm::dmat4& viewTransform = data.camera.combinedViewMatrix();
|
||||
@@ -1010,6 +1054,29 @@ void RenderableGlobe::renderChunks(const RenderData& data, RendererTasks&) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If our tile cache is very full, we assume we need to adjust the level of detail
|
||||
// dynamically to not keep rendering frames with unavailable data
|
||||
// After certain number of iterations(_debugProperties.dynamicLodIterationCount) of
|
||||
// unavailable/available data in a row, we assume that a change could be made.
|
||||
const int iterCount = _debugProperties.dynamicLodIterationCount;
|
||||
const bool exceededIterations = _iterationsOfUnavailableData > iterCount;
|
||||
const float clf = _generalProperties.currentLodScaleFactor;
|
||||
const float clfMin = _generalProperties.currentLodScaleFactor.minValue();
|
||||
const float targetLod = _generalProperties.targetLodScaleFactor;
|
||||
const bool validLodFactor = clf > clfMin;
|
||||
if (exceededIterations && validLodFactor) {
|
||||
_generalProperties.currentLodScaleFactor =
|
||||
_generalProperties.currentLodScaleFactor - 0.1f;
|
||||
_iterationsOfUnavailableData = 0;
|
||||
_lodScaleFactorDirty = true;
|
||||
} // Make 2 times the iterations with available data to move it up again
|
||||
else if (_iterationsOfAvailableData > (iterCount * 2) && clf < targetLod) {
|
||||
_generalProperties.currentLodScaleFactor =
|
||||
_generalProperties.currentLodScaleFactor + 0.1f;
|
||||
_iterationsOfAvailableData = 0;
|
||||
_lodScaleFactorDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableGlobe::renderChunkGlobally(const Chunk& chunk, const RenderData& data) {
|
||||
@@ -1527,17 +1594,19 @@ SurfacePositionHandle RenderableGlobe::calculateSurfacePositionHandle(
|
||||
}
|
||||
|
||||
bool RenderableGlobe::testIfCullable(const Chunk& chunk,
|
||||
const RenderData& renderData) const
|
||||
const RenderData& renderData,
|
||||
const BoundingHeights& heights) const
|
||||
{
|
||||
return (PreformHorizonCulling && isCullableByHorizon(chunk, renderData)) ||
|
||||
return (PreformHorizonCulling && isCullableByHorizon(chunk, renderData, heights)) ||
|
||||
(PerformFrustumCulling && isCullableByFrustum(chunk, renderData));
|
||||
}
|
||||
|
||||
int RenderableGlobe::desiredLevel(const Chunk& chunk, const RenderData& renderData) const
|
||||
int RenderableGlobe::desiredLevel(const Chunk& chunk, const RenderData& renderData,
|
||||
const BoundingHeights& heights) const
|
||||
{
|
||||
const int desiredLevel = _debugProperties.levelByProjectedAreaElseDistance ?
|
||||
desiredLevelByProjectedArea(chunk, renderData) :
|
||||
desiredLevelByDistance(chunk, renderData);
|
||||
desiredLevelByProjectedArea(chunk, renderData, heights) :
|
||||
desiredLevelByDistance(chunk, renderData, heights);
|
||||
const int levelByAvailableData = desiredLevelByAvailableTileData(chunk);
|
||||
|
||||
if (LimitLevelByAvailableData && (levelByAvailableData != UnknownDesiredLevel)) {
|
||||
@@ -1838,7 +1907,8 @@ void RenderableGlobe::calculateEclipseShadows(ghoul::opengl::ProgramObject& prog
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int RenderableGlobe::desiredLevelByDistance(const Chunk& chunk,
|
||||
const RenderData& data) const
|
||||
const RenderData& data,
|
||||
const BoundingHeights& heights) const
|
||||
{
|
||||
// Calculations are done in the reference frame of the globe
|
||||
// (model space). Hence, the camera position needs to be transformed
|
||||
@@ -1852,7 +1922,6 @@ int RenderableGlobe::desiredLevelByDistance(const Chunk& chunk,
|
||||
const glm::dvec3 patchNormal = _ellipsoid.geodeticSurfaceNormal(pointOnPatch);
|
||||
glm::dvec3 patchPosition = _ellipsoid.cartesianSurfacePosition(pointOnPatch);
|
||||
|
||||
const BoundingHeights heights = boundingHeightsForChunk(chunk, _layerManager);
|
||||
const double heightToChunk = heights.min;
|
||||
|
||||
// Offset position according to height
|
||||
@@ -1864,7 +1933,7 @@ int RenderableGlobe::desiredLevelByDistance(const Chunk& chunk,
|
||||
const double distanceToPatch = glm::length(cameraToChunk);
|
||||
const double distance = distanceToPatch;
|
||||
|
||||
const double scaleFactor = _generalProperties.lodScaleFactor *
|
||||
const double scaleFactor = _generalProperties.currentLodScaleFactor *
|
||||
_ellipsoid.minimumRadius();
|
||||
const double projectedScaleFactor = scaleFactor / distance;
|
||||
const int desiredLevel = static_cast<int>(ceil(log2(projectedScaleFactor)));
|
||||
@@ -1872,7 +1941,8 @@ int RenderableGlobe::desiredLevelByDistance(const Chunk& chunk,
|
||||
}
|
||||
|
||||
int RenderableGlobe::desiredLevelByProjectedArea(const Chunk& chunk,
|
||||
const RenderData& data) const
|
||||
const RenderData& data,
|
||||
const BoundingHeights& heights) const
|
||||
{
|
||||
// Calculations are done in the reference frame of the globe
|
||||
// (model space). Hence, the camera position needs to be transformed
|
||||
@@ -1907,7 +1977,6 @@ int RenderableGlobe::desiredLevelByProjectedArea(const Chunk& chunk,
|
||||
// +-----------------+ <-- south east corner
|
||||
|
||||
const Geodetic2 center = chunk.surfacePatch.center();
|
||||
const BoundingHeights heights = boundingHeightsForChunk(chunk, _layerManager);
|
||||
const Geodetic3 c = { center, heights.min };
|
||||
const Geodetic3 c1 = { Geodetic2{ center.lat, closestCorner.lon }, heights.min };
|
||||
const Geodetic3 c2 = { Geodetic2{ closestCorner.lat, center.lon }, heights.min };
|
||||
@@ -1949,7 +2018,7 @@ int RenderableGlobe::desiredLevelByProjectedArea(const Chunk& chunk,
|
||||
const double areaABC = 0.5 * glm::length(glm::cross(AC, AB));
|
||||
const double projectedChunkAreaApprox = 8 * areaABC;
|
||||
|
||||
const double scaledArea = _generalProperties.lodScaleFactor *
|
||||
const double scaledArea = _generalProperties.currentLodScaleFactor *
|
||||
projectedChunkAreaApprox;
|
||||
return chunk.tileIndex.level + static_cast<int>(round(scaledArea - 1));
|
||||
}
|
||||
@@ -2000,12 +2069,13 @@ bool RenderableGlobe::isCullableByFrustum(const Chunk& chunk,
|
||||
}
|
||||
|
||||
bool RenderableGlobe::isCullableByHorizon(const Chunk& chunk,
|
||||
const RenderData& renderData) const
|
||||
const RenderData& renderData,
|
||||
const BoundingHeights& heights) const
|
||||
{
|
||||
// Calculations are done in the reference frame of the globe. Hence, the camera
|
||||
// position needs to be transformed with the inverse model matrix
|
||||
const GeodeticPatch& patch = chunk.surfacePatch;
|
||||
const float maxHeight = boundingHeightsForChunk(chunk, _layerManager).max;
|
||||
const float maxHeight = heights.max;
|
||||
const glm::dvec3 globePos = glm::dvec3(0, 0, 0); // In model space it is 0
|
||||
const double minimumGlobeRadius = _ellipsoid.minimumRadius();
|
||||
|
||||
@@ -2079,10 +2149,14 @@ void RenderableGlobe::splitChunkNode(Chunk& cn, int depth) {
|
||||
cn.children[i] = new (memory[i]) Chunk(
|
||||
cn.tileIndex.child(static_cast<Quad>(i))
|
||||
);
|
||||
const BoundingHeights& heights = boundingHeightsForChunk(
|
||||
*(cn.children[i]),
|
||||
_layerManager
|
||||
);
|
||||
cn.children[i]->corners = boundingCornersForChunk(
|
||||
*cn.children[i],
|
||||
_layerManager,
|
||||
_ellipsoid
|
||||
_ellipsoid,
|
||||
heights
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2122,9 +2196,16 @@ bool RenderableGlobe::updateChunkTree(Chunk& cn, const RenderData& data) {
|
||||
// In addition, this didn't even improve performance --- 2018-10-04
|
||||
if (isLeaf(cn)) {
|
||||
updateChunk(cn, data);
|
||||
|
||||
|
||||
if (cn.status == Chunk::Status::WantSplit) {
|
||||
splitChunkNode(cn, 1);
|
||||
}
|
||||
else if (cn.status == Chunk::Status::DoNothing && (!cn.colorTileOK)) {
|
||||
// Checking cn.heightTileOK caused always not avaiable for certain HiRISE data
|
||||
_allChunksAvailable = false;
|
||||
}
|
||||
|
||||
return cn.status == Chunk::Status::WantMerge;
|
||||
}
|
||||
else {
|
||||
@@ -2141,28 +2222,37 @@ bool RenderableGlobe::updateChunkTree(Chunk& cn, const RenderData& data) {
|
||||
if (allChildrenWantsMerge && (cn.status != Chunk::Status::WantSplit)) {
|
||||
mergeChunkNode(cn);
|
||||
}
|
||||
else if (cn.status == Chunk::Status::WantSplit) {
|
||||
splitChunkNode(cn, 1);
|
||||
}
|
||||
else if (cn.status == Chunk::Status::DoNothing && (!cn.colorTileOK)) {
|
||||
_allChunksAvailable = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableGlobe::updateChunk(Chunk& chunk, const RenderData& data) const {
|
||||
const BoundingHeights& heights = boundingHeightsForChunk(chunk, _layerManager);
|
||||
chunk.heightTileOK = heights.tileOK;
|
||||
chunk.colorTileOK = colorAvailableForChunk(chunk, _layerManager);
|
||||
|
||||
if (_chunkCornersDirty) {
|
||||
chunk.corners = boundingCornersForChunk(chunk, _layerManager, _ellipsoid);
|
||||
chunk.corners = boundingCornersForChunk(chunk, _ellipsoid, heights);
|
||||
|
||||
// The flag gets set to false globally after the updateChunkTree calls
|
||||
}
|
||||
|
||||
if (testIfCullable(chunk, data)) {
|
||||
if (testIfCullable(chunk, data, heights)) {
|
||||
chunk.isVisible = false;
|
||||
chunk.status = Chunk::Status::WantMerge;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
chunk.isVisible = true;
|
||||
}
|
||||
|
||||
const int dl = desiredLevel(chunk, data);
|
||||
const int dl = desiredLevel(chunk, data, heights);
|
||||
|
||||
if (dl < chunk.tileIndex.level) {
|
||||
chunk.status = Chunk::Status::WantMerge;
|
||||
|
||||
@@ -47,6 +47,13 @@ class GPULayerGroup;
|
||||
class RenderableGlobe;
|
||||
struct TileIndex;
|
||||
|
||||
struct BoundingHeights {
|
||||
float min;
|
||||
float max;
|
||||
bool available;
|
||||
bool tileOK;
|
||||
};
|
||||
|
||||
namespace chunklevelevaluator { class Evaluator; }
|
||||
namespace culling { class ChunkCuller; }
|
||||
|
||||
@@ -65,6 +72,9 @@ struct Chunk {
|
||||
Status status;
|
||||
|
||||
bool isVisible = true;
|
||||
bool colorTileOK = false;
|
||||
bool heightTileOK = false;
|
||||
|
||||
std::array<glm::dvec4, 8> corners;
|
||||
std::array<Chunk*, 4> children = { { nullptr, nullptr, nullptr, nullptr } };
|
||||
};
|
||||
@@ -94,6 +104,8 @@ public:
|
||||
SurfacePositionHandle calculateSurfacePositionHandle(
|
||||
const glm::dvec3& targetModelSpace) const override;
|
||||
|
||||
bool renderedWithDesiredData() const override;
|
||||
|
||||
const Ellipsoid& ellipsoid() const;
|
||||
const LayerManager& layerManager() const;
|
||||
LayerManager& layerManager();
|
||||
@@ -112,6 +124,7 @@ private:
|
||||
properties::BoolProperty levelByProjectedAreaElseDistance;
|
||||
properties::BoolProperty resetTileProviders;
|
||||
properties::IntProperty modelSpaceRenderingCutoffLevel;
|
||||
properties::IntProperty dynamicLodIterationCount;
|
||||
} _debugProperties;
|
||||
|
||||
struct {
|
||||
@@ -119,7 +132,8 @@ private:
|
||||
properties::BoolProperty useAccurateNormals;
|
||||
properties::BoolProperty eclipseShadowsEnabled;
|
||||
properties::BoolProperty eclipseHardShadows;
|
||||
properties::FloatProperty lodScaleFactor;
|
||||
properties::FloatProperty targetLodScaleFactor;
|
||||
properties::FloatProperty currentLodScaleFactor;
|
||||
properties::FloatProperty cameraMinHeight;
|
||||
properties::FloatProperty orenNayarRoughness;
|
||||
properties::IntProperty nActiveLayers;
|
||||
@@ -134,7 +148,8 @@ private:
|
||||
* Goes through all available <code>ChunkCuller</code>s and check if any of them
|
||||
* allows culling of the <code>Chunk</code>s in question.
|
||||
*/
|
||||
bool testIfCullable(const Chunk& chunk, const RenderData& renderData) const;
|
||||
bool testIfCullable(const Chunk& chunk, const RenderData& renderData,
|
||||
const BoundingHeights& heights) const;
|
||||
|
||||
/**
|
||||
* Gets the desired level which can be used to determine if a chunk should split
|
||||
@@ -146,7 +161,8 @@ private:
|
||||
* <code>Chunk</code>, it wants to split. If it is lower, it wants to merge with
|
||||
* its siblings.
|
||||
*/
|
||||
int desiredLevel(const Chunk& chunk, const RenderData& renderData) const;
|
||||
int desiredLevel(const Chunk& chunk, const RenderData& renderData,
|
||||
const BoundingHeights& heights) const;
|
||||
|
||||
/**
|
||||
* Calculates the height from the surface of the reference ellipsoid to the
|
||||
@@ -190,10 +206,13 @@ private:
|
||||
bool renderBounds, bool renderAABB) const;
|
||||
|
||||
bool isCullableByFrustum(const Chunk& chunk, const RenderData& renderData) const;
|
||||
bool isCullableByHorizon(const Chunk& chunk, const RenderData& renderData) const;
|
||||
bool isCullableByHorizon(const Chunk& chunk, const RenderData& renderData,
|
||||
const BoundingHeights& heights) const;
|
||||
|
||||
int desiredLevelByDistance(const Chunk& chunk, const RenderData& data) const;
|
||||
int desiredLevelByProjectedArea(const Chunk& chunk, const RenderData& data) const;
|
||||
int desiredLevelByDistance(const Chunk& chunk, const RenderData& data,
|
||||
const BoundingHeights& heights) const;
|
||||
int desiredLevelByProjectedArea(const Chunk& chunk, const RenderData& data,
|
||||
const BoundingHeights& heights) const;
|
||||
int desiredLevelByAvailableTileData(const Chunk& chunk) const;
|
||||
|
||||
|
||||
@@ -247,6 +266,9 @@ private:
|
||||
bool _lodScaleFactorDirty = true;
|
||||
bool _chunkCornersDirty = true;
|
||||
bool _nLayersIsDirty = true;
|
||||
bool _allChunksAvailable = true;
|
||||
size_t _iterationsOfAvailableData = 0;
|
||||
size_t _iterationsOfUnavailableData = 0;
|
||||
Layer* _lastChangedLayer = nullptr;
|
||||
|
||||
// Labels
|
||||
|
||||
Reference in New Issue
Block a user