Canvas troubleshooting: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 9: Line 9:


* [https://www.packtpub.com/books/content/openscenegraph-methods-improving-rendering-efficiency OSG methods for improving rendering efficiency]
* [https://www.packtpub.com/books/content/openscenegraph-methods-improving-rendering-efficiency OSG methods for improving rendering efficiency]
* [http://3dcgtutorials.blogspot.com/2013/08/instancing-with-openscenegraph.html Instancing with OpenSceneGraph]
* [http://www.highperformancegraphics.org/wp-content/uploads/Steinlechner-LazySG.pdf Lazy Incremental Computation for Efficient Scene Graph Rendering]
* [http://www.highperformancegraphics.org/wp-content/uploads/Steinlechner-LazySG.pdf Lazy Incremental Computation for Efficient Scene Graph Rendering]


While these features are currently intended to be a just ''toolbox'' for better understanding Canvas internals, the hope in the mid-term is also that these can be used for determining future extensions/improvements to the Canvas system as a whole.
While these features are currently intended to be a just ''toolbox'' for better understanding Canvas internals, the hope in the mid-term is also that these can be used for determining future extensions/improvements to the Canvas system as a whole, to hopefully create more efficient scene graphs for Canvas based features.


Everybody is invited to help contribute to this article, be in the form of corrections, patches, feedback/advice or code snippets.
Everybody is invited to help contribute to this article, be in the form of corrections, patches, feedback/advice or code snippets.

Revision as of 15:54, 19 February 2015


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.

While there's usually plenty of optimization potential left in scripting space, 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.

These patches are primarily based on working through a few online articles on optimizing OSG performance and identifying missing hooks to better understand Canvas related performance issues, specifically:

While these features are currently intended to be a just toolbox for better understanding Canvas internals, the hope in the mid-term is also that these can be used for determining future extensions/improvements to the Canvas system as a whole, to hopefully create more efficient scene graphs for Canvas based features.

Everybody is invited to help contribute to this article, be in the form of corrections, patches, feedback/advice or code snippets.

Background

Cquote1.png I'm also interested in any performance issues. For example a canvas is always redrawn if any property changes within the current frame, even if the same value is just written again or changes are too small to be noticeable. Also if a property of a hidden element/group is changed, the canvas is redrawn. Maybe checking if properties have changed enough will gain some speed, but I'm not sure if this will be noticeable at all (only if always the same values are written to the tree...)
— TheTom (Tue Nov 12). Re: How to display Airport Chart?.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png 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
— TheTom (Fri Sep 21). Re: .
(powered by Instant-Cquotes)
Cquote2.png


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).

The real issue is accessibility - i.e. having things like XML, property tree, Nasal, effects, FDMs and even Canvas is great - but these are really just technology-enablers.

It's very much like driving a car or flying an airplane: you are given an enabling-technology, i.e. an interface to brake, accelerate, climb/descend etc - without having to understand how an engine works, or why an airplane flies. To sum it up, FlightGear is not much different from RL here - how many people flying airplanes and driving cars actually understand the underlying systems sufficiently to use them properly, i.e. to make some constraint like fuel efficiency ?

Whenever someone uses properties, XML, FDMs, Nasal, effects or Canvas - they only need to "understand" a fraction of the complexity involved here, and that comes at a cost obviously.

Let's say, the average contributor understands ~20% of the performance implications their work has (texturing, 3D modeling, scripting, XML, property rules, effects/shaders, FDM, Canvas) - in total, that means that even just per feature there's a 80% chance that people make fairly inefficient use of the resources available to them through these technology enablers.

So it always is a compromise - accessibility means that we're introducing abstraction layers and giving up control, so that certain stuff can be delegated to user space, which in turn means that we're also accepting the fact that people's work may slow down the whole flying experience until it's no longer flying, but a slide show.

Whenever we've seen dead-slow Nasal/Canvas code it was usually not the problem of Nasal or Canvas, but due to the people who wrote the code in the first place, including my own code by the way.

There's some really -sorry- stupid stuff done in various areas, that makes people think that Nasal and/or Canvas are generally slow. That is not true. Things like the m2000-5 or the extra500 are indeed slow (currently), but not primarily because of Nasal/Canvas, but because of the way Nasal/Canvas code is structured, as well as the resulting scene graph representation that isn't necessarily OSG-friendly.

Debugging Canvas code

Note  For the sake of simplicity, we are going to work with a simple code snippet showing a Canvas GUI dialog.
Snippets-canvas-dialog.png

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:

Slightly extended Property Browser to show a preview for each Canvas for troubleshooting/debugging purposes, as per [1].

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);

We can use a helper object to wrap the enable/disable logic, i.e. using a struct (pseudo code):

struct CanvasElementOption {
// abstract
virtual void enable() = 0;
virtual void disable() = 0;

private:
osg::StateSet _state = _element->getOrCreateStateSet();
};

struct CanvasElementTextureOption : public CanvasElementOption {
 
CanvasElementOption(CanvasElement* element) : _element(element) {}

void enable() {
 _state->setTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::PROTECTED | osg::StateAttribute::ON);
}

void disable() {
_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)

In OSG land, 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:

osg::Image* shot = new osg::Image();
shot->allocateImage(width, height, 24, GL_RGB, GL_UNSIGNED_BYTE);
camera->attach(osg::Camera::COLOR_BUFFER, shot);
osgDB::writeImageFile(*shot,"canvasImage.png");

In Canvas space, we can directly access the _texture, e.g.:

osg::ref_ptr<osg::Image> image = getTexture()->getImage();
osgDB::writeImageFile( *image, "canvasImage.png" );

Adapting this to render a Canvas to an image, and expose the whole thing via cppbind, gets us here:

And here are the corresponding changes for $FG_SRC:

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 element to a file (simply invoke the method on the top-level group to dump the whole canvas):

diff --git a/simgear/canvas/elements/CanvasElement.cxx b/simgear/canvas/elements/CanvasElement.cxx
index 6d8c930..6e0dc9b 100644
--- a/simgear/canvas/elements/CanvasElement.cxx
+++ b/simgear/canvas/elements/CanvasElement.cxx
@@ -222,6 +222,14 @@ namespace canvas
     }
   }
 
+  void Element::dumpSceneGraph(const std::string&filename)
+{
+       osgDB::writeNodeFile(*_transform.get(), filename);
+       SG_LOG(SG_GENERAL, SG_ALERT, "dumping Canvas scene graph to file:"<<filename);
+
+}
+
+
   //----------------------------------------------------------------------------
   void Element::onDestroy()
   {
diff --git a/simgear/canvas/elements/CanvasElement.hxx b/simgear/canvas/elements/CanvasElement.hxx
index 37e12de..5a2cec8 100644
--- a/simgear/canvas/elements/CanvasElement.hxx
+++ b/simgear/canvas/elements/CanvasElement.hxx
@@ -25,6 +25,8 @@
 #include <simgear/props/PropertyBasedElement.hxx>
 #include <simgear/misc/stdint.hxx> // for uint32_t
 
+#include <osgDB/WriteFile>
+
 #include <osg/BoundingBox>
 #include <osg/MatrixTransform>
 
@@ -212,6 +214,8 @@ namespace canvas
         return ElementPtr( new Derived(canvas, node, style, parent) );
       }
 
+
+      void dumpSceneGraph(const std::string& filename);
     protected:
 
       enum Attributes
@@ -276,6 +280,7 @@ namespace canvas
         return false;
       }
 
