Thursday, November 29, 2012

Quickie - Qt*Gui*.js - It (Mostly) Lives :)

A quick update to my previous blogs about trying to get Qt ported over to the wonderful Emscripten, so that a Qt + C++ app can be compiled to Javascript and HTML5, and so run in a browser. When last we met, I had QtCore ported, and was starting to look at making QtGui work, using QWS. I quote:
Qt's QWS, at a cursory glance, seems like it does a *huge* amount of stuff for you, mainly leaving the tasks of flushing pixels to screen (using Canvas's "putImageData" method, in this case) and providing it with mouse and key events. I'm sure that there's a whole host of little devils lurking in the details, though :)
I was right on both counts - QWS is awesome, and fundamentally there is very little that one needs to do, but I certainly have been beset my devils along the way :) I'm pleased to report that I've (mostly) got it working, with some caveats.
Humble Beginnings
Slightly-Less-Humble Not-Quite Beginnings
That's More Like It

So now we have Qt's QGraphicsWidget based "chips" demo running in a browser, with only very minimal changes! But here come those caveats:

1) At certain angles and sizes, the "chips" will stop rendering entirely. I haven't begun to look into this yet, but the "embedded dialogs" has this even worse - none of the graphics scene is drawn at all :/ If I were to guess, I'd guess that there is some floating point issue cropping up somewhere.

2) Currently, Google's "Closure" compiler - which Emscripten optionally uses to compress the Javascript to acceptable levels - is generating incorrect code that will bomb out at runtime. When it works, I would estimate that it would compress the "chips" demo down from its massive 100MB(!) (unoptimised build) / 38MB (optimised build) to a less massive 12MB. Any gzip'ing done by the web server should then bring this down to a more svelte ~500k or so. Hopefully :) I haven't begun to try to diagnose this yet. Having "Closure" work correctly is nice beyond the bandwidth cost as it reduces the start-up time and (I think) the peak memory usage.

3) Much more annoyingly, and something that swallowed up all of the last two days - you will often get huge memory spikes in Chrome and long (~30s) hangs where the browser asks if you want to kill the page or not. It eventually recovers and drops the memory usage back down, but this is still unacceptable. This has had me completely baffled, to the point where I was engaged in "Magical Thinking" about what could be causing it - especially as it *doesn't* seems to occur with an unoptimised build (I think it is builds that have been run through "Relooper" in particular that are causing the problem, but will need to experiment further).

Using the Chrome profiling tools, it seems that the QStyle/ QPlastiqueStyle draw*Control methods always swallow up the lions share of the CPU during these episodes. Looking at the generated code, I see that there are over a thousand(!) local Javascript variables and "constants" (constant during that particular invocation, that is) being declared and initialised - indiscriminately - at the top of the method, and if this method is running several times in quick succession - as it is - it's possible that Chrome is quickly racking up memory usage then doing a lengthly garbage collection. This is all speculation on my part, though (there's that "magical thinking" again!) and needs further investigation.

A potentially worthwhile optimisation here would be to tuck some of these invocation-constants into the case statements where they are actually used, which should generally speed things up and reduce memory consumption. I'm still baffled as to why this doesn't happen when Relooper is used, though. Firefox doesn't seem to suffer from this, but is in general much slower.

So, in general, I'm cautiously optimistic about this. I won't be linking directly to demo pages due to their current massive sizes, but here are links to compress versions that you can play with. One is completely unoptimised and hence very slow, but doesn't hang Chrome, whereas the other is a lot faster but occasionally does :/

Chips Unoptimised

Chips Optimised

(zip file containing both - ~17MB download)

The actual patches to chips were tiny: just the standard job of making sure that all local variables in main() that need to persist for the lifetime of the app are allocated on the heap, not the stack (in Emscripten apps - well, Javascript in general, really - there is no possibility of local event processing loops like we use when we call QApplication::exec() - so QApplication::exec() will exit immediately, and then so will main())

