Canvas MapStructure: Difference between revisions

Jump to navigation Jump to search
m
→‎SymbolCache: @Philosopher: artix seems to be struggling with the SymbolCache, so let's try to provide a few more pointers here ... (review/contributions appreciated !)
m (→‎SymbolCache: @Philosopher: artix seems to be struggling with the SymbolCache, so let's try to provide a few more pointers here ... (review/contributions appreciated !))
Line 719: Line 719:


{{Note|I am wondering if we could use some fancy metaprogramming to compile a draw() callback and ensure that it does NOT use certain Canvas APIs directly ? That would be kinda cool and useful, i.e. we could do a dry test-run to make sure that methods only use certain allowed APIs. One possible method would be overloading the canvas namespace to provide sstubs for each allowed/disallowed API and use call() to call each method and keep track via any disallowed APIs were called by using a counter or even just doing die()-so that people would get an error message along the lines of "please do not use setColor/setColorFill etc inside the draw() callback. Given that MapStructure itself handles positioning via setGeoPosition(me.element), we could even watch out for such mistakes that way}}
{{Note|I am wondering if we could use some fancy metaprogramming to compile a draw() callback and ensure that it does NOT use certain Canvas APIs directly ? That would be kinda cool and useful, i.e. we could do a dry test-run to make sure that methods only use certain allowed APIs. One possible method would be overloading the canvas namespace to provide sstubs for each allowed/disallowed API and use call() to call each method and keep track via any disallowed APIs were called by using a counter or even just doing die()-so that people would get an error message along the lines of "please do not use setColor/setColorFill etc inside the draw() callback. Given that MapStructure itself handles positioning via setGeoPosition(me.element), we could even watch out for such mistakes that way}}
== The SymbolCache ==
For the time being, the main optimization that helps speed up rendering Canvas/MapStructure-based displays like the [[NavDisplay]], is using caching. Caching is accomplished by a little helper framework called '''SymbolCache''' which sets up an empty Canvas texture for storing required symbols there. This is where symbols for VORs, DMEs, FIXes and waypoints will be kept. Internally, each *.symbol file will still contain all the logic required to actually render the corresponding symbol, which may include  hard-coded OpenVG drawing commands, but also SVG or raster images. However, the cache will be dynamically populated according to all the symbols that are required for each layer. This approach proved quite efficient and straightforward so that even the extra500 developers adopted this method on their [[Avidyne Entegra R9]] instrument.
However, in the meantime, the SymbolCache has been significantly extended to also support styling: in other words, the SymbolCache now even works for *.symbol files supporting styling by being aware of styling-relevant attributes (think width, symbols, colors etc) and will create distinct cache entries for each symbol variant. Under the hood, this is using some fancy meta-programming that Philosopher came up with last year - but all you need to know as a MapStructure/ND contributor is that styling and caching work in conjunction as long as you follow a few simple rules - which can easily translate into significant frame rate gains when compared to the old method. This section is intended to describe the basic method, as well as provide a few examples/pointers - we're hoping to grow this over time.
First of all, let's consider an existing example: VOR.symbol:
<syntaxhighlight lang="nasal">
var drawVOR = func(group) {
    group.createChild("path")
        .moveTo(-15,0)
        .lineTo(-7.5,12.5)
        .lineTo(7.5,12.5)
        .lineTo(15,0)
        .lineTo(7.5,-12.5)
        .lineTo(-7.5,-12.5)
        .close()
        .setStrokeLineWidth(line_width)
        .setColor(color);
};
</syntaxhighlight>
This is a function named '''drawVOR''' (note, that the name is arbitrary) which accepts a single argument: a canvas group.
The function body itself then uses the passed group to create the relevant geometry for the corresponding symbol (in this case a VOR symbol).
There are two configurable ("styling") settings:
* line_width
* color
Otherwise, everything else can be considered to be "hard-coded". The only other thing worth keeping in mind here is that  Nasal will implicitly return the last expression to the caller absent any explicit return statements - i.e. the group will be returned to the caller.
However, the code snippet above is just a drawing routine that is still unknown to the system, so that needs to be done is to set up a corresponding cache entry, which can be seen below:
<syntaxhighlight lang="nasal">
var cache = StyleableCacheable.new(
    name:name, draw_func: drawVOR,
    cache: SymbolCache32x32,
    draw_mode: SymbolCache.DRAW_CENTERED,
    relevant_keys: ["line_width", "color"],
);
</syntaxhighlight>
What is happening here is that a new cache entry supporting styling is set up (refer to MapStructure.nas for details), specifically:
* a new cache is set up using the name specified at the top of the file: '''VOR'''
* drawVOR is passed as the drawing function
* the cache texture to be used is SymbolCache32x32 (which is always available, provided by MapStructure itself)
* next, a draw_mode is set up (refer to  MapStructure.nas for etails)
And finally, the really cool stuff is happening in the last line: This is where we meet again the two styling-related variables we saw earlier in the actual drawVOR() implementation: there's an argument called '''relevant_keys''' which should be a vector of symbols that are styling-related. This can be used by the SymbolCache/MapStructure framework to tell if a styled symbol is already cached or not.
All of these steps may look complicated at first, but the difficult stuff is happening behind the scenes - now, when it comes to actually using/accessing our "style-able cache", the only thing you need to know is how to get a certain pre-cached symbol out of the cache, which is why we'll take another look at VOR.symbol and its draw() implementation:
<syntaxhighlight lang="nasal">
# determine the color to be used (remember, this is relevant for styling!)
me.style.color = active ? me.style.active_color : me.style.inactive_color;
# look up the correct symbol from the cache and render it into the group as a raster image, applying custom scaling
me.icon_vor = cache.render(me.element, me.style).setScale(me.style.scale_factor);
</syntaxhighlight>
Another example can be seen in FIX.symbol
There are still several layers where caching+styling isn't widely used yet - however, over time this is the correct method to lighten the canvas workload quite a bit, and doesn't require any C++ level modifications. If you're seeing heavy impact on performance with complex layers, we suggest you explore adding styling and caching support as described above.


=== Controllers ===
=== Controllers ===

Navigation menu