Hi all.
Recently I've spent some time trying to find a better way to do a complex scene rendering, avoiding any of the QML's constrains. For example, my project requires ability to draw complex curves, so this just can't be done by placing rectangles and text onto QML scene graph. Along with other requirements, like ability to scale and shift an infinite workspace, ability to make a snapshot of entire scene (not only the visible part of it), this task becomes impossible with plain QML scene graph.
Since I'm not so confident with GL, also it's not available on Windows and Android (that will be
fixed soon, but I simply can't wait), the first decision was to use the
Canvas item with
Context2D raster API. However, painting on Context2D might be quite slow, if there is a lot of shapes to draw each time. Well, my architecture wasn't good at first time — I used JavaScript to compute resulting (X, Y) points and JS loops were slow. Also that implied that I need to wrap my Go types into JS-objects, and so on. Don't do that.
After some deep thinking I've came to this solution: instead of passing high-level objects to the QML land for drawing, I could write something like "graphics pipline" in Go, i.e. generate slices of primitive shapes like circles, rectangles, lines, which then can be passed to QML for rendering. In other words, we can produce pre-computed sets of arguments directly for the Context2D API, e.g. for Context2D::rect, Context2D::circle, etc. That resulted in dramatically improved overall performance (since there was a lot of realtime rendering in the app).
## Other options (for freaks)
There is at least two options to use instead of Context2D API.
## svg PoC:
buf := new(bytes.Buffer)
s := svg.New(buf)
s.Start(200, 200)
s.Circle(0, 0, 100, "fill:none; stroke:black")
s.End()
uri := "data:image/svg+xml;charset=utf-8," + buf.String()
Then `uri` might be used on QML side as Image's source, as url in Context2D::drawImage, Canvas::loadImage, etc. This method works fine, but the drawback is a ~1,5s lag to render (first time). O_o! Despite the complexity of SVG. Since my graphics evolve in realtime and have not any predictable/cacheable fragments, this method is a dead end.
The second option, which should be much faster, is to use raster graphics to render the image on the Go side and then pass it to QML as `image.RGBA`. The good candidate is the
draw2d package which have all the features that I need.
## draw2d PoC:
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
gc := draw2d.NewGraphicContext(img)
gc.SetStrokeColor(color.RGBA{0, 0, 0, 255})
draw2d.Circle(gc, 0, 0, 100)
gc.Stroke()
Then there is two ways to pass `img` to the QML land. First, it could be png-encoded and then base64-encoded and then passed as
"data:image/png;base64,..." string. This method gives us a
~0.8s delay to load in QML land. Second way is to implement a go-qml's image provider, consider the
imgprovider example. Loading/undloading images by url might be tricky, for example there is a need to generate an unique uri each time, like "image://draw2d/id0", "image://draw2d/id1", "image://draw2d/id2", ...
There also is a
~0.3s delay when loading images that way. It is better than
1.5s, but still scene is obviously lagging. This can be improved in the future, see the
CanvasImageData spec, we can generate it in the Go land and pass directly to the
Canvas2D::putImageData. However, it's impossible until Gustavo implements an option to pass
[]byte /
[]uint8 values or CanvasImageData itself, which would be nice.
To sum up, there is many techniques to compose/rasterize scenes on the Go side, but the actual performance problems are on the QML side and we can't deal with them yet. Maybe I don't know something, I would be glad if someone would share their experience too.
^_^