Board index FlightGear Development Canvas

Canvas ADI ball (shuttle) / circular clipping

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.

Canvas ADI ball (shuttle) / circular clipping

Postby Hooray » Thu Oct 27, 2016 7:50 pm

Thorsten wrote:For instance, my actual problem is property I/O - I can't read/write several hundreds of properties per frame without creating a bottleneck. So it's largely irrelevant how fast the Nasal code runs, whether it's parallel or whether it's Python-driven code running on the GPU - as long as property I/O speed doesn't change, performance will be stuck right there.


Do you actually have a real/valid need/use-case for getting, and especially, setting/updating so many properties per frame ? I mean, are those state changes relevant per frame ?

What we are commonly doing in such situations is having a "DataProvider" abstraction that wraps getprop/setprop and props.nas calls, which only take place at the desired pace - e.g. 1-5 hz will usually do - obviously the requirements will usually be different when it comes to control algorithms (AP/PID) and probably spaceflight ?

I think Richard also mentioned that his MFD framework is using a similar mechanism - basically, you end up overloading getprop/setprop at the module level (as well as props.nas APIs) and put the corresponding requests into a queue that will return the cached value until the next actual update happens (aka memoization)
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Nasal must go

Postby Thorsten » Fri Oct 28, 2016 6:11 am

Do you actually have a real/valid need/use-case for getting, and especially, setting/updating so many properties per frame ? I mean, are those state changes relevant per frame ?


I think I've gone through the basics a few times, but:

The actual bottleneck is the ADI ball canvas code. As I've tested it all needs to be done in the same frame, and the ADI should be updating reasonably fast. Also, getting the properties to display is not the issue, setting the points for canvas is - data provider ain't helping a bit.

(ultimately as I said, the ADI is probably best drawn by a GLSL shader which is actually designed for this kind of crap, then it happens at near zero performance footprint - for the time being, we have what we have).

As for the rest, a data provider helps you if you have n identical displays. Say for the HUDs we need 30 properties each, so we can fetch them once, have them in Nasal space, save 30 getprop calls in the second HUD.

For the MFDs... let's go through the numbers. We have 40 pages by now, each displays on average something like 50 properties. That's 2000 getprop calls for the data provider to manage. At 3 Hz, and 30 fps, that's 200 requests per frame.

Now, if these 40, no more than 9 different ones can actually be on at any given time - so that's 450 getprop calls if you do it without data provider.

Now, we're not updating them all at once, we're updating in a staggered fashion - user selectable but per default just one display in a frame - so that's 50 getprop calls per frame. So effectively you get an update rate of ~3 Hz and query only the properties you really need. Seems kinda hard to beat with a data provider...
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Re: Nasal must go

Postby Hooray » Fri Oct 28, 2016 6:31 pm

I think I've gone through the basics a few times

To be fair, I have also posted responses to this specifically that didn't quite appear to "hit home".
I think we want the same thing, but we work with different background knowledge - to be honest, I don't care about the GLSL solution (even though it makes perfect sense), because it won't work for any non-GLSL modes, but also because it doesn't help address the underlying problem. For example, I'd much rather see the Canvas system improved, then specifically porting a single instrument to a less constrained environment (like GLSL).
Like I also said on various ocassions, property I/O is a challenge that other Canvas efforts have in common -e.g. pretty much any GUI dialog showing dozens of widgets will typically have hundreds of properties that need to be processed in a timely fashion.

I would really try to keep this constructive, and I fully appreciate that you are extremely familiar with the rendering side - but have you actually checked if the problem is Canvas related or if it's pure property tree overhead ? I am asking because we have several solutions actually - and we have faced the problem previously (I keep mentioning the KNUQ taxiways, which would take seconds to render using the conventional/naive approach, whereas a simple algorithmic improvement (setting all path drawing commands via a single property) made a huge difference.

I do understand what the data provider is all about, and I also understand how you are using the term - we had the same discussion in other threads where I suggested to introduce property lists with sprintf-style formatting to significantly reduce the property overhead.

There also is a patch in the wiki to add draw-masks for Canvas textures specifically - which means that you can measure the impact of keeping rendering disabled (=same property I/O) and compare that to also updating the actual texture and traversing the scenegraph.

Like I originally said, I am deeply impressed by how you came up with the ADI ball - but I also suggested that simply using an actual 3D model would make a huge difference (no question, the shader would be even better if GLSL can be generally assumed to be always available). But for the sake of experimenting, I can live with using the Canvas ADI - I am not familiar with the underlying Nasal code, but be assured that some of the early Canvas code was fairly naive in comparison to what we're now doing in various places, e.g. see:

https://sourceforge.net/p/flightgear/fg ... iways.draw

You can look at the commit history of said file to compare that code to the original approach (which would take seconds to render KNUQ).

Another example would be projection handling code that Gijs updated, that would otherwise take seconds to render a map (depending on your position).

So it's not like we're out of options - let's just assume we are familiar with different parts of the problem, and we should be able to join forces and make this work much better.

~500 property accesses/updates would not be that far-fetched with typical hardware these days (in fact, the numbers posted in 2002 doing a standalone test were doing even more than that and still provide ~40 fps)

Admittedly, the original design assumptions were hugely different: http://www.mail-archive.com/flightgear- ... 00937.html
http://www.mail-archive.com/flightgear- ... 20339.html
http://www.mail-archive.com/flightgear- ... 03176.html

Note that for the sake of completeness, bugs like the infamous effects listeners leaking, would also show up as property/Canvas being slow, while the culprit would be something else entirely.

I do realize that you are not a fan of using tools like gdb, gprof or valgrind - however, this may be a valid situation to actually make use of the built-in profiler (you only need google perftools installed), and it will tell you exactly what it is doing - it is how the taxiway rendering at KNUQ got boosted so much: viewtopic.php?f=71&t=17650

For starters, if you aren't already, you may want to use SVG path syntax (as per the taxiways.draw file)

PS: I realize that this may not interest you that much, so feel free to post a link to your code, or a dump of the canvas group representing the ADI ball (you can use props.dump(node) for that). Alternatively, you could also view the Canvas group using the property browser - I assume that you are using too many properties (without having looked at any code so far, i.e. unnecessarily)
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Nasal must go

Postby Thorsten » Sat Oct 29, 2016 6:13 am

500 property accesses/updates would not be that far-fetched with typical hardware these days


We're talking 500 properties in addition to everything else that is happening (limit checks, thermal system updates, CWS queries, simulation of co-orbiting objects, simulation of sensor errors,...)


I would really try to keep this constructive, and I fully appreciate that you are extremely familiar with the rendering side - but have you actually checked if the problem is Canvas related or if it's pure property tree overhead ?


The center portion of the ADI ball

Image

consists of the line grid which is generated by projecting a pattern of meridians and coordinate circles on a sphere into 2d space and then clipping the central portion out of it.

That pattern is generated in the following way:

* start from the mathematical expression for meridians and circles on a unit sphere
* find the rotation matrix corresponding to the current attitude and apply it
* evaluate the expression for a finite set of points along circles and meridians (at this stage, a resolution is picked)
* project the resulting set into 2d space
* clip to the visible portion of the circle

-> At the output stage of this, there's a long Nasal array filled with point coordinates and a flag that tells whether the plotter should draw a line between two points or just move to the next point.

I can run all these operations at high resolution without being able to detect any framerate hit.

* clear all points of the canvas path element from the last update, then write the whole set of points as determined above via .moveTo and .lineTo (at which point canvas must execute setprop() )

(Now, note that we can't assume much about the points having moved, or even being the same number - dependent on what part of the original structure survives projection and clipping, numbers and what is connected in the array is quite different - some meridians may not be visible at all,... - which makes moving points rather than drawing from scratch a very difficult exercise and which is why real time 3d rendering strategies never bother really bother to re-use parts of the scene)

Now, I can disable the update after drawing once, so I get the pure cost of rendering. Which is low.

Since doing the projection and the math is not the holdup, and since the actual rendering is not the holdup, it stands to reason that it's the property IO.

The resolution chosen for the evaluation grid is the single decisive parameter for the framerate cost. Now, why do we need a high resolution - look at the screenshot, and you'll see that clipping is not good - the circle and meridian lines sometimes do not reach the boundary circle, sometimes go beyond it. So the resolution needs to be good to get decent edge detection and this drives the numbers.

Now, we don't need the resolution in the center to represent curvature - so I've introduced another step that after doing 2d projection and clipping, it culls points in the center of the display.

The result is that while Nasal computation overhead goes up (the additional step needs to be processed), property I/O goes down (because the array requested to be written is smaller), and the net result is a decent improvement in framerate.

You can feel entirely free to use any profiling tools you like, but to me the math of performance consumption is very well established by these findings. The optimization goal is to minimize property throughput by culling as agressively early on as we can possible get away with.

(See - we agree on 'too many properties' the question is just to reduce their need - if we had circular clipping on the fragment shader level for this, we'd get by with a lot less resolution and it'd be a simple task for the GPU which, as I said elsewhere, is designed to do this kind of crap.

Anyway, the ADI ball is created as part of Nasal/PFD/p_pfd.nas using the lower-level routines of Nasal/canvas_draw.nas where the whole math of projection and culling is done. I think that calls the Tait-Bryon rotation equations which are elsewhere (probably in Nasal/rel_orbital.nas)
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Re: Nasal must go

Postby Hooray » Sat Oct 29, 2016 10:18 am

Thanks for the additional information and thanks for your explanations, i.e. for keeping this constructive.

So let me ask a few questions:
  • when the display is updated/redrawn are you removing all child elements or are you reusing previously created ones (I don't care about the primitives but about the nodes in the tree, i.e. how they are set up and updated, because that is something that made quite a difference when we were hitting a bottlneck).
  • how many elements/groups are actually created for the grid you are creating - have you compacted the whole thing to get away with fewer properties, or does it basically result in one group/element per line ? (you can use the property browser to navigate to the canvas/group and see what the largest index is, e.g. group[22] would mean 23 groups, likewith with paths etc).
  • do you think that all the projection handling does matter in terms of performance, i.e. would it possibly help to have additional projections supported by native Canvas code ? (I am asking because TheTom organized the code so that new projections can be easily added/supported basically using 50 lines of code)
  • again: the "clear all points out", does that mean just clearing the visuals or are you literally removing those property nodes - this is what I was hinting at with the "removeAllChildren()" idiom that is far from canvas-friendly, and that is causing unnecessary property I/O - fixing this in conjunction with using Canvas path syntax was responsible for a major speed-up, because the property pressure was significantly reduced by writing more paths to a single property, and by only updating a few properties rather than always clearing all proeprties and re-creating them from scratch
  • and would it be a lot of work to reuse any of your code in a standalone Nasal/Canvas GUI dialog, e.g. driven by some "harness" code that simply rotates the whole thing randomly, so that I can do testing, and bencharking, without necessarily having to use the full shuttle ?

Thorsten wrote:Now, why do we need a high resolution - look at the screenshot, and you'll see that clipping is not good - the circle and meridian lines sometimes do not reach the boundary circle, sometimes go beyond it. So the resolution needs to be good to get decent edge detection and this drives the numbers.
[...]if we had circular clipping on the fragment shader level for this, we'd get by with a lot less resolution and it'd be a simple task for the GPU which, as I said elsewhere, is designed to do this kind of crap


As you may remember, James and Tom originally discussed adding support for circular clipping - that would require C++ changes obviously, but should not be that much work, I guess it's less than 20-30 lines of code actually:

https://sourceforge.net/p/flightgear/ma ... /29592872/
Clipping: Already mentioned. At least rectangular regions are needed.
(eg. group/clip-min[0..1], group/clip-max[0..1])

Clipping: For different reasons we will need to be able to clip
some elements to certain regions. It should work with specifying
either a clipping rectangle or by using a path. OpenVG seems to have
support for it, although I haven't looked into it too deep. We also
need to ensure that it also works with text.

Right, clipping is needed for 2D panel support too. We need to consider this one carefully since it's needed for various GUI widgets as well, and there's a few different implementation strategies. Stencil planes are one option, I'm unsure about the performance of 'real' GL clipping on modern hardware.

We will need to support clipping using the stencil buffer in any case,
as it allows arbitrary shapes to be used. We need to check if OpenGL
clipping planes are faster - if it's the case we should use them for
rectangular clipping regions.

in practice rectangular clipping is what's need 90% of the time so I'd rather optimise for that case, whether that means stencils, GL scissors clip or clip planes.


The C++ code can be found here: https://sourceforge.net/p/flightgear/si ... t.cxx#l562
Note that anything added there will be automatically available to all Canvas elements (images, paths, text etc)

Again, my interest would be two-fold: Understanding how to improve overall performance, but also move things back into C++ space that are no longer specific to any single aircraft/instrument or use-case.
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Nasal must go

Postby Thorsten » Sat Oct 29, 2016 12:30 pm

when the display is updated/redrawn are you removing all child elements or are you reusing previously created ones (I don't care about the primitives but about the nodes in the tree, i.e. how they are set up and updated, because that is something that made quite a difference when we were hitting a bottlneck).


I have no idea what structure you have in mind.

As I said, the ADI ball after Nasal is a huge vector of vectors like

[[0.1, 0.2, 1], [0.2, 0.2, 1], [0.2, 0.4, 0],.....]

(the first two are coordinates, the last is the instruction whether to draw a line or simply move to the next point) the code first does

Code: Select all
p_pfd_orbit.adi_inner.removeAllChildren();


and then creates three groups (lines and the colored background outlines )

Code: Select all
   var adi_sphere_bg = p_pfd_orbit.adi_inner.createChild("path")
        .setStrokeLineWidth(1)
   .setColorFill(0.15,0.15,0.15)
        .setColor(1,1,1);

   var adi_sphere_bg_bright = p_pfd_orbit.adi_inner.createChild("path")
        .setStrokeLineWidth(1)
   .setColorFill(0.3,0.3,0.3)
        .setColor(1,1,1);

   var adi_sphere = p_pfd_orbit.adi_inner.createChild("path")
        .setStrokeLineWidth(1)
        .setColor(1,1,1);


and then draws all three from the Nasal vectors.

I have no idea how you would structure code to possibly re-use anything from the previous step.

how many elements/groups are actually created for the grid you are creating


The whole center grid is one path element.

do you think that all the projection handling does matter in terms of performance


Negligibly.

again: the "clear all points out", does that mean just clearing the visuals or are you literally removing those property nodes


I know you somehow have the picture of not re-using the property nodes somehow - how would you possibly do this if you don't know anything about the array that comes in to be drawn, not even size?

Again, this is code which utilizes techniques from real-time rendering, and trying to utilize parts of the last frame is not something that's typically done - for good reason.

So what's needed is the capability to draw a picture of the outlines of Micky Mouse in one frame, throw it away and draw the Ubuntu logo in the next frame. And something else in the next.

While the fist outline might have 5000 points, the second may just have 3400 points. If there's an efficient way to accomplish that without removeAllChildren, I guess you should point me to the command to call instead. But I'm not going to sort through the first 3400 points of Micky mouse and move their position and connectivity and then remove just 1600 points 'by hand'.

and would it be a lot of work to reuse any of your code in a standalone Nasal/Canvas GUI dialog, e.g. driven by some "harness" code that simply rotates the whole thing randomly, so that I can do testing, and bencharking, without necessarily having to use the full shuttle ?


The ADI ball used pitch, yaw and roll - the ufo ought to be able to have these defined. The issue is probably more that the code really expects Richard's MFD framework.
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Re: Nasal must go

Postby Hooray » Sat Oct 29, 2016 12:44 pm

So what's needed is the capability to draw a picture of the outlines of Micky Mouse in one frame, throw it away and draw the Ubuntu logo in the next frame. And something else in the next.

While the fist outline might have 5000 points, the second may just have 3400 points. If there's an efficient way to accomplish that without removeAllChildren, I guess you should point me to the command to call instead. But I'm not going to sort through the first 3400 points of Micky mouse and move their position and connectivity and then remove just 1600 points 'by hand'.


Thanks for the tangible analogy - I am perfectly aware of that actually, the "point" was that I wasn't suggesting to reuse 40% of mickey mouse's geometry, but to reuse the underlying Canvas elements - in rendering terms, a Canvas element represents an osg::Geode mapped to a property tree hierarchy. If you always end up calling removeAllChildren(), you always throw away previously created geodes and subsequently tell the Canvas to recreate those from scratch.

To see if this is a red herring or not, my suggestion would be to allocate a vector of canvas elements, and reuse those you need - i.e. by calling .hide() for irrelevant ones (not deleting those), and merely updating those that you need.

[img]and then draws all three from the Nasal vectors.

I have no idea how you would structure code to possibly re-use anything from the previous step.[/img]

I may be missing something, because I am not sure if you are creating separate elements or putting everything into a single element:
Could you post a screenshot of the property browser showing the top-level Canvas group that contains your ADI geometry ?
If in doubt, set a name and use that to navigate to the corresponding group, resize the property browser via CTRL + mouse click.

Like I said, I appreciate the mickey mouse/unbuntu logo analogy - however, as you are well-aware, there actually is a well-defined mathematical relationship between two separately created frames in the case of the ADI ball.

Regarding Richard's MFD framework: I don't think that should be making any assumptions that would not allow rendering a MFD to a GUI dialog - it's basically just the placement that is different, i.e. Gijs' ND code happily renders in all sorts of placements
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Nasal must go

Postby Hooray » Sat Oct 29, 2016 1:09 pm

Okay, thanks to your pointers, I now briefly looked at the code in your update() loop and was wondering if you cannot put the invariant parts into a different child group, and the variant parts into a separate group, and only clear out that for starters, referring to the code below (I haven't looked at the functions you are calling to tell if the code/geometry is actually not invariant at all because it is mutating some global/class variables, but the first couple of lines/geometry setup look like they don't change much in between frames, i.e. could live in their own group that you don't need to clear ?):

https://sourceforge.net/p/fgspaceshuttl ... /p_pfd.nas
Code: Select all
# clear previous step

   p_pfd_orbit.adi_inner.removeAllChildren();

   # draw

   var adi_sphere_bg = p_pfd_orbit.adi_inner.createChild("path")
        .setStrokeLineWidth(1)
   .setColorFill(0.15,0.15,0.15)
        .setColor(1,1,1);

   var adi_sphere_bg_bright = p_pfd_orbit.adi_inner.createChild("path")
        .setStrokeLineWidth(1)
   .setColorFill(0.3,0.3,0.3)
        .setColor(1,1,1);

   var adi_sphere = p_pfd_orbit.adi_inner.createChild("path")
        .setStrokeLineWidth(1)
        .setColor(1,1,1);

   # projection vecs for labels
   var p_vecs = SpaceShuttle.projection_vecs(-pitch_adi, yaw_adi, -roll_adi);

   # ADI sphere
   var data = SpaceShuttle.draw_circle(0.75*95, 30);
   pfd_segment_draw(data,adi_sphere_bg);



which makes moving points rather than drawing from scratch a very difficult exercise and which is why real time 3d rendering strategies never bother really bother to re-use parts of the scene [...]

The optimization goal is to minimize property throughput by culling as agressively early on as we can possible get away with.


The point of my suggestions is to minimize internal state changes: Canvas is primarily a subsystem that works in terms of listeners, each element added to a Canvas, and all their child elements will monitor the tree for "events", i.e. property accesses/updates.

It basically maps a tiny subset of the property tree to OSG primitives, so is a property-driven state machine.

This may result in hundreds, or even thousands, of callbacks to be invoked recursively to propagate events properly.

Whenever an element-specific property is updated (think a color, translation etc changed), this results in the element-specific geode to be marked as "dirty", so that the geode is updated.

These updates can be minimized by not setting/updating certain properties, e.g. those of invariant canvas elements that will basically stay the same in between multiple frames - in such cases, the corresponding group/element-specific osg::Geode from the previous frame can be reused "as is" - which is not specific to Canvas, it's a general scenegraph thing to minimize unnecessary scenegraph traversals.

So, regarding your comment quoted above, imagine how the workload of the Canvas system can be reduced if the spacecraft is stationary, even though we are still updating things per frame that never really change (orientation, altitude etc).

If you really have to/want to use removeAllChildren(), don't use it per instrument, but only for those instrument parts that are variable - and put everything else into a static group that you never touch (think background images etc), and even then, consider if reusing the underlying data structures makes more sense to clearing out everything and re-allocating those from scratch.

The Canvas-specific thing is that all this happening via listeners comes at a cost, which is why it is generally a good idea to check if reusing previously allocated data structures (as in, the elements/groups forming the geometry) can be reused, instead of clearing out the geometry and re-creating it from scratch - because at that point, you are throwing all optimization opportunities out of the window, because that will inevitably cause all code to re-run - whereas reusing parts of the scenegraph in the next frame, can simplify the workload tremendously - e.g. that is why we marked certain NavDisplay geometry as "static" and rendered that to a separate Canvas texture which we are treating as a texture map to get out relevant bits, at that point, it's just dealing with textured quads only - and no longer has to run any Canvas::Path or ShivaVG code to actually render the geometry.

(I do understand that this particular use-case is much more complicated and is unlikely to benefit from rendering many parts to a texture) - but imagine you could represent a similar grid through a handful of transformations of textures rendered to 8 different textures that are combined in a layered fashion - at that point, the workload would mainly be a few transform() calls (i.e. calling transform() gives you more control than just setTranslation)
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Nasal must go

Postby Thorsten » Sat Oct 29, 2016 1:34 pm

Okay, thanks to your pointers, I now briefly looked at the code in your update() loop and was wondering if you cannot put the invariant parts into a different child group, and the variant parts into a separate group, and only clear out that for starters,


That's how it's done. You'll see all the invariant parts of the ADI (including those that can be animated by translations/rotations) created in the ondisplay part and only the parts which can't be done that way in the update part of the display methods.

but the first couple of lines/geometry setup look like they don't change much in between frames, i.e. could live in their own group that you don't need to clear ?):


Yes, unfortunately I've been searching in vain for a command to remove just the points of a plot - probably I have overlooked something, otherwise I would have used that.
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Re: Nasal must go

Postby Hooray » Sat Oct 29, 2016 1:38 pm

Sorry, I just edited the previous posting - in general, it would have been good if you could come up with really reduced snippets and post a corresponding question in the Canvas forum, so that everybody can help/benefit, without any of this being specific to any particular aircraft, instrument or use case.

To selectively remove data from a Canvas, just put it into a separate top-level group - and use .hide() to make it invisible or .clear() to clear it out.
Note that you can also use these two in combination to selectively update those data structures - which helps reduce the updating overhead, i.e. no unnecessary reallocation taking place.

In summary, the Canvas system really is a scenegraph - so if you have something that you'd like to be able to hide/show, clear or change selectively, just put it into a separate group and use that group as the "handle" to deal with the whole shebang (it really is a osg::Group under the hood, i.e. an osg::Transform IIRC, which is a child class inheriting from ::Group)


(I wrote the following as a general intro and will copy it over to the wiki, you will certainly be familiar with most things already)
I think you have repeatedly pointed out how abstraction layers always come with a penalty.

That applies particularly when we're using them for use-cases other than what they're designed for (e.g. doing 3D stuff in 2D without any explicit support for that)

  • Nasal
  • Property Tree
  • Canvas
  • SGSubsystemMgr
  • OpenSceneGraph/ShivaVG
  • and finally, OpenGL

So, we need to keep in mind what is happening behind the scenes between the various abstraction layers, i.e. the translation overhead caused by going from one abstraction to the next one below it.

The first step is Nasal (FlightGear's scripting language), it is used to provide an OSG-like high-level abstraction, but all it really does (well usually) is setting dozens of properties in the global property tree under /canvas for you behind the scenes.

Then, there is the property tree, it has listeners that are mappped to the Canvas subsystem which looks for a few well-defined properties and checks whether the changed properties match the heuristics to make sense (name of the property, type and value written to it, current state/mode of the element etc) - whenever that is the case, the whole Canvas must be marked as "dirty", triggering everything to be rendered.

And then there is the "element" level: Each Canvas just represents a FBO/RTT context (an off-screen texture that is rendered but not normally visible), but there isn't anything to be rendered yet - it's just an empty texture with a background color, a size and a few other attributes.

To become something that can be rendered, the top-level element that must be always added to a canvas is a so called "group", which represents a group of items that can be rendered, including other groups (and so on).

Internally, what takes place here is that we have a base class implementing a Canvas::Element, this exposes attributes and methods that all Canvas elements have in common, including groups -because they're just that: elements inheriting from Canvas::Element.

That is why all elements must be registered at the group level, e.g. images, paths or text nodes are registered by letting the Group element watch for property accesses that create new child-nodes using a corresponding name (e.g. image, path or text).

The name serves as the lookup key for the lookup to get the helpers out of the factories implementing each element.

Under the hood, this makes sure that the corresponding element receives "events" (property write notifications using listeners) whenever one of its child nodes is updated - but at that point, it doesn't yet know whether the event is really valid or relevant, because it still hasn't parsed the property name, the type or the value that was written to it - and checked whether those changes actually make sense in the element's given scope.

Still, the property/canvas subsystem will make sure to dispatch events by invoking the element's notification handler so that it can parse/process the corresponding updates properly.

That is one of the reasons why unnecessary property updates will usually affect performance of rendering the whole Canvas, because its top-level <group> element basically serves as a list of render-able elements (text, paths, images), and whenever one of those changes, the corresponding scene graph node is marked as dirty, and must be updated, and the whole Canvas must be updated/re-rendered afterwards.

Thus, when dealing with primitives that may need to be separately hidden/shown, updated, animated, transformed etc - it makes sense to "group" them accordingly, i.e. by adding them to a separate group that can be directly addressed.

This is also where the abstraction overhead comes in, because whenever we are dealing with such lists of render-able elements, there is not just the property tree representation in the global property tree, but also an internal representation that maps the whole property tree representation to something that makes sense to OpenSceneGraph, and in turn, to OpenGL.

If we don't ever have to update a complex Canvas scenegraph after creating it, all it will do is render a textured quad - which it can do really fast, but once we introduce many state changes, we make things unnecessarily difficult and heavy, even if the state changes are redundant, because identical values are written to the corresponding nodes.

Obviously, Canvas being all about modern avionics and dynamic stuff, we cannot just reuse previously lines/shapes - but what we can do is pre-allocate the corresponding data structures and clear/reuse those selectively, to reduce the allocation/re-allocation overhead.

The next challenge is carefully reviewing dynamic elements of the scene, and grouping those accordingly (e.g. background images vs. elements that need to be animated)- i.e. looking at the requirements of what needs to be updated, and how that will affect the scene.

For example, a group that merely needs to be transformed/translated will have much less of an impact than one that always needs to be updated/redrawn entirely. However, sometimes there are elements that merely need translations, but which still need different elements, e.g. labels - at that point, it makes sense to introduce a new, separate, group to keep the labels.
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Canvas ADI ball (shuttle) / circular clipping

Postby Thorsten » Sat Oct 29, 2016 4:33 pm

In summary, the Canvas system really is a scenegraph - so if you have something that you'd like to be able to hide/show, clear or change selectively, just put it into a separate group and use that group as the "handle" to deal with the whole shebang


I can't shake the feeling we're massively talking past each other.

Since you like the scenegraph - let's try like this.

i have a model I want to render - say the dragon - who happens to be a shape-shifter. So I load the model, and there it is in the scenegraph, with the state set attached. Now I want it to beat wings.

Basically that's a situation in which I want to keep the mesh connectivity, but just distort it - stretch vertices here and there to move the wings around. Well, the vertex shader can totally do this for me, so there's no need to make a per-frame transition between 30 different dragon models all in a different phase of wing-beat.

Now I want the dragon to instant shape-shift into human form.

There's no way I can no this re-using the existing mesh by introducing vertex transforms. The transform from the dragon mesh to a human mesh is mathematically so complex I can't even start to write it down.

I'm going to load a new model instead. I don't want to keep the old one around (except for sentimental purposes) - I just discard the dragon mesh and load a human mesh.

Now the dragon goes insane and shape-shifts into something different each frame. Say it's really a four dimensional entity and we're moving along its extra dimentsion. Sounds bad, but there it is - I'm going to have to load a new model every frame because everything else is much worse. Being able to hide the dragon model ain't doing the trick, because the chances of ever re-using it are slim.

That's what the ADI ball is, just in 2d. It has a 3d reality, but what is in the scenegraph is a 2d entity, and so it may change appearance drastically via a non-trivial transformation as we move along its 3d appearance - parts may cease to be visible, new parts may appear... So just as the dragon changes the 3d mesh from frame to frame, the ADI changes the path (potentially) a lot from update to update. We don't want to hide it - we want it gone and re-load a new path.

So, I have a path described by an array of coordinates in one frame, I have a second path element described by a different array in the next frame, I want to replace the first by the second in the fastest way possible without making any assumptions about size or content of the arrays - what's the command that does it?
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Re: Canvas ADI ball (shuttle) / circular clipping

Postby Hooray » Sat Oct 29, 2016 5:09 pm

We don't want to hide it - we want it gone and re-load a new path.

Yes, I am well aware of all of that, i.e. we're indeed talking past eachother, because I already understood all of that prior to you talking about mickey mouse ;-)

I really was referring to the Canvas elements, i.e. the data structure - mainly because of the re-allocation overhead when you begin removing and re-adding them.
If you think that this is a red-herring, I suggest that you simply try reusing those elements instead of using removeAllChildren().

I have a path described by an array of coordinates in one frame, I have a second path element described by a different array in the next frame, I want to replace the first by the second in the fastest way possible without making any assumptions about size or content of the arrays - what's the command that does it?


Without having looked at any of your lower level helpers, I guess that you may already be doing the right thing: i.e. keeping a handle to the created path element, just calling .setData() should be .reset()ting the existing path element and updating the vector to be used (coordinates and commands).

See api.nas, Path.setData() for details (as well as the underlying implementation)

Like I said, it would be helpful to have a really stripped-down code snippet that can be tested in isolation (i.e. rendering to a Canvas window/dialog), because at that point I could actually use a profiler and see what is going on behind the scenes - no matter if that means finding actual bugs or just opportunities to optimize this further (Tom hand-optimized the Path drawing stuff in response to those KNUQ taxiways).

But I definitely would not calling removeAllChildren() on a group that contains geometry that you immediately recreate using the same coordinates/colors etc - if in doubt, use separate groups.
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Canvas ADI ball (shuttle) / circular clipping

Postby Thorsten » Sun Oct 30, 2016 7:17 am

Yes, I am well aware of all of that, i.e. we're indeed talking past each other, because I already understood all of that prior to you talking about mickey mouse


Is there then any particular reason why you tell me in some length how to hide groups?

If you think that this is a red-herring, I suggest that you simply try reusing those elements instead of using removeAllChildren().


I suspect it largely is a red herring because I see a direct strong dependence on the number of points in the array I write - the overhead for adding and removing the elements doesn't depend on how many points I push later, so it may be there, but clearly not a decisive factor.

just calling .setData() should be .reset()ting the existing path element and updating the vector to be used (coordinates and commands).


I've been wondering whether setData does what I need - sadly the wiki entry doesn't exactly go into details on how to use it.

Like I said, it would be helpful to have a really stripped-down code snippet that can be tested in isolation


Assume I have one array I have written into a path, now I want to replace the path element by one corresponding to the second array. How much more stripped down does it have to be? Forget the ADI specifics, any long array will do, you can fill it with random numbers.

Can you post an actual code-example how you would solve this problem? That's really all I need to know, and it's going to be much more efficient than telling me to re-use elements by commands which sadly don't seem to be documented.
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Re: Canvas ADI ball (shuttle) / circular clipping

Postby Hooray » Sun Oct 30, 2016 7:22 am

Please just post a snippet of code that we can both work with to make this a bit more positive for the two of us ...

PS: To be honest, I don't exactly understand how, and why, someone with your track record, and especially background in Nasal coding, makes such a fuss about a wiki page containing a paragraph that does not have any information ? I mean, you must have helped dozens of people by pointing them to $FG_ROOT/Nasal/canvas/api.nas - which is also where the corresponding .setData() API is to be found, and we usually also tell people to "grep" $FG_ROOT/Nasal to find existing uses o the corresponding API.
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,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11317
Joined: Tue Mar 25, 2008 8:40 am

Re: Canvas ADI ball (shuttle) / circular clipping

Postby Thorsten » Sun Oct 30, 2016 7:53 am

Seems we're still not done talking past each other.

From my perspective - I made an offhand remark that property I/O is a bigger concern than Nasal-native computations in the context of 'Nasal must go' -citing as an example the ADI which does all the projection math and culling at lightning speed and then slows where properties need to be written.

With regard to the ADI specifically, I think I have said that it is workable 'as is' and that doing the work in GLSL is ultimately probably the best solution performance wise. With that, the ADI is bumped waaay down on the priority list.

For me, looking at a huge pile of other unsolved problems with much higher priority, spending a few hours to do performance profiling to convince you that the experiments I have already done are valid is not an option. Neither is digging into the canvas code to find undocumented syntax - if you can't post it here so that I can take 10 minutes implementing it, then it takes too much time.

I gathered that you are interested in this for canvas- optimization reasons which aren't helped by a GLSL solution, so I've been taking some time in this thread.

I've told you how it works, where the code is found, what my observations of performance scaling were and how that influenced my optimization strategy. From here, it depends on what your aim is.

* If it is to help me and you have a quick fix that could be applied, please just post it

* If you want to guide me to a faster canvas solution, then I have to say GLSL is likely going to be _much_ faster so I'm not interested in spending much time with improving a Nasal based thing till I learn that GLSL fails

* If it is to understand why this drains performance and you don't trust my observations, run the code and do your own tests - or run the random number array example, but don't ask me to do it.

Either is really fine with me, but as you seemed to realize earlier, at this point I'm really not all that interested in working on this problem (I have a light direction vector error in Earthview which is undebugged, the question whether I should implement a classic Lambert solver to orbital navigation or continue with my own fit routine and the question of what the purpose of the switch covers aboard the Shuttle really is in my mind at the moment, and I don't want to start a canvas performance profile now, sorry).
Thorsten
 
Posts: 10642
Joined: Mon Nov 02, 2009 8:33 am

Next

Return to Canvas

Who is online

Users browsing this forum: No registered users and 1 guest