Skip to content

Instantly share code, notes, and snippets.

@jasonbeverage
Created December 17, 2021 18:56
Show Gist options
  • Save jasonbeverage/59dd58d3441afe40025ddb99f3675ed7 to your computer and use it in GitHub Desktop.
Save jasonbeverage/59dd58d3441afe40025ddb99f3675ed7 to your computer and use it in GitHub Desktop.
/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2020 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include <osgEarth/ImGui/ImGui>
#include <osgEarth/EarthManipulator>
#include <osgEarth/ExampleResources>
#include <osgEarth/Metrics>
#include <osgViewer/Viewer>
#include <osgGA/FlightManipulator>
#include <osg/TexGenNode>
#include <osg/ClipNode>
#include <osg/OcclusionQueryNode>
#include <chrono>
#include <sstream>
#define LC "[imgui] "
using namespace osgEarth;
using namespace osgEarth::Util;
using namespace osgEarth::GUI;
#define CULL_TIMING
struct TraversalTime
{
std::string message;
osg::Node* node;
std::chrono::nanoseconds duration;
std::chrono::nanoseconds selfDuration;
std::chrono::nanoseconds childDuration;
std::vector< TraversalTime > children;
double getSelfDurationPercentage()
{
if (duration.count() == 0) return 0;
return (double)selfDuration.count() / (double)duration.count();
}
double getChildDurationPercentage()
{
if (duration.count() == 0) return 0;
return (double)childDuration.count() / (double)duration.count();
}
void computeStats()
{
childDuration = std::chrono::nanoseconds{ 0 };
for (auto &c : children)
{
childDuration += c.duration;
}
selfDuration = duration - childDuration;
for (auto &c : children)
{
c.computeStats();
}
}
};
struct TraversalStats
{
unsigned int count = 0;
std::chrono::nanoseconds duration;
void reset()
{
count = 0;
duration = std::chrono::nanoseconds{ 0 };
}
};
static TraversalTime s_traversalTime;
static TraversalTime *s_traversalTimeParent = nullptr;
static TraversalStats s_traversalStats;
static std::map< std::string, TraversalStats > s_byType;
static std::chrono::nanoseconds s_totalDuration{ 0 };
static void computeGlobalStats(TraversalTime& time)
{
if (time.node)
{
s_byType[typeid(*time.node).name()].count++;
s_byType[typeid(*time.node).name()].duration += time.selfDuration;
s_totalDuration += time.selfDuration;
}
for (auto &c : time.children)
{
computeGlobalStats(c);
}
}
static bool collect_timing = false;
//! Custom CullVisitor to test the getDistanceToViewPoint override approach.
struct MyCullVisitor : public osgUtil::CullVisitor
{
MyCullVisitor() : osgUtil::CullVisitor()
{
}
MyCullVisitor(const MyCullVisitor& rhs) :
osgUtil::CullVisitor(rhs)
{
}
virtual osgUtil::CullVisitor* clone() const
{
std::cout << "Creating cullvisitor" << std::endl;
return new MyCullVisitor(*this);
}
virtual void reset()
{
s_traversalTimeParent = &s_traversalTime;
s_traversalTime.children.clear();
s_byType.clear();
s_totalDuration = std::chrono::nanoseconds{ 0 };
osgUtil::CullVisitor::reset();
}
template<typename T>
void applyWithTiming(T& node)
{
if (collect_timing)
{
//++level;
auto startTime = std::chrono::high_resolution_clock::now();
TraversalTime t;
std::stringstream buf;
buf << typeid(node).name() << " " << node.getName();
t.message = buf.str();
t.node = &node;
TraversalTime* currentParent = s_traversalTimeParent;
// Push the parent
s_traversalTimeParent = &t;
osgUtil::CullVisitor::apply(node);
s_traversalTimeParent = currentParent;
auto endTime = std::chrono::high_resolution_clock::now();
t.duration = std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime);
currentParent->children.push_back(t);
}
else
{
osgUtil::CullVisitor::apply(node);
}
//s_byType[typeid(node).name()].count++;
//s_byType[typeid(node).name()].duration += t.duration;
//for (unsigned int i = 0; i < level; ++i) std::cout << " ";
//std::cout << typeid(node).name() << " " << node.getName() << " " << totalTime << "ns" << std::endl;
//--level;
}
virtual void apply(osg::Node& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Geode& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Drawable& drawable)
{
applyWithTiming(drawable);
}
virtual void apply(osg::Billboard& node)
{
applyWithTiming(node);
}
virtual void apply(osg::LightSource& node)
{
applyWithTiming(node);
}
virtual void apply(osg::ClipNode& node)
{
applyWithTiming(node);
}
virtual void apply(osg::TexGenNode& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Group& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Transform& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Projection& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Switch& node)
{
applyWithTiming(node);
}
virtual void apply(osg::LOD& node)
{
applyWithTiming(node);
}
virtual void apply(osg::ClearNode& node)
{
applyWithTiming(node);
}
virtual void apply(osg::Camera& node)
{
applyWithTiming(node);
}
virtual void apply(osg::OccluderNode& node)
{
applyWithTiming(node);
}
virtual void apply(osg::OcclusionQueryNode& node)
{
applyWithTiming(node);
}
};
class CullGUI : public BaseGUI
{
public:
CullGUI() : BaseGUI("Cull performance")
{
}
void draw(osg::RenderInfo& ri) override
{
if (!isVisible())
return;
s_traversalTime.computeStats();
computeGlobalStats(s_traversalTime);
ImGui::Begin(name(), visible());
ImGui::Checkbox("Collect stats", &collect_timing);
globalStats();
const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x;
static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
if (ImGui::BeginTable("Cull Traversal", 4, flags))
{
// The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On
//ImGui::TableSetupColumn("Node", ImGuiTableColumnFlags_NoHide);
//ImGui::TableSetupColumn("Duration", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
//ImGui::TableSetupColumn("Self", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
//ImGui::TableSetupColumn("Child", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
ImGui::TableSetupColumn("Node");
ImGui::TableSetupColumn("Duration");
ImGui::TableSetupColumn("Self");
ImGui::TableSetupColumn("Child");
ImGui::TableHeadersRow();
printTable(s_traversalTime);
ImGui::EndTable();
}
//print(s_traversalTime);
ImGui::End();
}
void globalStats()
{
unsigned int totalNodes = 0;
ImGui::Text("By Type");
ImGui::Separator();
ImGui::Text("Total duration %0.3f", s_totalDuration.count() / 1e6);
if (ImGui::BeginTable("By Type", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable))
{
ImGui::TableSetupColumn("Type");
ImGui::TableSetupColumn("Count");
ImGui::TableSetupColumn("Duration (ms)");
ImGui::TableSetupColumn("Percentage");
ImGui::TableHeadersRow();
for (auto& pair : s_byType)
{
ImGui::TableNextColumn();
ImGui::Text(pair.first.c_str());
ImGui::TableNextColumn();
ImGui::Text("%d", pair.second.count);
ImGui::TableNextColumn();
ImGui::Text("%0.3f ms", pair.second.duration.count() / 1e6);
ImGui::TableNextColumn();
ImGui::Text("%0.1f %%", ((double)pair.second.duration.count() / (double)s_totalDuration.count()) * 100.0);
totalNodes += pair.second.count;
}
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("Total Nodes %d", totalNodes);
ImGui::Separator();
}
void print(TraversalTime& time)
{
if (ImGui::TreeNode(time.node, "%s self=(%.1lf) children=(%.1lf)", time.message.c_str(), time.getSelfDurationPercentage() * 100.0, time.getChildDurationPercentage() * 100.0))
{
for (auto& c : time.children)
{
print(c);
}
ImGui::TreePop();
}
}
void printTable(TraversalTime& time)
{
ImGui::TableNextRow();
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth;
if (time.children.empty())
{
flags |= (ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen);
}
ImGui::TableNextColumn();
bool open = ImGui::TreeNodeEx(time.message.c_str(), flags);
ImGui::TableNextColumn();
ImGui::Text("%.2f ms", time.duration.count() / 1e6);
ImGui::TableNextColumn();
ImGui::Text("%2f ms / %.1f %%", time.selfDuration.count() / 1e6, time.getSelfDurationPercentage() * 100.0);
ImGui::TableNextColumn();
ImGui::Text("%2f ms / %.1f %%", time.childDuration.count() / 1e6, time.getChildDurationPercentage() * 100.0);
if (open)
{
for (auto &c : time.children)
{
printTable(c);
}
ImGui::TreePop();
}
}
};
int
usage(const char* name)
{
OE_NOTICE
<< "\nUsage: " << name << " file.earth" << std::endl
<< MapNodeHelper().usage() << std::endl;
return 0;
}
int
main(int argc, char** argv)
{
osgEarth::initialize();
osg::ArgumentParser arguments(&argc, argv);
if (arguments.read("--help"))
return usage(argv[0]);
#ifdef CULL_TIMING
osgUtil::CullVisitor::prototype() = new MyCullVisitor();
#endif
osgViewer::Viewer viewer(arguments);
viewer.setThreadingModel(viewer.SingleThreaded);
viewer.setCameraManipulator(new EarthManipulator(arguments));
// Call this to enable ImGui rendering.
// If you use the MapNodeHelper, call this first.
viewer.setRealizeOperation(new GUI::ApplicationGUI::RealizeOperation);
osg::ref_ptr<osg::Node> node = MapNodeHelper().loadWithoutControls(arguments, &viewer);
if (node.valid())
{
// Call this to add the GUI.
// Passing "true" tells it to install all the built-in osgEarth GUI tools.
// Put it on the front of the list so events don't filter
// through to other handlers.
auto gui = new GUI::ApplicationGUI(arguments, true);
#ifdef CULL_TIMING
gui->add(new CullGUI);
#endif
viewer.getEventHandlers().push_front(gui);
viewer.setSceneData(node);
return Metrics::run(viewer);
}
else
{
return usage(argv[0]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment