The DWS Mandelbrot Explorer, which renders tiles generated by Eric Grange’s tile server, has been updated.The following is a braindump of information about the app, about FireMonkey, about threading, and about how they all interrelate. I think some points will be interesting to you.
Miscellaneous things I’ve learned
- It turns out that you cannot reliably use two FireMonkey Direct2D TBitmaps in two different threads at the same time. The code had a background thread to download the fractal data and create a tile bitmap, which was then passed to the main thread to draw. (Only one thread accessed each TBitmap at a time, but several threads used independent TBitmaps concurrently.) Sometimes this results in silent failures to update the texture object backing the TBitmap data. The DirectX methods CopyResource and DrawBitmap, and possibly others, can fail.
I spent a long time investigating this, seeing what I might be able to do to hack in enough thread-awareness into the library that using two theoretically-independent bitmaps at the same time would work. The code is strongly designed around shared objects, used for all operations. I’ve looked into:
- Direct3D factories and using the ID2D1MultiThread interface to synchronize (only Windows 7 and up though);
- the (different) ID3D10Multithread interface for synchronization, which works brilliantly right up until it deadlocks;
- surface sharing between APIs;
- DXGI shared surfaces;
- per-thread instances of render targets and textures;
- hand-rolled synchronization around specific areas;
- …you name it. Several things have been almost successful but nothing is reliable, and the more I read, especially when there are caveats about certain functionality not working on Vista but only on 7, or only on hardware and not on WARP, the more I understand why it makes sense for the FireMonkey Direct2D canvas implementation not to have even tried to implement it.
Because of this, there’s a lot more processing in the app’s main thread, generating the bitmaps as well as just drawing everything onscreen. This is not ideal. It seems FMX apps will unfortunately have to stay away from second-thread graphics processing but this is due to the underlying graphics libraries, specifically here Direct2D. Ie, it’s not really FireMonkey’s fault. If you really need to, you can use a specific graphics library in your other threads, just don’t use lots of plain TCanvases and TBitmaps and expect it to work – keep them in one thread. Graphics32 and VPR might be worth investigating.
- Also, hacking thread support into a non-threaded library you’re not overly familiar with is a hard task. Just saying. Fun though.
- There seems to be no cross-platform Delphi RTL equivalent of WaitForMultipleObjects. The TEvent class wraps an event, but what happens when you want to wait for two of them? This would make a good open source class, I think (a list of events, with wait methods?) but right now this app’s code re-uses one event for several things making it a bit complicated.
- If you want to interrupt a TIdHTTP downloading (via blocking call TidHTTP.Get), call TIdHTTP.Disconnect. The object seems to be reusable afterwards for a subsequent Get call. I use Disconnect to achieve fast termination of thread downloading when I want to stop the background thread objects and need to wait on them, so need them to stop quickly.
- The Quartz canvas on OSX is noticeably slower than Direct2D. I think, without measuring, it might even be slower than GDI+. I am curious why this is and if anyone else has seen the same thing in their FireMonkey apps.
- TImageControl on OSX does not render correctly when you write to its Bitmap. I traced through the code and am not sure what it’s doing – the code looks valid – but I can state that after drawing onto the Bitmap what was displayed onscreen, a blank solid color, was wrong. Since I just needed a canvas to draw on and a control to give mouse events, I ended up just using a TPanel.
- A FireMonkey TForm is missing some seemingly obvious events: there is no OnClick or OnDblClick. I shouldn’t need a client-aligned panel to give mouse click events, but I did.
- The look of FireMonkey on Windows has improved greatly since XE2, and is quite close to how native Windows controls / the VCL looks. The following image (click to expand) has exactly the same controls placed in the same position with the same dimensions on a XE2 FMX form, a XE4 FMX form and VCL FMX form. The XE2 one doesn’t look very native; the XE4 one is quite similar to the VCL.
In some cases I think FireMonkey is better. Look how the TTrackBar’s edges align nicely in FireMonkey, for example, but don’t in the genuine native control – something that bugs me every time I use the real one.
Notes about the app itself
- It uses Direct2D instead of GDI+ on Windows whenever possible. See this post delving into the various types of FireMonkey canvas and when it chooses to use which. You can use the unit I wrote to turn on D2D in your own apps too.
- It’s not perfect. (I’m specifically not happy with the threading code.) Don’t look too closely.
FireMonkey in practice
- The point of the app was a write a non-trivial FireMonkey app and see, in practice, what issues arose. I can confidently state I have learned a lot about FireMonkey building this app. That was the goal.
Has there been anything particularly bad? I don’t think so. There were three areas:
- Bugs: none serious. Graphics performance is easily fixable.
- Cross-platform: some code is slightly less clear than it could be, because I’ve stuck to using the cross-platform RTL and FMX only. (For example, had I been able to drop back to using WaitForMultipleObjects(Ex) some code might have been clearer.)
- Framework: FireMonkey is different to the VCL, and I limited myself to sticking to it and the Delphi RTL / libraries only rather than using, say, Windows API code. This is no different to learning and using any other framework: the same problems occur learning the .Net libraries, Cocoa, etc.
- On the whole, FireMonkey is a good framework the details of which you have to learn, like any other. It’s backed up by the Delphi RTL which is fairly comprehensive. Put it this way, if you can write a VCL app using Delphi, you can write a FireMonkey one.
- Finally, thanks to François Piette, who submitted some changes to port from XE2 to XE5. I integrated his changes which improved the code – especially bitmap generation from the downloaded data – greatly. Thanks François!
- Download version 1.1 or find the source code here. Windows 32, 64 and OSX 32.
Next up will be something completely different.