Board index FlightGear Development Canvas

Discussion MapStructure/ND optimizations (Nasal space)

Canvas is FlightGear's new fully scriptable 2D drawing system that will allow you to easily create new instruments, HUDs and even GUI dialogs and custom GUI widgets, without having to write C++ code and without having to rebuild FlightGear.

Discussion MapStructure/ND optimizations (Nasal space)

Postby Hooray » Wed Jan 28, 2015 8:29 pm

Given that we tend to mention all sorts of ideas in a bunch of unrelated threads, I was thinking it might be a good idea to collect all ideas in a single thread - i.e. as a "go-to place" for people wanting to improve performance, without having to know C++ necessarily. Here's some of the stuff that I recently mentioned in 3-4 different threads:

  • change navdisplay.mfd to call predicate(nd,symbol) in the foreach loop - all the predicates in can then be changed to only show()/hide() or update() if necessary - avoiding redundant/unnecessary state changes, will keep canvas happy - equally, passing the symbol directly, will reduce hash lookups (which currently happen way too often):
    Code: Select all
    var apply_state = func(condition_cb, action_cb) {
    if (condition_cb()) action_cb()

    Code: Select all
                    id: 'compassApp',
                    impl: {
                        init: func(nd,symbol),
                        predicate: func(nd, symbol) (nd.get_switch('toggle_centered') and nd.in_mode('toggle_display_mode', ['APP','VOR'])),
                        is_true: func(nd, symbol) {
                                var hdg = nd.aircraft_source.get_trk_tru();
                                var hdg = nd.aircraft_source.get_trk_mag();
                        is_false: func(nd, symbol) symbol.hide(),
  • use caching for symbols, see the VOR.symbol and DME.symbol files for examples on doing this correctly using the SymbolCache
  • don't re-draw redundant layers: for instance, the compass rose on the ND is redundant, no matter how many NDs you are displaying - it makes sense to treat the compass rose as a dedicated layer (see APS.* for examples) and then render that into its own texture map - we could easily set up a LayerCache analogous to the existing SymbolCache - transformations (rotating) would then merely be applied to the referenced raster image - all of a sudden, there will be less work for shivaVG to do, because the compass rose will rarely -if ever- need to be updated - all variations will be rendered into a layer cache and the COMPASS.symbol file would merely reference the correct sub-texture (e.g. plan/arc), and merely update/rotate the raster image child. We kinda discussed the technique a few times already - i.e. introducing the concept of an "Overlay"-layer would make sense - typically, this could be shared among multiple instances of a MFD (think ND/PFD) - this would even be more efficient than the existing hard-coded od_gauge based instruments
  • when doing route-drawing (or any other complex layer), apply range based filtering - in the case of waypoints, just do a range check using geo.nas helpers - in the case of waypoint legs, you need to ensure that at least one waypoint is within range to draw the whole leg, otherwise (if both waypoints of a leg are out of range), you can skip the whole leg - the latter should help draw complex routes, especially in low-range settings, because most of the rendering can be skipped - you merely need to adapt searchCmd() in WPT/RTE (lcontroller) to skip certain waypoints/legs there.
  • finally, when dealing with complex layers that may have dozens of active symbols/labels shown, consider adapting the .del() method in the scontroller file to append(, me) - you only need to invoke .hide() first - what this will do is maintain a "free list" of symbols/labels that were previously allocated but that are no longer in use. By changing the .new() method you can then re-use/recycle those canvas elements, so there should be less of an overhead when allocating new nodes (think toggling range).

None of these should require any C++ knowledge, and you also don't need to know how to build FG from source - I am sure we can come up with more ideas to squeeze out some more performance.
As long as people are already familiar with Nasal and Canvas basics, most of these can be quickly applied.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Posts: 11437
Joined: Tue Mar 25, 2008 8:40 am

Re: Discussion MapStructure/ND optimizations (Nasal space)

Postby tikibar » Fri Feb 06, 2015 8:30 am

Thanks, Hooray, for summarizing and consolidating some of the stuff that needs to be done looking forward with the canvas mapstructure. Real life has unfortunately pulled me away from working/playing with FG for the time being, so I'm not in a position to test or develop anything. But I can speak to the optimization you mentioned with range-based filtering for drawing the RTE layer. I added a few lines to the RTE.lcontroller script that only draws route segments with an end point within 1500 km plus the first segment that goes beyond that range limit. Here's the modified code:

Code: Select all
var searchCmd = func {
        # FIXME: do we return the current route even if it isn't active?
        printlog(_MP_dbg_lvl, "Running query: ", name);
        var plans = []; # TODO: multiple flightplans?

        var fp = flightplan();
        var fpSize = fp.getPlanSize();
        var loc_curr = geo.aircraft_position();   ### range filter addition
        if (!getprop(me.layer.options.active_node)) fpSize = 0;
        var coords = [];
        var in_range_prev = 0;   ### range filter addition
        for (var i=0; i<fpSize; i += 1) {
                var leg = fp.getWP(i);
                var leg_loc =, leg.wp_lon);   ### range filter addition
                var in_range = 0;   ### range filter addition
                if (loc_curr.distance_to(leg_loc)<1500000)   ### range filter addition
                    in_range = 1;
                if (in_range==1 or in_range_prev==1)
                    coords ~= leg.path();
                in_range_prev = in_range;
        append(plans, coords);
        return plans;

I tested this before I had to start my FG hiatus, and found it to give a significant performance restoration. There are use-cases where it does not work, though.

The first is when waypoint on the route are widely spaced (greater than 1000 nm). When the spacing is greater than the filter radius, the first out-of-range leg is drawn regardless of length. An example of this would be a 2-waypoint route from KSFO to EDDF where the only waypoints are the airports.

The second use-case I found is when the route goes over or close to the north (and probably also the south) pole. In this case, the route segment over the pole is drawn with a big curve in it, and there is a big performance drain while that segment is in range.

Overall, I think changing the map projection, as Gijs suggested, may be the answer to this particular problem. That is a pretty involved change. The simple change above, while not perfect and not a complete solution for all use cases, provides some relief in the meantime. The same fix could also be applied to WPT.lcontroller to do range filtering on that layer too.

My version of RTE.lcontroller can be found here: ... controller
It might be worth putting that filter in the 3.4 release as a temporary bugfix.

Boeing 747-8 (rename folder to 747-8i)
Boeing 757-200/300 (rename folder to 757-200)
Boeing 767-300/ER (rename folder to 767-300)
McDonnell Douglas MD-11 (rename folder to MD-11)
User avatar
Posts: 515
Joined: Mon Mar 05, 2012 6:05 am
Location: Los Angeles
Callsign: CHT0009
OS: Ubuntu 14.04

Re: Discussion MapStructure/ND optimizations (Nasal space)

Postby Hooray » Fri Feb 06, 2015 8:54 am

Indeed, that's looking pretty good already - the only suggestion I'd have is changing this to use geo.nas to compute a "visibility range" of all searchCmd() elements based on the map ref-lat/ref-lon and the map range property in combination with the map heading. That would even handle "offset" maps, and it would give you a way to process both waypoints of each leg to determine if any of those waypoints is visible, if it is - the whole leg must be drawn, if neither waypoint is visibile, you can drop the leg. I think that should "filter" out even more elements without adding a hard-coded constraint that would be highly use-case specific anyway (think ND/map range, orientation and groundspeed - e.g. this should work even when flying the ufo with the Map dialog open )

I guess artix may have another look and prototype this at some point and report back here, because he's currently busy with all sorts of fancy MapStructure/ND stuff, and your changes should be a straightforward way to also optimize his recent work.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Posts: 11437
Joined: Tue Mar 25, 2008 8:40 am

Return to Canvas

Who is online

Users browsing this forum: No registered users and 1 guest