+
       /**
        * Register a function for setting a style specified by the given property
        *

Here's the FlightGear part for the SimGear changes above exposing the new API via ELEMENT.dumpSceneGraph():

diff --git a/src/Scripting/NasalCanvas.cxx b/src/Scripting/NasalCanvas.cxx
index 4fa7652..ffa8211 100644
--- a/src/Scripting/NasalCanvas.cxx
+++ b/src/Scripting/NasalCanvas.cxx
@@ -492,7 +492,8 @@ naRef initNasalCanvas(naRef globals, naContext c)
     .method("setFocus", &sc::Element::setFocus)
     .method("dispatchEvent", &sc::Element::dispatchEvent)
     .method("getBoundingBox", &sc::Element::getBoundingBox)
-    .method("getTightBoundingBox", &sc::Element::getTightBoundingBox);
+    .method("getTightBoundingBox", &sc::Element::getTightBoundingBox)
+    .method("dumpSceneGraph", &sc::Element::dumpSceneGraph);

And here's the Nasal code to test the new API via the Nasal Console:

var window = canvas.Window.new([320,160],"dialog");
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
var root = myCanvas.createGroup();

var myGroup = root.createChild('group', 'my group');
var textNode = myGroup.createChild("text")
        .setText("Some text")
        .setTranslation(20, 200)
        .set("alignment", "left-center")
        .set("character-size", 15)
        .set("font", "LiberationFonts/LiberationSans-Bold.ttf")
        .set("fill", "#ff0000");

textNode.dumpSceneGraph('textnode.osg');

The serialized scene graph can be easily viewed using the standalone osgviewer[3] tool like this

osgviewer textnode.osg

Doing this can be useful for comparing rendering performance between fgfs and osgviewer (i.e. via the osg on screen stats).

This is what a simple Canvas sub-scenegraph may look like:

MatrixTransform {
  nodeMask 0xffffffff
  cullingActive TRUE
  StateSet {
    rendering_hint DEFAULT_BIN
    renderBinMode OVERRIDE
    binNumber 0
    binName PreOrderBin
  }
  referenceFrame RELATIVE
  Matrix {
    1 0 0 0
    0 1 0 0
    0 0 1 0
    0 0 0 1
  }
  num_children 1
  Geode {
    nodeMask 0xffffffff
    cullingActive TRUE
    num_drawables 1
    osgText::Text {
      UniqueID Text_0
      StateSet {
        UniqueID StateSet_1
        rendering_hint TRANSPARENT_BIN
        renderBinMode USE
        binNumber 10
        binName DepthSortedBin
      }
      supportsDisplayList FALSE
      useDisplayList FALSE
      useVertexBufferObjects FALSE
      color 1 0 0 1
      font /home/hooray/sources/fgroot/Fonts/LiberationFonts/LiberationSans-Bold.ttf
      fontResolution 32 32
      characterSize 15 1
      characterSizeMode OBJECT_COORDS
      alignment LEFT_CENTER
      rotation 1 0 0 6.12303e-17
      layout LEFT_TO_RIGHT
      position 0 0 0
      drawMode 1
      BoundingBoxMargin 0
      BoundingBoxColor 0.94902 0.945098 0.941176 1
      text "Some text"
      backdropType NONE
      backdropHorizontalOffset 0.07
      backdropVerticalOffset 0.07
      backdropColor 0 0 0 1
      backdropImplementation NO_DEPTH_BUFFER
      colorGradientMode SOLID
      colorGradientTopLeft 1 0 0 1
      colorGradientBottomLeft 0 1 0 1
      colorGradientBottomRight 0 0 1 1
      colorGradientTopRight 1 1 1 1
    }
  }
}

To dump the scene graph of the whole Canvas, just invoke the dumpSceneGraph() method on the top-level root node like this:

root.dumpSceneGraph('my-canvas-scenegraph.osg');

Now, let's consider another example - this time actually building a little scene graph hierarchy with multiple groups and nodes, using a for loop:

Dumping-canvas-scenegraphs.png
var window = canvas.Window.new([320,160],"dialog");
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
var root = myCanvas.createGroup();
 
var group = root.createChild('group');
for (var i=0;i<=5;i+=1) {
 
var textNode = group.createChild("text")
        .setText("=>text node in group # "~i)
        .setTranslation(20+i*5, 60+i*15)
        .set("alignment", "left-center")
        .set("character-size", 12)
        .set("font", "LiberationFonts/LiberationSans-Bold.ttf")
        .set("fill", "#ff0000");
group = group.createChild('group');
}

root.dumpSceneGraph('my-little-sg.osg');

This will create a new group for each text node, added to its parent group.

It now being 16 kb in size, the new scene graph file has already grown considerably, including already many redundant state sets.

Canvas-scenegraph-in-standalone-osgviewer.png
osgviewer my-little-sg.osg

As can be seen in the osgviewer screen shot, our osgText nodes are not properly transformed - i.e. the appearance doesn't quite match the original canvas in FlightGear. This is because we are directly dumping the corresponding osg::Nodes at the element level, without taking into account the top-level camera allocated by ODGaugage (our RTT/FBO helper) - to address this, we need to dump the top-level camera group instead, which also contains the transformation matrix that is used by the lower level nodes:

Using this helper function, we'll get a correctly transformed scene graph, matching the one originally created and shown inside FlightGear.

Once we inspect the scene graph, we'll see that it contains quite a bit of redundant state. However, this is where osg::Optimizer[4] can shine, by optimizing our scene graph to get rid of redundant state sets and flatten the scene graph.

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.

Next, we're going to explore dumping complex scene graphs to disk, i.e. those created by Canvas MFDs like the NavDisplay - so that we can check what the scene graph looks like after optimizing/simplifying it using osg::Simplifier and osg::Optimizer respectively, while also investigating which additional OSG properties may be helpful to expose for the optimizer/simplifier to provide satisfying results.

Using Osg::Optimizer for complex Canvases

A trivial example invoking the in-built osg::Optimizer for an arbitrary Canvas element would be this:

osgUtil::Optimizer optimizer;
// _transform is the element specific Node containing all Canvas child nodes
optimizer.optimize(_transform.get());

Obviously, there are additional optimization options available, for details refer to [5].

To map these options to either a C++ callback exposed via Nasal or via properties, a std::map<std::string,OptimizationOptions> can be used. Linking requires osgUtil.lib (or libosgUtil.so under Unix/Linux)[6].


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:

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 and Osg Stats

There are three parts to stats - the stats storage, gathering and the stats display.

The osg::Stats class is a thread safe container for storing collected stats data, and various OSG classes can have Stats objects attached to them, such as osg::Camera and osg::View.

The various traversals then fill the above Stats obects with data, in particular it's the Viewer/CompositeViewer/Renderer that do most of'the stats collection.

Then we have the StatsHandler that switches on the stats collect so the above start collecting and storing stats, and then displays the results.

To add custom stats you'll just need to add your own stats collection, and you may be able to simply reuse one of the exiting osg::Stats object as a place to dump this data, then subclass from osgViewer::StatsHandler or simply derive your own for the display of your stats.

The following patches are based on the osguserstats.cpp example:

Experimenting with Osg::Simplifier

The default simplifier[7] algorithm implemented in OSG is focused on terrain/mesh simplification and is typically invoked like this:

osgUtil::Simplifier simplifier;
simplifier->setSampleRatio(0.7f); // simplify down to 70%
simplifier->setMaximumError(3.0f);

// invoke the simplifier for the current node (Canvas element)
_transform->accept(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 (based on this tutorial).

Tracking RAM utilization per Canvas

Tracking update/cull and draw per Canvas or Element

Patching CanvasMgr to use CompositeViewer internally

Cquote1.png A moving map is a different beast. It would make sense to implement that as a scene graph in its own right[1]
— Tim Moore
Cquote2.png
Cquote1.png I believe that we need to distinguish between different render to texture cameras. Camera nodes must be accessible from within flightgear. That ones that will end in mfd displays or hud or whatever that is pinned into models. And one that are real application windows like what you describe - additional fly by view, and so on. And I believe that we should keep that separate and not intermix the code required for application level stuff with building of 3d models that do not need anything application level code to animate the models ... I think of some kind of separation that will also be good if we would do HLA between a viewer and an application computing physical models or controlling an additional view hooking into a federate ...[2]
— Mathias Fröhlich
Cquote2.png
Cquote1.png I have an animation that I call rendertexture, where you can replace a texture on a subobject with such a rtt camera. Then specify a usual scenegraph to render to that texture and voila. [...] The idea is to make mfd instruments with usual scenegraphs and pin that on an object ...
— Mathias Fröhlich (2008-06-28). Re: [Flightgear-devel] RFC: changes to views and cameras.
(powered by Instant-Cquotes)
Cquote2.png

With Canvas textures typically not rendering any scene/scenery data (terrain), we don't necessarily need those Cameras to render within the main viewer, i.e. by settig up a CompositeViewer instance rendering to a pbuffer instead of a visible window [8]:

# #include <osgViewer/CompositeViewer>

...

osgViewer::CompositeViewer *v = new osgViewer::CompositeViewer; 
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits; 

traits->x =x; 
traits->y = y; 
traits->width = width; 
traits->height = height; 
traits->windowDecoration = false; 
traits->doubleBuffer = false; 

// http://forum.openscenegraph.org/viewtopic.php?t=10340
traits->sharedContext = 0; 
traits->pbuffer = true; 

osg::ref_ptr<osg::GraphicsContext> _gc= osg::GraphicsContext::createGraphicsContext(traits.get());

if (!_gc) 
    { 
        osg::notify(osg::NOTICE)<<"Failed to create pbuffer gc, failing back to normal graphics window."<<std::endl; 
        traits->pbuffer = false; 
        _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); 
    } 

if (gc.valid()) {
 v->getCamera()->setGraphicsContext(_gc.get()); 
 v->getCamera()->setViewport(new osg::Viewport(x,y,width,height)); 
 
 osgViewer::View* view = new osgViewer::View;
 view->setName("Main Canvas View");
 v.addView(view);
 view->setUpViewOnSingleScreen(0);
 //view->setSceneData(scene.get());
}

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:

  1. Tim Moore (Mon, 04 Aug 2008 02:43:52 -0700). Cockpit displays (rendering, modelling).
  2. Mathias Fröhlich (30 Jun 2008 22:46:34 -0700). RFC: changes to views and cameras.