From 9c73978eb167d54a46531f0105376bf3ce60194f Mon Sep 17 00:00:00 2001 From: Glenn Waldron Date: Mon, 20 Nov 2023 12:57:49 -0500 Subject: [PATCH] Chonk: rework the chonk infrastructure to support IndirectGeometry; add Chonk support to the XYZ and Feature model graphs + layers. --- src/osgEarth/Chonk | 44 +++- src/osgEarth/Chonk.cpp | 401 ++++++++++++++++++++--------- src/osgEarth/FeatureModelGraph | 3 +- src/osgEarth/FeatureModelGraph.cpp | 62 +++-- src/osgEarth/TextureArena | 11 +- src/osgEarth/TextureArena.cpp | 7 +- src/osgEarth/XYZModelGraph | 10 + src/osgEarth/XYZModelGraph.cpp | 90 ++++++- src/osgEarth/XYZModelLayer.cpp | 1 + tests/osm.earth | 12 +- tests/osm_buildings.xml | 5 +- tests/viewpoints.xml | 18 ++ 12 files changed, 487 insertions(+), 177 deletions(-) diff --git a/src/osgEarth/Chonk b/src/osgEarth/Chonk index 1e9309edfc..52d2b751b3 100644 --- a/src/osgEarth/Chonk +++ b/src/osgEarth/Chonk @@ -78,10 +78,12 @@ namespace osgEarth { public: using element_t = GLuint; + using Ptr = std::shared_ptr; + using WeakPtr = std::weak_ptr; //! Adds a node. bool add( - osg::Node*, + osg::Node* node, ChonkFactory& factory); //! Adds a node with screen-space-error limits. @@ -129,8 +131,6 @@ namespace osgEarth }; public: - using Ptr = std::shared_ptr; - using WeakPtr = std::weak_ptr; //! Creates a new empty chonk. static Ptr create(); @@ -175,6 +175,10 @@ namespace osgEarth using GetOrCreateFunction = std::function; + osg::ref_ptr _textures; + + GetOrCreateFunction _getOrCreateTexture; + public: ChonkFactory( TextureArena* textures); @@ -183,15 +187,27 @@ namespace osgEarth //! creating new ones. Good for sharing data across invocations. void setGetOrCreateFunction(GetOrCreateFunction); - //! Adds a node and generates an asset drawable for it. - //! The asset will upload to the GPU on the next call to apply. - void load( + //! For the given node, this method populates the provided Chonk wit + //! its geometry. If the factory finds any InstancedGeometry's along + //! the way it will create a Chonk for each one in out_instances. + //! Returns false if the node contained no geometry. + bool load( osg::Node* node, - Chonk& chonk); + Chonk* chonk, + float far_pixel_scale = 0.0f, + float near_pixel_scale = FLT_MAX); - private: - osg::ref_ptr _textures; - GetOrCreateFunction _getOrCreateTexture; + bool load( + osg::Node* node, + ChonkDrawable* drawable); + + /** + * Stock "getOrCreate" function for the ChonkFactory that works on + * a vector of Weak pointers. + */ + static GetOrCreateFunction createWeakTextureCacheFunction( + std::vector& cache, + std::mutex& cache_mutex); }; /** @@ -209,6 +225,9 @@ namespace osgEarth //! @param renderBinNumber OSG render bin number for the drawable ChonkDrawable(int render_bin_number = 3); + //! Adds a node to the drawable. + bool add(osg::Node* node, ChonkFactory& factory); + //! Adds one instance of a chonk to the drawable. void add(Chonk::Ptr value); @@ -247,6 +266,11 @@ namespace osgEarth //! Installs a basic chonk-rendering shader on a drawable. static void installRenderBin(ChonkDrawable*); + //! Whether this drawable contains any geometry + bool empty() const { + return _batches.empty(); + } + public: virtual osg::BoundingBox computeBoundingBox() const override; diff --git a/src/osgEarth/Chonk.cpp b/src/osgEarth/Chonk.cpp index 983e1d9ba8..6353531cee 100644 --- a/src/osgEarth/Chonk.cpp +++ b/src/osgEarth/Chonk.cpp @@ -26,6 +26,8 @@ #include "VirtualProgram" #include "Shaders" #include "Utils" +#include "NodeUtils" +#include "DrawInstanced" #include #include @@ -58,6 +60,14 @@ namespace void apply(osg::Geometry& node) override { + auto instanced = dynamic_cast(&node); + if (instanced) + { + return; // skip these. + //apply(*instanced); + //return; + } + auto verts = dynamic_cast(node.getVertexArray()); if (verts) { @@ -89,7 +99,8 @@ namespace */ struct Ripper : public osg::NodeVisitor { - Chonk& _result; + Chonk* _chonk = nullptr; + ChonkDrawable* _drawable = nullptr; // optional. TextureArena* _textures; ChonkFactory::GetOrCreateFunction _getOrCreateTexture; std::list _materialCache; @@ -156,8 +167,13 @@ namespace return material; } - Ripper(Chonk& chonk, TextureArena* textures, ChonkFactory::GetOrCreateFunction func) : - _result(chonk), + // Constructor. + // Pointer to the "current base chonk" is required. + // Pointer to the IG chonk vector is optional; populate this if you want the ripper to rip InstanceGeometry nodes + // and add them to the vector. + Ripper(Chonk* chonk, ChonkDrawable* drawable, TextureArena* textures, ChonkFactory::GetOrCreateFunction func) : + _chonk(chonk), + _drawable(drawable), _textures(textures), _getOrCreateTexture(func) { @@ -166,8 +182,7 @@ namespace setTraversalMode(TRAVERSE_ACTIVE_CHILDREN); setNodeMaskOverride(~0); - _materialStack.push(reuseOrCreateMaterial( - nullptr, nullptr, nullptr, nullptr, nullptr)); + _materialStack.push(reuseOrCreateMaterial(nullptr, nullptr, nullptr, nullptr, nullptr)); _transformStack.push(osg::Matrix()); } @@ -176,8 +191,11 @@ namespace // adds a teture to the arena and returns its index Texture::Ptr addTexture(unsigned slot, osg::StateSet* stateset) { - if (!_textures) - return nullptr; + OE_SOFT_ASSERT_AND_RETURN(_textures != nullptr, {}); + + // if the slot isn't mapped, bail out + if (slot < 0) + return {}; Texture::Ptr arena_tex; @@ -285,120 +303,169 @@ namespace { bool pushed = pushStateSet(node.getStateSet()); - unsigned numVerts = node.getVertexArray()->getNumElements(); - - unsigned vbo_offset = _result._vbo_store.size(); + Chonk* chonk = _chonk; - auto verts = dynamic_cast(node.getVertexArray()); - auto colors = dynamic_cast(node.getColorArray()); - auto normals = dynamic_cast(node.getNormalArray()); - auto normal_techniques = dynamic_cast(node.getVertexAttribArray(NORMAL_TECHNIQUE_SLOT)); - auto flexors = dynamic_cast(node.getTexCoordArray(FLEXOR_SLOT)); - auto extended_material = dynamic_cast(node.getVertexAttribArray(EXTENDED_MATERIAL_SLOT)); + // If this an instanced geometry, create a new chonk for it and + // let the code below rip to that new chonk. Afterwards we will add that + // new chonk to the drawable with each instance matrix. + Chonk::Ptr ig_chonk; + auto ig = dynamic_cast(&node); + if (_drawable && ig) + { + ig_chonk = Chonk::create(); - // support either 2- or 3-component tex coords, but only read the xy components! - auto uv2s = dynamic_cast(node.getTexCoordArray(0)); - auto uv3s = dynamic_cast(node.getTexCoordArray(0)); + // preallocate for speed: + Counter counter; + node.accept(counter); + if (counter._numElements > 0 && counter._numVerts > 0) + { + ig_chonk->_vbo_store.reserve(counter._numVerts); + ig_chonk->_ebo_store.reserve(counter._numElements); + } - auto& material = _materialStack.top(); - osg::Vec3f n; + chonk = ig_chonk.get(); + } - for (unsigned i = 0; i < numVerts; ++i) + // rip the geometry into our chonk. + if (chonk) { - Chonk::VertexGPU v; + unsigned numVerts = node.getVertexArray()->getNumElements(); - if (verts) - { - v.position = (*verts)[i] * _transformStack.top(); - } - - if (colors) - { - int k = colors->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.color = Color((*colors)[k]).asNormalizedRGBA(); - } - else - { - v.color.set(255, 255, 255, 255); - } + unsigned vbo_offset = chonk->_vbo_store.size(); - if (normals) - { - int k = normals->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.normal = osg::Matrix::transform3x3((*normals)[k], _transformStack.top()); - } - else - { - v.normal.set(0, 0, 1); - } + auto verts = dynamic_cast(node.getVertexArray()); + auto colors = dynamic_cast(node.getColorArray()); + auto normals = dynamic_cast(node.getNormalArray()); + auto normal_techniques = dynamic_cast(node.getVertexAttribArray(NORMAL_TECHNIQUE_SLOT)); + auto flexors = dynamic_cast(node.getTexCoordArray(FLEXOR_SLOT)); + auto extended_material = dynamic_cast(node.getVertexAttribArray(EXTENDED_MATERIAL_SLOT)); - if (normal_techniques) - { - int k = normal_techniques->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.normal_technique = (*normal_techniques)[k]; - } - else - { - v.normal_technique = 0; - } + // support either 2- or 3-component tex coords, but only read the xy components! + auto uv2s = dynamic_cast(node.getTexCoordArray(0)); + auto uv3s = dynamic_cast(node.getTexCoordArray(0)); - if (uv2s) - { - int k = uv2s->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.uv = (*uv2s)[k]; - } - else if (uv3s) - { - int k = uv3s->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.uv.set((*uv3s)[k].x(), (*uv3s)[k].y()); - } - else - { - v.uv.set(0.0f, 0.0f); - } + auto& material = _materialStack.top(); + osg::Vec3f n; - if (flexors) - { - int k = flexors->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.flex = osg::Matrix::transform3x3((*flexors)[k], _transformStack.top()); - } - else + for (unsigned i = 0; i < numVerts; ++i) { - v.flex.set(0, 0, 1); - } + Chonk::VertexGPU v; - v.albedo_index = material ? material->albedo_index : -1; - v.normalmap_index = material ? material->normal_index : -1; - v.pbr_index = material ? material->pbr_index : -1; + if (verts) + { + v.position = (*verts)[i] * _transformStack.top(); + } - // prioritize material textures over vertex material ids. - v.extended_material_index = material ? osg::Vec2s(material->material1_index, material->material2_index) : osg::Vec2s(-1, -1); + if (colors) + { + int k = colors->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.color = Color((*colors)[k]).asNormalizedRGBA(); + } + else + { + v.color.set(255, 255, 255, 255); + } - // fallback is to use vertex stream to simulate material ids. - if (extended_material && v.extended_material_index[0] == -1 && v.extended_material_index[1] == -1) - { - int k = extended_material->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; - v.extended_material_index = osg::Vec2s( (*extended_material)[k], -1 ); - } + if (normals) + { + int k = normals->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.normal = osg::Matrix::transform3x3((*normals)[k], _transformStack.top()); + } + else + { + v.normal.set(0, 0, 1); + } + + if (normal_techniques) + { + int k = normal_techniques->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.normal_technique = (*normal_techniques)[k]; + } + else + { + v.normal_technique = 0; + } + + if (uv2s) + { + int k = uv2s->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.uv = (*uv2s)[k]; + } + else if (uv3s) + { + int k = uv3s->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.uv.set((*uv3s)[k].x(), (*uv3s)[k].y()); + } + else + { + v.uv.set(0.0f, 0.0f); + } + + if (flexors) + { + int k = flexors->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.flex = osg::Matrix::transform3x3((*flexors)[k], _transformStack.top()); + } + else + { + v.flex.set(0, 0, 1); + } + + v.albedo_index = material ? material->albedo_index : -1; + v.normalmap_index = material ? material->normal_index : -1; + v.pbr_index = material ? material->pbr_index : -1; - _result._vbo_store.emplace_back(std::move(v)); + // prioritize material textures over vertex material ids. + v.extended_material_index = material ? osg::Vec2s(material->material1_index, material->material2_index) : osg::Vec2s(-1, -1); - // per-vert materials: - _result._materials.push_back(_materialStack.top()); + // fallback is to use vertex stream to simulate material ids. + if (extended_material && v.extended_material_index[0] == -1 && v.extended_material_index[1] == -1) + { + int k = extended_material->getBinding() == osg::Array::BIND_PER_VERTEX ? i : 0; + v.extended_material_index = osg::Vec2s((*extended_material)[k], -1); + } + + chonk->_vbo_store.emplace_back(std::move(v)); + + // per-vert materials: + chonk->_materials.push_back(_materialStack.top()); + } + + // assemble the elements set + auto copy_indices = [this, chonk, vbo_offset]( + osg::Geometry& geom, + unsigned i0, unsigned i1, unsigned i2, + const osg::Matrix& l2w) + { + if (vbo_offset + i0 >= chonk->_vbo_store.size() || + vbo_offset + i1 >= chonk->_vbo_store.size() || + vbo_offset + i2 >= chonk->_vbo_store.size()) + { + OE_WARN << LC << "Index out of range" << std::endl; + return; + } + + chonk->_ebo_store.emplace_back(vbo_offset + i0); + chonk->_ebo_store.emplace_back(vbo_offset + i1); + chonk->_ebo_store.emplace_back(vbo_offset + i2); + }; + osg::ref_ptr raw_geom = new osg::Geometry(node, osg::CopyOp::SHALLOW_COPY); + TriangleVisitor copy_visitor(copy_indices); + raw_geom->accept(copy_visitor); } - // assemble the elements set - auto copy_indices = [this, vbo_offset]( - osg::Geometry& geom, - unsigned i0, unsigned i1, unsigned i2, - const osg::Matrix& l2w) + if (_drawable && ig_chonk && ig) { - _result._ebo_store.emplace_back(vbo_offset + i0); - _result._ebo_store.emplace_back(vbo_offset + i1); - _result._ebo_store.emplace_back(vbo_offset + i2); - }; - TriangleVisitor copy_visitor(copy_indices); - node.accept(copy_visitor); + ig_chonk->_lods.push_back({ 0u, chonk->_ebo_store.size(), 0.0f, FLT_MAX }); + ig_chonk->_box.init(); + + for(auto matrix : ig->getMatrices()) + { + float* ptr = matrix.ptr(); + ptr[3] = ptr[7] = ptr[11] = 0.0f; ptr[15] = 1.0f; // strip the object ID! + _drawable->add(ig_chonk, matrix * _transformStack.top()); + } + } if (pushed) popStateSet(); } @@ -442,12 +509,7 @@ Chonk::add(osg::Node* node, ChonkFactory& factory) OE_SOFT_ASSERT_AND_RETURN(node != nullptr, false); OE_HARD_ASSERT(_lods.size() < 3); - factory.load(node, *this); - _lods.push_back({ 0u, _ebo_store.size(), 0.0f, FLT_MAX }); - - _box.init(); - - return true; + return factory.load(node, this); } bool @@ -460,19 +522,8 @@ Chonk::add( OE_SOFT_ASSERT_AND_RETURN(node != nullptr, false); OE_HARD_ASSERT(_lods.size() < 3); - unsigned offset = _ebo_store.size(); - factory.load(node, *this); - _lods.push_back({ - offset, - (_ebo_store.size() - offset), // length of new variant - far_pixel_scale, - near_pixel_scale }); - - _box.init(); - - OE_DEBUG << LC << "Added LOD with " << (_ebo_store.size() - offset) / 3 << " triangles" << std::endl; - - return true; + //unsigned offset = _ebo_store.size(); + return factory.load(node, this, far_pixel_scale, near_pixel_scale); } const Chonk::DrawCommands& @@ -554,9 +605,11 @@ ChonkFactory::setGetOrCreateFunction(GetOrCreateFunction value) _getOrCreateTexture = value; } -void -ChonkFactory::load(osg::Node* node, Chonk& chonk) +bool +ChonkFactory::load(osg::Node* node, Chonk* chonk, float far_pixel_scale, float near_pixel_scale) { + OE_SOFT_ASSERT_AND_RETURN(node != nullptr, false); + OE_SOFT_ASSERT_AND_RETURN(chonk != nullptr, false); OE_PROFILING_ZONE; // convert all primitive sets to indexed primitives @@ -566,15 +619,74 @@ ChonkFactory::load(osg::Node* node, Chonk& chonk) // first count up the memory we need and allocate it Counter counter; node->accept(counter); - chonk._vbo_store.reserve(counter._numVerts); - chonk._ebo_store.reserve(counter._numElements); + + chonk->_vbo_store.reserve(chonk->_vbo_store.size() + counter._numVerts); + chonk->_ebo_store.reserve(chonk->_ebo_store.size() + counter._numElements); + + unsigned offset = chonk->_ebo_store.size(); // rip geometry and textures into a new Asset object - Ripper ripper(chonk, _textures.get(), _getOrCreateTexture); + Ripper ripper(chonk, nullptr, _textures.get(), _getOrCreateTexture); node->accept(ripper); // dirty its bounding box - chonk._box.init(); + if (chonk->_ebo_store.size() > 0) + { + chonk->_lods.push_back({ offset, chonk->_ebo_store.size(), far_pixel_scale, near_pixel_scale }); + } + chonk->_box.init(); + + return (counter._numVerts > 0 && counter._numElements > 0); +} + +bool +ChonkFactory::load(osg::Node* node, ChonkDrawable* drawable) +{ + OE_SOFT_ASSERT_AND_RETURN(node != nullptr, false); + OE_SOFT_ASSERT_AND_RETURN(drawable != nullptr, false); + OE_PROFILING_ZONE; + + // convert all primitive sets to indexed primitives + //osgUtil::Optimizer o; + //o.optimize(node, o.INDEX_MESH); + + Chonk::Ptr chonk; + + // first count up the memory we need and allocate it + Counter counter; + node->accept(counter); + if (counter._numElements > 0 && counter._numVerts > 0) + { + chonk = Chonk::create(); + chonk->_vbo_store.reserve(counter._numVerts); + chonk->_ebo_store.reserve(counter._numElements); + } + + Ripper ripper(chonk.get(), drawable, _textures.get(), _getOrCreateTexture); + node->accept(ripper); + + if (chonk && !chonk->_ebo_store.empty()) + { + chonk->_lods.push_back({ 0u, chonk->_ebo_store.size(), 0.0f, FLT_MAX }); + chonk->_box.init(); + drawable->add(chonk); + } + + return true; +} + + +bool +ChonkDrawable::add(osg::Node* node, ChonkFactory& factory) +{ + OE_SOFT_ASSERT_AND_RETURN(node != nullptr, false); + OE_PROFILING_ZONE; + + // convert all primitive sets to indexed primitives + //osgUtil::Optimizer o; + //o.optimize(node, o.INDEX_MESH); + + return factory.load(node, this); } @@ -1327,3 +1439,38 @@ ChonkRenderBin::releaseSharedGLObjects(osg::State* state) if (proto->_cullSS.valid()) proto->_cullSS->releaseGLObjects(state); } + + +ChonkFactory::GetOrCreateFunction ChonkFactory::createWeakTextureCacheFunction( + std::vector& cache, + std::mutex& cache_mutex) +{ + return [&cache, &cache_mutex](osg::Texture* osgTex, bool& isNew) + { + std::lock_guard lock(cache_mutex); + auto* image = osgTex->getImage(0); + for (auto iter = cache.begin(); iter != cache.end(); ) + { + Texture::Ptr cache_entry = iter->lock(); + if (cache_entry) + { + if (ImageUtils::areEquivalent(image, cache_entry->osgTexture()->getImage(0))) + { + isNew = false; + return cache_entry; + } + ++iter; + } + else + { + // dead entry, remove it + iter = cache.erase(iter); + } + } + + isNew = true; + auto new_texture = Texture::create(osgTex); + cache.emplace_back(Texture::WeakPtr(new_texture)); + return new_texture; + }; +} \ No newline at end of file diff --git a/src/osgEarth/FeatureModelGraph b/src/osgEarth/FeatureModelGraph index 1b4f12a47b..5239ce220f 100644 --- a/src/osgEarth/FeatureModelGraph +++ b/src/osgEarth/FeatureModelGraph @@ -212,7 +212,8 @@ namespace osgEarth { namespace Util optional _minRange; optional _maxRange; osg::ref_ptr _textures; - std::unordered_map _texturesCache; + mutable std::vector _texturesCache; + mutable std::mutex _texturesCacheMutex; std::atomic_int _cacheReads; std::atomic_int _cacheHits; diff --git a/src/osgEarth/FeatureModelGraph.cpp b/src/osgEarth/FeatureModelGraph.cpp index 9e8b59c6e4..a110f11275 100644 --- a/src/osgEarth/FeatureModelGraph.cpp +++ b/src/osgEarth/FeatureModelGraph.cpp @@ -418,11 +418,19 @@ FeatureModelGraph::FeatureModelGraph(const FeatureModelOptions& options) : void FeatureModelGraph::setUseNVGL(bool value) { - if (value == true && GLUtils::useNVGL()) + if (value == true && GLUtils::useNVGL() && !_textures.valid()) { _textures = new TextureArena(); - _textures->setBindingPoint(1); getOrCreateStateSet()->setAttribute(_textures, 1); + + // auto release requires that we install this update callback! + _textures->setAutoRelease(true); + + addUpdateCallback(new LambdaCallback<>([this](osg::NodeVisitor& nv) + { + _textures->update(nv); + return true; + })); } } @@ -1710,40 +1718,51 @@ FeatureModelGraph::createOrUpdateNode(FeatureCursor* cursor, { // simple caching function to share textures across requests static std::mutex cache_mutex; + const auto cache_function = [&](osg::Texture* osgTex, bool& isNew) { std::lock_guard lock(cache_mutex); - auto it = this->_texturesCache.find(osgTex); - if (it != _texturesCache.end()) + auto* image = osgTex->getImage(0); + for (auto iter = _texturesCache.begin(); iter != _texturesCache.end(); ) { - isNew = false; - return it->second; - } - else - { - isNew = true; - auto t = Texture::create(osgTex); - _texturesCache[osgTex] = t; - return t; + Texture::Ptr cache_entry = iter->lock(); + if (cache_entry) + { + if (ImageUtils::areEquivalent(image, cache_entry->osgTexture()->getImage(0))) + { + isNew = false; + return cache_entry; + } + ++iter; + } + else + { + // dead entry, remove it + iter = _texturesCache.erase(iter); + } } + + isNew = true; + auto new_texture = Texture::create(osgTex); + _texturesCache.emplace_back(Texture::WeakPtr(new_texture)); + return new_texture; }; auto xform = findTopMostNodeOfType(output.get()); - auto root = xform && xform->getNumChildren() > 0 ? xform->getChild(0) : output.get(); - // Convert the geometry into chonks ChonkFactory factory(_textures); - factory.setGetOrCreateFunction(cache_function); + + factory.setGetOrCreateFunction( + ChonkFactory::createWeakTextureCacheFunction( + _texturesCache, _texturesCacheMutex)); osg::ref_ptr drawable = new ChonkDrawable(); if (xform) { for (unsigned i = 0; i < xform->getNumChildren(); ++i) { - auto chonk = Chonk::create(); - if (chonk->add(xform->getChild(i), factory)) - drawable->add(chonk); + drawable->add(xform->getChild(i), factory); } xform->removeChildren(0, xform->getNumChildren()); xform->addChild(drawable); @@ -1751,14 +1770,11 @@ FeatureModelGraph::createOrUpdateNode(FeatureCursor* cursor, } else { - auto chonk = Chonk::create(); - if (chonk->add(output.get(), factory)) + if (drawable->add(output.get(), factory)) { - drawable->add(chonk); output = drawable; } } - } return ok; diff --git a/src/osgEarth/TextureArena b/src/osgEarth/TextureArena index 5bca34c9bf..000ce764ee 100644 --- a/src/osgEarth/TextureArena +++ b/src/osgEarth/TextureArena @@ -212,11 +212,12 @@ namespace osgEarth mutable TextureVector _textures; mutable std::unordered_map _textureIndices; mutable std::unordered_set _dynamicTextures; - bool _autoRelease; - unsigned _bindingPoint; - bool _useUBO; - mutable int _releasePtr; - unsigned _maxDim; + + bool _autoRelease = false; + unsigned _bindingPoint = 1u; + bool _useUBO = false; + mutable int _releasePtr = 0; + unsigned _maxDim = 65536u; mutable Mutex _m; diff --git a/src/osgEarth/TextureArena.cpp b/src/osgEarth/TextureArena.cpp index c521d7aee3..27eb82258c 100644 --- a/src/osgEarth/TextureArena.cpp +++ b/src/osgEarth/TextureArena.cpp @@ -513,12 +513,7 @@ Texture::releaseGLObjects(osg::State* state, bool force) const #define LC "[TextureArena] " -TextureArena::TextureArena() : - _autoRelease(false), - _bindingPoint(5u), - _useUBO(false), - _releasePtr(0), - _maxDim(65536u) +TextureArena::TextureArena() { // Keep this synchronous w.r.t. the render thread since we are // going to be changing things on the fly diff --git a/src/osgEarth/XYZModelGraph b/src/osgEarth/XYZModelGraph index c82dbc576e..a024ea51d8 100644 --- a/src/osgEarth/XYZModelGraph +++ b/src/osgEarth/XYZModelGraph @@ -24,6 +24,10 @@ #include #include #include +#include + +#include +#include namespace osgEarth { @@ -37,6 +41,9 @@ namespace osgEarth { void setOwnerName(const std::string& value); + //! Whether to attempt to use NVGL extensions + void setUseNVGL(bool value); + public: // SimplePager virtual osg::ref_ptr createNode(const TileKey& key, ProgressCallback* progress) override; @@ -47,6 +54,9 @@ namespace osgEarth { bool _invertY; osg::ref_ptr< osgEarth::StateSetCache > _statesetCache; osg::ref_ptr< osgDB::Options > _options; + osg::ref_ptr< TextureArena > _textures; + mutable std::vector _texturesCache; + mutable std::mutex _texturesCacheMutex; }; } diff --git a/src/osgEarth/XYZModelGraph.cpp b/src/osgEarth/XYZModelGraph.cpp index 44b3303493..382e6b9471 100644 --- a/src/osgEarth/XYZModelGraph.cpp +++ b/src/osgEarth/XYZModelGraph.cpp @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include using namespace osgEarth; @@ -35,6 +38,25 @@ XYZModelGraph::XYZModelGraph(const osgEarth::Map* map, const Profile* profile, c _statesetCache = new StateSetCache(); } +void +XYZModelGraph::setUseNVGL(bool value) +{ + if (value == true && GLUtils::useNVGL() && !_textures.valid()) + { + _textures = new TextureArena(); + getOrCreateStateSet()->setAttribute(_textures, 1); + + // auto release requires that we install this update callback! + _textures->setAutoRelease(true); + + addUpdateCallback(new LambdaCallback<>([this](osg::NodeVisitor& nv) + { + _textures->update(nv); + return true; + })); + } +} + void XYZModelGraph::setOwnerName(const std::string& value) { @@ -81,7 +103,73 @@ XYZModelGraph::createNode(const TileKey& key, ProgressCallback* progress) osg::ref_ptr< osg::Node > node = myUri.readNode(_options.get()).getNode(); if (node.valid()) { - osgEarth::Registry::shaderGenerator().run(node.get(), _statesetCache); + if (_textures.valid()) + { + // simple caching function to share textures across requests + static std::mutex cache_mutex; + + const auto cache_function = [&](osg::Texture* osgTex, bool& isNew) + { + std::lock_guard lock(cache_mutex); + auto* image = osgTex->getImage(0); + for (auto iter = _texturesCache.begin(); iter != _texturesCache.end(); ) + { + Texture::Ptr cache_entry = iter->lock(); + if (cache_entry) + { + if (ImageUtils::areEquivalent(image, cache_entry->osgTexture()->getImage(0))) + { + isNew = false; + return cache_entry; + } + ++iter; + } + else + { + // dead entry, remove it + iter = _texturesCache.erase(iter); + } + } + + isNew = true; + auto new_texture = Texture::create(osgTex); + _texturesCache.emplace_back(Texture::WeakPtr(new_texture)); + return new_texture; + }; + + + auto xform = findTopMostNodeOfType(node.get()); + + // Convert the geometry into chonks + ChonkFactory factory(_textures); + + factory.setGetOrCreateFunction( + ChonkFactory::createWeakTextureCacheFunction( + _texturesCache, _texturesCacheMutex)); + + osg::ref_ptr drawable = new ChonkDrawable(); + if (xform) + { + for (unsigned i = 0; i < xform->getNumChildren(); ++i) + { + drawable->add(xform->getChild(i), factory); + } + xform->removeChildren(0, xform->getNumChildren()); + xform->addChild(drawable); + node = xform; + } + else + { + if (drawable->add(node.get(), factory)) + { + node = drawable; + } + } + } + else + { + osgEarth::Registry::shaderGenerator().run(node.get(), _statesetCache); + } return node.release(); } return nullptr; diff --git a/src/osgEarth/XYZModelLayer.cpp b/src/osgEarth/XYZModelLayer.cpp index 2cea7db2b1..7a10a96af2 100644 --- a/src/osgEarth/XYZModelLayer.cpp +++ b/src/osgEarth/XYZModelLayer.cpp @@ -208,6 +208,7 @@ XYZModelLayer::create() xyzGraph->setMaxLevel(*_options->maxLevel()); xyzGraph->setRangeFactor(*_options->rangeFactor()); xyzGraph->setSceneGraphCallbacks(getSceneGraphCallbacks()); + xyzGraph->setUseNVGL(options().useNVGL().get()); xyzGraph->build(); _root->removeChildren(0, _root->getNumChildren()); diff --git a/tests/osm.earth b/tests/osm.earth index 1d4d9d19a0..cca6fcd55d 100644 --- a/tests/osm.earth +++ b/tests/osm.earth @@ -14,11 +14,19 @@ osgEarth Sample - OpenStreetMap Features - + + + + + https://pelican-public.s3.amazonaws.com/buildings_mak/{z}/{x}/{y}.osgb + 14 + 14 + spherical-mercator + - + diff --git a/tests/osm_buildings.xml b/tests/osm_buildings.xml index 1b0b7aa674..ab2c763018 100644 --- a/tests/osm_buildings.xml +++ b/tests/osm_buildings.xml @@ -1,5 +1,5 @@ - true + false true 15000 2000 @@ -9,7 +9,8 @@ - ../data/resources/textures_us/catalog_atlas.osgb.xml + + ../data/resources/textures_us/catalog.xml