I'm going to spend the rest of today a) resting and b) making a project page & documentation so that other people can play with it. If anyone has any questions in the meantime, please ask away :)

PS - it also no longer works with Konqueror or Rekonq, sadly: I thought I could get away with not using typed arrays - which Konqueror and Rekong don't appear to support(?) - but it looks Qt really does need them.

Update:I'm adding some documentation here, but it's still very incomplete and I'm too tired right now to add to it :)

Update2:Looks like my hunch about the hangs being due to large numbers of local variables might have been correct - I've "tucked" a few hundred local variables from some of the problematic functions into smaller scopes using a Vim macro, and, while it still hangs in Chromium from time to time, it is far less severe. Find it here!

Monday, November 19, 2012

Quickie - QtCore.js Progress Update

A quick follow-up to my previous blog here. In a nutshell - I'm experimenting with using Emscripten to try and compile Qt+C++ apps to Javascript and HTML5, so that they can be run in a browser (with some limitations).

Well, looks like I got lazy again ;) But having just restarted working on this, I now have QFile support working, and more importantly, a limited "event loop" which now means I can use QTimer, queued connections, posting events etc. This represents a big chunk of QtCore, and although I wasn't in any doubt that they would work, it's nice to see that all the moc+signal+slot stuff works seamlessly :)

I'll now start working on the GUI part. Qt's QWS, at a cursory glance, seems like it does a *huge* amount of stuff for you, mainly leaving the tasks of flushing pixels to screen (using Canvas's "putImageData" method, in this case) and providing it with mouse and key events. I'm sure that there's a whole host of little devils lurking in the details, though :)

Konqueror and Rekonq now work (I suppose they don't support the typed arrays which are Emscripten's default); the current test example can be found here: it is 2.3MB, but if your browser (and my webserver) support gzip, then it should be about a 280k download. Gobbles quite a bit of RAM, though :/

Source code for the example:

timer-qt.cpp
timer-qt.h

Tuesday, November 6, 2012

Quickie - QtCore.js proof-of-concept

Wow - long time, no blog. I've recently being doing some work on Kate's Vim mode and should really be continuing to work on that (I'll blog about it when I have something more substantial to report), but got side-tracked when I heard about the remarkable Emscripten project, and starting pondering whether I could get Qt to work on it.

For the uninitiated - Emscripten leverages the awesome LLVM and clang projects to take code compiled using clang - usually C/C++, but presumably any language which clang can compile and for which there is an Emscripten-ified library/ runtime available - and converts it into Javascript. After about 15 hours of work, I finally have Qt4 QtCore Emscripten-ified sufficiently well to print a simple, QString-based "Hello, world!". Warning: This is a big (13MB!) webpage, which will require a decent amount of RAM:

Hello, QtCore.js!

The original C++ source is the trivially simple one from here.

It doesn't seem to work in Konqueror/ Rekonq at the moment, but then, very few of the official demos work there, either. I haven't looked into why, yet.

There's a lot left to do - obviously, one of the major issues is that I haven't even looked at QtGui yet, and I haven't implemented the QtCore.js approximation of the event loop - but it should be a fun mini-project :) I'm not sure how far I'll take it, though - the resulting Javascript is impractically huge (although currently I don't think I'm applying any of Emscriptens - or maybe even llvm's - optimisations, yet) and that's just for QtCore. Plus, there are some limitations that are the result of using Javascript - no QThread support, for a start (and HTML5's web workers offer no help, here) and also it will almost certainly be impossible to have local event loops, meaning that any app that uses the *::exec methods for their pop up menus, message boxes, network activity etc will have to be re-worked to use the asyncronous versions instead.

If anyone's interested, I'm blog again in a few days time, if I've made any progress. If any Trolls want to give me some hints on how to get QtGui + plus a simple window system up-and-running using HTML5 Canvases, that would be hugely appreciated :)

Quick update: I turned the optimisations on, and it shrunk to a much more reasonable 1.8MB :)