No, you didn't miss anything - you hit the nail on the head, because that's exactly the kind of discussion I was hoping to have here. So it's really appreciated !
First of all, there are elements that don't need to be dynamic at all - think the "fascia" of the FG1000 - that doesn't need to be dynamically "animated". And there are other MFD elements that can be treated as "static", which means less init/runtime overhead.
But like you say, the real issue is dealing with dynamic elements - since those are currently represented as a scene graph internally, each of which is addressable via a corresponding Canvas.Element and its helper methods (properties) - i.e. to set translations, scaling etc
Right now, this is accomplished by parsing each SVG file in scripting space, using a sub-classed variant of our own XML parser, which then builds the corresponding scene graph hierarchy by traversing all elements in turn (text nodes, paths, raster images).
I have been profiling parsesvg/svg.nas extensively, using different SVG files - and especially the really complex ones (several 100 kbytes in size) add up considerably, due to hundreds of context switches between Nasal and C++.
Stuart and James suggested to move parts of svg.nas back into C++ to reduce the runtime footprint - which is why I pointed out that OSG already comes with SVG handling support by using librsvg - in fact, people only need to install librsvg-dev to see for themselves how much of a difference that makes, by going through
ReaderWriter instead.
This is because:
Like you said, the issue remains how to deal with SVG elements that need to be dynamically animated, which is what is currently accomplished by using a handle for each dynamic element (its SVG id) and then associating a Canvas.Element instance with it (Canvas.Text, Canvas.Path or Canvas.Image)
Thus, the first step would be to annotate those elements that can indeed by treated as static raster images (think the fascia) - this could be accomplished using a hash/vector respectively, so that the parser creates a Canvas.Image instead of a Canvas.Path/Canvas.Text for such elements.
Note:
SVG <text> nodes going through librsvg/cairo will internally not go through osgText at all - which also has certain ramifications, i.e. much greater flexibility for text handling than what we currently provide/support in svg.nas or Canvas.Text specifically. So, getting back to your question: The reader/writer plugin's code is really straightforward currently, because it only returns a single osg::Image:
https://github.com/openscenegraph/OpenS ... G.cpp#L105- Code: Select all
osg::Image* createImage(RsvgHandle *handle, unsigned int width, unsigned int height) const
{
RsvgDimensionData dimensionData;
rsvg_handle_get_dimensions( handle, &dimensionData);
// If image resollution < 128, cairo produces some artifacts.
// I don't know why, but we check the size...
if (width < 128) width = 128;
if (height < 128) height = 128;
//width = osg::Image::computeNearestPowerOfTwo(width);
//height = osg::Image::computeNearestPowerOfTwo(height);
osg::Image *image = new osg::Image();
image->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
image->setPixelFormat(GL_BGRA);
// make sure the image data is cleared before we write to it.
memset(image->data(), 0, image->getTotalSizeInBytesIncludingMipmaps());
cairo_surface_t *cairo_surface = cairo_image_surface_create_for_data(image->data(),
CAIRO_FORMAT_ARGB32, width, height, image->getRowSizeInBytes());
cairo_t *cr = cairo_create(cairo_surface);
cairo_scale(cr,((float)width)/dimensionData.width, ((float)height)/dimensionData.height);
rsvg_handle_render_cairo(handle, cr);
cairo_destroy(cr);
cairo_surface_destroy(cairo_surface);
image->flipVertical();
return image;
}
(only the last 10 lines are relevant here)
The point being, cairo is told to generate an image using its
rsvg_handle_render_cairo() API
This will take the whole DOM and render it into the corresponding raster image.
Next, let's look at the cairo docs:
https://developer.gnome.org/rsvg/stable ... nder-cairoDraws a loaded SVG handle to a Cairo context. Drawing will occur with respect to the cr 's current transformation: for example, if the cr has a rotated current transformation matrix, the whole SVG will be rotated in the rendered version.
This function depends on the RsvgHandle's DPI to compute dimensions in pixels, so you should call rsvg_handle_set_dpi() beforehand.
Note that cr must be a Cairo context that is not in an error state, that is, cairo_status() must return CAIRO_STATUS_SUCCESS for it. Cairo can set a context to be in an error state in various situations, for example, if it was passed an invalid matrix or if it was created for an invalid surface.
However, we can also selectively re-render only a certain part of the DOM using the concept of a "layer", let's look at this:
https://developer.gnome.org/rsvg/stable ... nder-layerRenders a single SVG element in the same place as for a whole SVG document.
This is equivalent to rsvg_handle_render_document(), but it renders only a single element and its children, as if they composed an individual layer in the SVG. The element is rendered with the same transformation matrix as it has within the whole SVG document. Applications can use this to re-render a single element and repaint it on top of a previously-rendered document, for example.
Element IDs should look like an URL fragment identifier; for example, pass "#foo" (hash foo) to get the geometry of the element that has an id="foo" attribute.
This means, this API could be used to selectively re-render only a certain part of the SVG document.
Thus, we could use this machinery to patch the current SVG plugin to hook it up to the sc::Element machinery to selectively update/re-render only certain parts of the document.
As far as I know, FlightGear already comes with custom/forked ReaderWriter plugins for FlightGear specific files, so we could use the same approach to customize the SVG ReaderWriter plugin to return a Canvas::Group instead, which would mean that we'd retain the flexibility of a scene graph and of the Canvas in particular.
That is why I asked Stuart how we'd like to see this structured, when he suggested that people interested in working on this, are encouraged to provide patches.
Personally, I don't see much of a benefit in re-implementing svg.nas in C++ if we can use a "proper" OSG-enabled SVG parser that comes with better performance/conformity (and threading support), at the mere cost of adding librsvg to our dependencies - but maybe, I am missing something ?
Basically, we can sub-class sc::Element to create something like sc::SVGImage which goes through OSG's ReaderWriterSVG plugin but instead of returning an osg::Image it returns a sc::Group structure, i.e. a proper osg scene graph consisting of sc::Image elements that can be addressed via the sc::Element API, to selectively update/re-render parts of the raster image as needed, without causing any Nasal overhead whatsoever, i.e. all handled in a background thread using native code.For some features, it simply doesn't make sense to represent them in the form of low-level building blocks like OSG text nodes, OpenVG paths or osg::Image raster images assembled inside the main loop - either because of the corresponding Nasal overhead, or because of the required property I/O (and possibly even both)
For instance, a synthetic terrain view, loading/translating 3D models or PDF files would also not be implemented using these low level building bocks, but instead new dedicated Canvas elements should be used:
http://wiki.flightgear.org/Howto:Extend ... _3D_modelshttp://wiki.flightgear.org/Canvas_Sandbox#CanvasPDFhttp://wiki.flightgear.org/Canvas_View_Camera_Element