Created
December 17, 2021 18:56
-
-
Save jasonbeverage/59dd58d3441afe40025ddb99f3675ed7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* -*-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