Canvas Troubleshooting

From FlightGear wiki
Jump to: navigation, search

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
Cquote1.png I'd suggest to print out the type/sie of each variable used in that line, i.e. using typeof()/size() or just debug.dump().

that should tell us if there is anything odd there - maybe, the cmd vector has grown massively or something like that.

Alternatively, pause the simulator and navigate to the property tree node of the Canvas group and inspect that (you can post a screenshot here)
— Hooray (Jan 18th, 2016). Re: 777 freezes and FPS loss.
(powered by Instant-Cquotes)
Cquote2.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

####################################

# change the background color 
myCanvas.set("background", "#ffaac0");


var imgSize = 120;

# path is relative to $FG_ROOT (base package)
var path = "Textures/Splash1.png";
# create an image child for the texture
var child = root.createChild("image")
    .setFile(path)
    .setTranslation((width-imgSize)/2, (height-imgSize)/2)
    .setSize(imgSize,imgSize);


var text=root.createChild("text")
      .setText(getprop("/sim/aircraft"))
      .setFontSize(24, 0.9)          # font size (in texels) and font aspect ratio
      .setColor(1,0,0,1)             # red, fully opaque
      .setAlignment("center-center") # how the text is aligned to where you place it
      .setTranslation(width/2, height/2);     # where to place the text

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.


making Map::update() smarter

Cquote1.png the canvas::map mode actually knows current range, ref lat/lon and heading - in other words, it could be a little faster by checking the ratio of pixels vs. position change - to have some update() threshold and not update things that have not changed significantly enough to be visible to the user - just imagine the ND at 320 nm showing airports: even when flying at >= 400 nm, it would not need to redraw everything at frame rate.
— Hooray (Nov 11th, 2013). Re: How to display Airport Chart?.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png Another thing that would work is coming up with a "pixel-threshold" to skip updates that are not even visible - I think TheTom mentioned that also once - currently, the Map::update() method will basically run the equivalent of a foreach loop to update each "symbol" (lat/lon), without taking into account the resolution of the map, the visibility of the element or the map's resolution.

It isn't unlikely that this would be a simple optimiation to skip updates/redrawing - and when it does redraw, the projection code could be changed to use memoization/caching, i.e. a std::map<> lookup table for different std::pair<latitude, longitude> to return the matching x/y screen positions - which would mean that the cache would be populated quickly, so that redundant computations can be skipped. Note that this would be in line with the recent PagedLOD/AI work, where the size of the AI object in screen pixels determines if it to be rendered or not.

Equally, an element having a bounding box of x*x does not necessarily need to be updated if its position only changed by 10% its visible size
— Hooray (Feb 10th, 2016). Re: Route Manager consumes a lot of Frame Rate.
(powered by Instant-Cquotes)
Cquote2.png

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:

Exposing setDataVariance() for finalized elements

Calling setDataVariance() is only required for Drawables and StateSet's, changes to Nodes don't make it into the draw traversal so aren't affected by the requirement to set the DataVariance to DYNAMIC that the DrawThreadPerContext threading model requires.

The DataVariance is used during the draw traversal to monitor when all DYNAMIC StateSet and Drawables have been dispatched, as once they have been the next frame can be started in a parallel with the remaining STATIC objects are rendered (in DrawThreadPerContex, CullThreadPerCameraDrawThreadPerContext threading models.)

Nodes do not need to be set to DYNAMIC for the purposes of update, event, cull and draw traveresals. The only part of the OSG that checks the DataVariance of Nodes is the osgUtil::Optimizer.

If you plan using the osgUtil::Optimizer on your data then the DataVaraince value is used as a hint about what can safely be optimized and what can't be, for instance a DYNAMIC transform node can't be optimized away. This is unlikely to be an issue for in frame updates.

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

Screenshot showing the FlightGear Property browser with additional Draw masks

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

CanvasImage: adding an unref flag

Cquote1.png In OpenGL when you create a texture object you first have the memory for the imagery in the applications main memory, when you (via osg::Texture2D) pass that image data to OpenGL fifo, then afterwards the the driver takes the data from the fifo and creates an internal representation of that data that is suitable for passing directly to the graphics hardware. This representation may have different pixel format depending upon the hardware and the texture settings you used, and also may create mipmaps for you. Finally when the actual texture is needed on the GPU it'll be copied to local memory on the graphics card. The result of this pipeline is several copies of your data, it's not a bug, but just the way that OpenGL/hardware manages things. The
Texture::setUnRefImageAfterApply(true);
usage tells the OSG to unref the image after the data has been passed into the OpenGL fifo, and as long as no other references are kept to the osg::Image this data will be deleted so getting rid of one copy of the data which is why it helps. Another aspect to take into account is that files on disk that are compressed in .jpeg etc. are all much smaller than they are once they are loaded into memory. However, if you use an OpenGL compressed format such as S3TC then it'll be stored in a format that you can pass directly to OpenGL without any unpacking, here the memory usage will be consistent from disk to OSG memory to driver memory and to the GPU memory, with a caveat that if you generate mipmaps at runtime this will increase the footprint by around 40%.
— Robert Osfield
Cquote2.png
Robert Osfield (Thu, 21 Nov 2013 01:52:04 -0800). Optimizing memory usage by Texture of size 4096x2048. Retrieved September 23, 2017.

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.