Canvas troubleshooting: Difference between revisions
Line 140: | Line 140: | ||
=== Inspecting a Canvas Group === | === Inspecting a Canvas Group === | ||
To inspect the structure of the <code>root</code> group, all you need to do is using <code>debug.dump</code>: | To inspect the structure of the <code>root</code> group, all you need to do is using <code>debug.dump()</code>: | ||
<syntaxhighlight lang="nasal"> | <syntaxhighlight lang="nasal"> |
Revision as of 19:14, 16 February 2015
The FlightGear forum has a subforum related to: Canvas |
Objective
This article is intended to be a collection of tips, code snippets and patches for helping troubleshoot Canvas related issues, i.e. debugging, profiling and optimizing Canvas based efforts. Usually, there's plenty of optimization potential left even in scripting space.
But at the end of the article, you'll also find a few C++ patches for SG/FG adding features to the Canvas system for exposing additional functionality to each Canvas/element, i.e. to help disable Canvas entirely, to track update/rendering times per Element or to get comprehensive OSG stats for each active Canvas camera.
Background
if we don't look at improving performance right now, we cannot realistically replace the map dialog or the navdisplay
|
I'm currently working on this one (lazy updates/rendering). I have some working code, but it's very hackish. So if anyone has experiences in render-to-texture on demand in OSG any hint is appreciated
|
Canvas itself is a great piece of technology, but using it to implement complex MFDs is like asking someone to create an object-oriented and multi-threaded application using assembly language: it is possible, but very tedious and requires tons of experience and expertise. This is basically the reason why the most generic Nasal/Canvas code was typically written by people already familiar with FG/SG and OSG internals (i.e. core developers).
— Hooray (Mon Feb 02). Re: Project Farmin [Garmin Flightdeck Frame work].
(powered by Instant-Cquotes) |
Debugging Canvas code
Note For the sake of simplicity, we are going to work with a simple code snippet showing a Canvas GUI dialog. |
The following snippet is taken from Canvas Snippets, it can be copied/pasted into the Nasal Console for testing purposes.
# create a new window, dimensions are 320 x 160, using the dialog decoration (i.e. titlebar)
var window = canvas.Window.new([320,160],"dialog");
# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
# Using specific css colors would also be possible:
# myCanvas.set("background", "#ffaac0");
# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();
Canvas Status
Modifying the Property Browser to show a Canvas preview
The property browser GUI dialog can be easily extended to contain a Canvas widget for previewing/inspecting canvas texture at run-time using the following changes:
You can also create a Canvas dialog showing a preview of all active Canvas textures using 10-15 lines of code, for example see:
var (width, height) = (640,480);
var window = canvas.Window.new([width,height],"dialog");
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
var root = myCanvas.createGroup();
var (x,y) = (30,30);
var size = 128;
var padding = 30;
foreach(var c; props.globals.getNode('canvas/by-index').getChildren('texture') ) {
if (c.getPath() == myCanvas.getPath()) continue; # skip own Canvas
var child=root.createChild("image")
.setFile( 'canvas://'~c.getPath() )
.setTranslation(x,y)
.setSize(size,size);
x += size + padding;
if (x >= width) {
y += size;
x = padding;
}
}
Inspecting a Canvas Group
To inspect the structure of the root
group, all you need to do is using debug.dump()
:
debug.dump(root._node);
to write the structure of the property sub-tree to a file, you can use io.write_properties()
like this:
var filename = getprop('/sim/fg-hime')~'CanvasRootGroup.xml';
io.write_properties(filename, root._node);
Inspecting a whole Canvas
Equally, you can inspect the structure of the whole canvas by using something like this:
props.dump( myCanvas._node );
The output (shown on the console) will look something like this:
texture[2] {NONE} = nil texture[2]/status {INT} = 1 texture[2]/status-msg {STRING} = Creation pending... texture[2]/blend-source-rgb {STRING} = src-alpha texture[2]/view {DOUBLE} = 320 texture[2]/view[1] {DOUBLE} = 160 texture[2]/size {DOUBLE} = 320 texture[2]/size[1] {DOUBLE} = 160 texture[2]/placement {NONE} = nil texture[2]/placement/type {STRING} = window texture[2]/placement/id {STRING} = 1 texture[2]/blend-destination-rgb {STRING} = one-minus-src-alpha texture[2]/blend-source-alpha {STRING} = zero texture[2]/blend-destination-alpha {STRING} = one texture[2]/data-focused {LONG} = 1 texture[2]/background {STRING} = rgba(242,241,240,1) texture[2]/group {NONE} = nil
to write the structure of the whole Canvas sub-tree to a file, you can use io.write_properties()
like this:
var filename = getprop('/sim/fg-home')~'/Export/WholeCanvas.xml';
io.write_properties(filename, props.wrap(myCanvas._node));
The output, written to $FG_HOME/Export/WholeCanvas.xml, would look like this:
<?xml version="1.0"?>
<PropertyList>
<status type="int">1</status>
<status-msg type="string">Creation pending...</status-msg>
<blend-source-rgb type="string">src-alpha</blend-source-rgb>
<view type="double">320</view>
<view n="1" type="double">160</view>
<size type="double">320</size>
<size n="1" type="double">160</size>
<placement>
<type type="string">window</type>
<id type="string">1</id>
</placement>
<blend-destination-rgb type="string">one-minus-src-alpha</blend-destination-rgb>
<blend-source-alpha type="string">zero</blend-source-alpha>
<blend-destination-alpha type="string">one</blend-destination-alpha>
<data-focused type="long">1</data-focused>
<background type="string">rgba(242,241,240,1)</background>
</PropertyList>
Do note that this is an entirely valid PropertyList encoded XML file, which means that it could be also used to unserialize the Canvas, i.e. reading it back into FG to create a new valid Canvas and display the texture there. The main limitation here being that this would be a "passive" Canvas only, without any listeners/timers etc being bound obviously, because just the visual aspects of the original Canvas texture would be re-created, but none of the logics used to originally create/animate the Canvas (think PFD, ND, CDU or Map):
var filename = getprop('/sim/fg-home')~'/Export/WholeCanvas.xml';
io.read_properties(filename, '/canvas/by-index/texture[100]');
However, even with those restrictions, serializing a Canvas to an XML file may still be useful - i.e. to make bug reports attached with with reproducible settings using a simple XML file.
Profiling Canvas code
Optimizing Canvas code
Useful C++ changes
Note The following patches are intended to help better understand what's going on behind the scenes. They are intended to be regularly rebased onto SG/FG 'next'. In the mid-term, our hope is to ensure that people working on Nasal/Canvas related features get a better understanding about when, where and why Canvas performance is affected, and how to restructure their code accordingly to make better use of Canvas - as well as identify potential C++ extensions to further improve Canvas performance. |
Adding support for wireframe mode
To enable wireframe mode, we only need to change the osg::StateSet accordingly. Internally, each Canvas element contains a osg::MatrixTransform
[2] node - there's a virtual method getOrCreateStateSet()
exposed returning the StateSet for each element, so that we can directly it:
osg::StateSet *state = getOrCreateStateSet();
osg::PolygonMode *polyModeObj;
polyModeObj = dynamic_cast< osg::PolygonMode* >
( state->getAttribute( osg::StateAttribute::POLYGONMODE ));
if ( !polyModeObj ) {
polyModeObj = new osg::PolygonMode;
state->setAttribute( polyModeObj );
}
polyModeObj->setMode( osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE );
This change could be applied globally for each Canvas, or optionally per Canvas Element - the latter is more useful for troubleshooting things without affecting all active Canvas textures.
The corresponding diff for $SG_SRC adding this as a property-controlled option, looks like this:
Adding support for disabling textures
Analogous to the previous example, we can modify the osg::StateSet for a Canvas/CanvasElement to disable texturing, too:
osg::StateSet *state = getOrCreateStateSet();
state->setTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::PROTECTED | osg::StateAttribute::OFF);
The diff adding this as a run-time option to the Canvas system, looks like this:
Serializing a Canvas to disk (as raster image)
The following C++ code can be used to easily serialize a Canvas to disk - this can be useful for making bug reports, without having to take/edit a full screen shot, it's based on the existing screen shot code that's been slightly adapted:
Dumping Canvas scene graphs to disk
Here's the required SimGear changes to add a new method for dumping the scene graph for each Canvas to a file:
Here's the FlightGear part for the SimGear changes above exposing the new API via canvas.dumpSceneGraph()
:
diff --git a/src/Scripting/NasalCanvas.cxx b/src/Scripting/NasalCanvas.cxx
index 4fa7652..2b63f02 100644
--- a/src/Scripting/NasalCanvas.cxx
+++ b/src/Scripting/NasalCanvas.cxx
@@ -478,7 +478,8 @@ naRef initNasalCanvas(naRef globals, naContext c)
.method("dispatchEvent", &sc::Canvas::dispatchEvent)
.method("setLayout", &sc::Canvas::setLayout)
.method("setFocusElement", &sc::Canvas::setFocusElement)
- .method("clearFocusElement", &sc::Canvas::clearFocusElement);
+ .method("clearFocusElement", &sc::Canvas::clearFocusElement)
+ .method("dumpSceneGraph", &sc::Canvas::dumpSceneGraph);
canvas_module.set("_newCanvasGhost", f_createCanvas);
canvas_module.set("_getCanvasGhost", f_getCanvas);
This is what a simple Canvas scene graph may look like:
Tracking update frequency per Canvas/Element
This is intended to help people better understand how frequently a Canvas is being updated, which happens automatically once a property in the sub-tree is modified (usually involving timers and listeners):
Tracking update/rendering duration per Canvas/Element
This is a fairly useful thing to do, while also being pretty easy - all that is needed are two SimGear classes:
- PropertyObject<>
- SGTimeStamp
These two can be used to time the duration of each method invocation and automatically write it to the property tree. Which in turn can then be viewed/processed by the property browser and/or Nasal respectively to identify comparatively expensive canvas elements/groups.
Adding draw masks for Canvas
There's so called "draw-masks" which are property-controlled switches for enabling/disabling rendering of certain scene details, including 1) scenery/terrain, 2) aircraft, 3) models, 4) clouds. This can be used for troubleshooting performance issues - you can basically toggle individual scene graphs on/off, to see if/how performance is affected. For example, if performance improves dramatically by disabling the terrain, you are mainly affected scenery complexity. Equally, disabling the (main) aircraft, will tell you if it's the complexity of the 777 3D model (cockpit).
This would work analogous to our existing "draw-masks", (as per the minimal startup profile detailed on the wiki) - basically, more and more features would get dedicated properties with draw-masks to disable/enable rendering and optionally customize things - this isn't too difficult to do, and it doesn't necessarily involve touching tons of STG/BTG files - it's mainly a change that would involve SimGear/FG, so that buildings (and other heavy stuff) would be loaded into dedicated sub-scene graph that can be easily disabled using an osg::switch node - here's a code snippet demonstrating the concept by disabling rendering of the sun/sky.
We've used these for doing some more profiling on the netbook that I mentioned to help better understand where performance is spent, not just in terms of subsystems, but also at the rendering level (PUI!) -this is line with James' original "draw-masks" commit and uses the same property location for new draw masks - I think this could be a useful extension for various reasons, because we can simply keep stuff disabled while booting - and also use it for troubleshooting.
diff --git a/src/Viewer/renderer.cxx b/src/Viewer/renderer.cxx
index 2c4d5c7..d3ba9a6 100644
--- a/src/Viewer/renderer.cxx
+++ b/src/Viewer/renderer.cxx
@@ -82,7 +82,10 @@
#include <simgear/scene/tgdb/pt_lights.hxx>
#include <simgear/scene/tgdb/userdata.hxx>
#include <simgear/structure/OSGUtils.hxx>
+
#include <simgear/props/props.hxx>
+#include <simgear/props/propertyObject.hxx>
+
#include <simgear/timing/sg_time.hxx>
#include <simgear/ephemeris/ephemeris.hxx>
#include <simgear/math/sg_random.h>
@@ -380,9 +383,60 @@ public:
static bool scenery_enabled;
};
-
bool FGScenerySwitchCallback::scenery_enabled = false;
+// update callback for the sky switch node
+struct FGSkySwitchCallback : public osg::NodeCallback {
+ FGSkySwitchCallback() : _enabled("/sim/rendering/draw-mask/sky") {}
+
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ assert(dynamic_cast<osg::Switch*>(node));
+ osg::Switch* sw = static_cast<osg::Switch*>(node);
+
+ sw->setValue(0, _enabled);
+ if (!_enabled)
+ return;
+ traverse(node, nv);
+ }
+private:
+simgear::PropertyObject<bool> _enabled;
+};
+
+// update callback for the GUI (old) switch node
+struct FGOldGUISwitchCallback : public osg::NodeCallback {
+ FGOldGUISwitchCallback() : _enabled("/sim/rendering/draw-mask/old-gui") {}
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ assert(dynamic_cast<osg::Switch*>(node));
+ osg::Switch* sw = static_cast<osg::Switch*>(node);
+
+ sw->setValue(0, _enabled);
+ if (!_enabled)
+ return;
+ traverse(node, nv);
+ }
+private:
+simgear::PropertyObject<bool> _enabled;
+};
+
+// update callback for the GUI (old) switch node
+struct FGSunlightSwitchCallback : public osg::NodeCallback {
+ FGSunlightSwitchCallback() : _enabled("/sim/rendering/draw-mask/sunlight") {}
+ virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+ {
+ assert(dynamic_cast<osg::Switch*>(node));
+ osg::Switch* sw = static_cast<osg::Switch*>(node);
+
+ sw->setValue(0, _enabled);
+ if (!_enabled)
+ return;
+ traverse(node, nv);
+ }
+private:
+simgear::PropertyObject<bool> _enabled;
+};
+
FGRenderer::FGRenderer() :
_sky(NULL),
_ambientFactor( new osg::Uniform( "fg_SunAmbientColor", osg::Vec4f() ) ),
@@ -1483,7 +1537,8 @@ FGRenderer::setupView( void )
lightSource->setLocalStateSetModes(osg::StateAttribute::ON);
lightSource->setUpdateCallback(new FGLightSourceUpdateCallback);
_viewerSceneRoot->addChild(lightSource);
-
+
+
// we need a white diffuse light for the phase of the moon
osg::ref_ptr<LightSource> sunLight = new osg::LightSource;
sunLight->setName("sunLightSource");
@@ -1501,7 +1556,11 @@ FGRenderer::setupView( void )
sunLight->addChild(skyGroup);
if ( _classicalRenderer ) {
- _root->addChild(sunLight);
+ osg::Switch *sw = new osg::Switch;
+ sw->setName("sunLightSwitch");
+ sw->setUpdateCallback( new FGSunlightSwitchCallback);
+ sw->addChild(sunLight);
+ _root->addChild(sw);
}
osg::Group* sceneGroup = globals->get_scenery()->get_scene_graph();
@@ -1531,19 +1590,25 @@ FGRenderer::setupView( void )
stateSet->setAttributeAndModes(fog);
stateSet->setUpdateCallback(new FGFogEnableUpdateCallback);
+
// plug in the GUI
osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault());
if (guiCamera) {
+ osg::Switch* sw = new osg::Switch;
+ sw->setName("OldGUISwitch");
+ sw->setUpdateCallback(new FGOldGUISwitchCallback);
+
osg::Geode* geode = new osg::Geode;
geode->addDrawable(new SGHUDDrawable);
geode->addDrawable(new SGPuDrawable);
+ sw->addChild(geode);
+ sw->addChild( FGPanelNode::create2DPanelNode() );
// Draw first (eg. before Canvas GUI)
- guiCamera->insertChild(0, geode);
- guiCamera->insertChild(0, FGPanelNode::create2DPanelNode());
+ guiCamera->insertChild(0, sw);
}
- osg::Switch* sw = new osg::Switch;
+ osg::Switch *sw = new osg::Switch;
sw->setName("scenerySwitch");
sw->setUpdateCallback(new FGScenerySwitchCallback);
sw->addChild(_root.get());
@@ -1552,8 +1617,13 @@ FGRenderer::setupView( void )
// because, in theory, they don't want the same default state set
// as the rest of the scene. This may not be true in practice.
if ( _classicalRenderer ) {
- _viewerSceneRoot->addChild(_sky->getCloudRoot());
- _viewerSceneRoot->addChild(FGCreateRedoutNode());
+ sw = new osg::Switch;
+ sw->setName("skySwitch");
+ sw->setUpdateCallback(new FGSkySwitchCallback);
+ sw->addChild(_sky->getCloudRoot());
+ sw->addChild(FGCreateRedoutNode());
+
+ _viewerSceneRoot->addChild(sw);
}
// Attach empty program to the scene root so that shader programs
@@ -1625,7 +1695,8 @@ FGRenderer::update( ) {
: osg::Vec4(0, 0, 0, 1);
camera->setClearColor(clear_color);
- updateSky();
+ if (fgGetBool("/sim/rendering/draw-mask/sky",true))
+ updateSky();
// need to call the update visitor once
_frameStamp->setCalendarTime(*globals->get_time_params()->getGmt());
diff --git a/preferences.xml b/preferences.xml
index dbbbe2a..bc35ef3 100644
--- a/preferences.xml
+++ b/preferences.xml
@@ -224,10 +224,13 @@ Started September 2000 by David Megginson, david@megginson.com
non-cockpit + aircraft elements. Use draw-mask instead. -->
<draw-otw type="bool">true</draw-otw>
<draw-mask>
+ <sunlight type="bool">true</sunlight>
<terrain type="bool">true</terrain>
<models type="bool">true</models>
<aircraft type="bool">true</aircraft>
<clouds type="bool">true</clouds>
+ <sky type="bool">true</sky>
+ <old-gui type="bool">true</old-gui>
</draw-mask>
<shadows-ac type="bool" userarchive="y">false</shadows-ac>
We are probably going to add a few more draw-masks, also for Canvas - not because I believe that we need to run FG without Canvas :lol: , but to provide stats/evidence for benchmarking purposes, i.e. we can tell people to use those draw masks to disable all CanvasGUI/CanvasScenery/CanvasAircraft rendering and watch their frame rate/spacing accordingly. The following patch is intended to help determine the rendering overhead of Canvas by adding optional draw-masks for disabling rendering of Canvas textures via their corresponding placements (aircraft, scenery and GUI):
Under the hood, the corresponding Nasal/C++ code updating Canvas textures would obviously still be running - it would just be rendering that is explicitly disabled here.
For the FGCanvas mode, this just means that sky/sunlight and PUI rendering can be completely disabled for even better performance/appearance, i.e. Nasal/Canvas are up and running in under 5 seconds here normally. Likewise, this is a good thing for debugging and regression testing, i.e. to keep certain rendering features completely disabled - for example so that only Canvas related OSG/OpenGL calls show up in the gDebugger profile, i.e. much more fine-grained info, without having to patch FG.
Hooking up Canvas to the Osg Stats
Using Osg::Optimizer for complex Canvases
Once a complex Canvas texture (MFD) has been set up, the following patch can be used for invoking osgUtil::Optimizer() to finalize and optimize the scene graph:
Experimenting with Osg::Simplifier
We can extend each Canvas element inheriting from the CanvasElement base class to have its own osg::Simplifier sub-class implementing 2D simplification using CGAL. Such nodes (CanvasGroups) could in turn be used for procedurally creating LOD nodes for different viewer distances.
Tracking RAM utilization per Canvas
Tracking update/cull and draw per Canvas or Element
Patching CanvasMgr to use CompositeViewer internally
With Canvas textures typically not rendering any scene/scenery data (terrain), we don't necessarily need those Cameras to render within the main viewer. This is intended to help better leverage OSG-level concurrency support by using a separate CompositeViewer instance to render Canvas textures, without having to change the main OSGViewer and its threading mode: