The case against native engines

3
Official Construct Team Post
Ashley's avatar
Ashley
  • 6 Jul, 2016
  • 4,512 words
  • ~18-30 mins
  • 3,973 visits
  • 0 favourites

One of the most enduring requests from users is for native exporters. We have always strongly believed this is not necessary, and not even in the interests of our users due to the inevitable consequences of such a shift. I know this will probably be a contentious post, but I still repeatedly see basic misconceptions about what the benefits of native exporters over our HTML5 engine would be. So I don't mean to troll everyone, but I want to outline some of the technical basis for why we don't see native exporters as actually solving many of the issues people raise as reasons to support them, as well as some of the other surrounding issues. And I think we have good reasons to stick to HTML5.

A common reason is performance. Often users assume if their game has a performance problem, native exporters would solve that, even when this is highly doubtful. This post covers several reasons why it's not a silver bullet. Let's look at components of the Construct 2/HTML5 stack and how they compare to native.

It's difficult to make direct comparisons with measurements. Every game engine is fundamentally different, so the only fair comparison would be if we wrote a native port of the Construct 2 engine and compared the HTML5 performance to that. That would be months of work just to collect data for a blog post, so is simply not an option. However I think there are solid technical reasons to believe there would not always be that much difference.

Graphics and rendering

The main bottleneck for most games we see is in rendering. Construct 2 renders using WebGL, which is almost universally supported now. WebGL is close enough to OpenGL to be considered the same performance-wise. In other words, Construct 2 games render in practically the same manner a native engine would.

The Next Penelope, a Construct 2 game published to Steam demonstrating native-grade rendering and effects

WebGL shader effects in particular are actually small programs that run entirely on the GPU. So those effects run identically to the same effects in a native engine. Consequently if your game runs slow due to overuse of WebGL shader effects, moving to a native engine would not improve performance. It would perform exactly the same. This is an important (and recurring) point.

A common GPU performance issue is fillrate. This is basically how fast the GPU hardware can write pixels, which depends on the available memory bandwidth. Often low-end mobile devices, or high-resolution desktop/laptop systems with weak integrated (usually Intel) GPUs, struggle to fill pixels fast enough to maintain 60 FPS. Overdraw - multiple sprites on top of each other - consumes more fillrate, and deeply-layered games often have trouble with this. Again in this case moving to a native engine does not help: the problem is the hardware is not fast enough to do what you want, and the hardware spec does not magically improve because you changed the software. We have seen multiple cases of Construct 2 games performing poorly due to hardware fillrate limitations, but the user blames HTML5, Construct 2, or browsers - even though changing any of those things would not solve the problem.

Physics

An asm.js physics stress test on a 2014 iPad Air 2, managing 1300 objects at 30 FPS, hopefully enough for most games. Note this test includes the overhead of rendering 1300 sprites as well!

Realistic physics simulations are often cited as one of the most CPU-demanding features of a game engine. However we've been defaulting to asm.js-based physics for some time now. asm.js involves compiling native C++ code to a special form of Javascript that browsers can effectively optimise directly in to the same machine code that the C++ compiler would have produced. In practice, this can achieve 70-80% the performance of a native engine. I believe this is close enough to native performance that the difference does not matter much. It's a really impressive result to achieve in a browser - and unlike native code, it runs across platforms and devices! So while physics simulation performance may improve slightly with a native engine, it's probably less than the performance variation between devices in the same generation. So I don't believe this qualifies as a reason to develop a native engine either.

Pathfinding

Pathfinding in Construct 2 runs in parallel to the game, eliminating any impact to the framerate

Similarly to physics, pathfinding is another CPU-intensive operation that can take a while to complete. Fortunately our Pathfinding behavior uses Web Workers (now universally supported) to run pathfinding on a separate thread. This means paths are calculated in parallel to the game continuing to run. Most modern devices are at least dual-core, which allows the operating system to schedule the work on a different CPU core to the one running the game. Even our previous native engine did not do this! Given it has a carefully-tuned A* algorithm which runs pretty quickly anyway, and since as far as I remember nobody has ever complained about pathfinding performance (with a sensible grid size), I think this demonstrates HTML5 can still usefully exploit multi-CPU systems to improve performance.

Collisions

Construct 2 handling 60,000,000 collision checks per second at 60 FPS, by eliminating most of them with collision cells.

Collisions are another often CPU-demanding aspect of games. Accordingly, Construct 2 has a very carefully optimised collisions engine, with many hours of work having gone in to perfecting it. The most important optimisation came in r155 when we added the collision cells optimisation which used improved algorithms to automatically eliminate the vast majority of collision checks in a large game. In many cases this made the performance impact of collisions simply irrelevant. Also Construct 2 only tests collisions to when you ask it to with events, or using a behavior. As a result this area is highly amenable to improvements by rearranging events to be more efficient. Therefore I don't see any need for native code to improve upon this - we already have a great engine to handle this.

Audio

All browsers process audio in a separate thread to the game, with similar benefits to Pathfinding in using multiple CPU cores. Due to the way the Web Audio API is designed, any audio processing such as filters or convolution are processed by native code in the browser on a different thread. Given browsers are so widely used, these algorithms have been very highly optimised, sometimes even by CPU manufacturers like Intel themselves. This definitely is a part of HTML5 which has reached native equivalent - if not better. On the development side, I can also add of all the audio APIs I've used, the Web Audio API is a particularly strong one. Few platforms have an audio API that makes it so easy to set up sophisticated audio processing graphs.

Mobile support

Cordova provides comprehensive mobile support for Construct 2 games

We've been using Cordova for mobile support for some time now with it working well. It's also important to note that while the webviews used to be slow compared to browsers on mobile, as of the latest iOS and Android versions they have the same performance (when using WKWebView on iOS). Cordova also has loads of plugins to cover native features as well, including everything from purchases to notifications, which third party plugin developers often integrate. So native engines are not necessary to support mobile platforms or their specific native features. Meanwhile we are uniquely able to develop the same engine that helps ensure your game ports smoothly between platforms like iOS and Android, since it's literally the same code.

Also consider that modern mobile devices are about as powerful as laptops now. For a long time mobile devices were thought of as weaker, lesser counterparts to desktop systems. I think as time goes by, this is less and less the case. Soon enough, low-end mobiles will have the same spec as today's high-end flagships, and we'll be able to regard mobile and desktop systems in the same performance bracket.

Build systems

PhoneGap Build and Intel XDK, two build systems that can be used for Construct 2 games

Building mobile apps can be tricky, particularly since the configuration and publishing systems provided by companies like Apple and Google are designed for programmers, whereas Construct 2 saves you from having to do the programming work. I particularly like PhoneGap Build for the simplicity of its interface: once configured, you just upload a zip, wait a minute, and it gives you back an APK (Android) or IPA (iOS). The Intel XDK has a somewhat more complicated interface, but achieves the same goal and is completely free. And there are other alternative services like cocoon.io. If you are technical you can also go for local builds with the Cordova CLI (although note this is not an officially supported option - you'll need to be technical enough to follow the guides yourself).

These build systems can indeed be a source of frustration for users - I've found it annoying to configure the necessary certificates and accounts myself. However many aspects of these are similar even with native engines. For example some tools produce an Xcode project, which is another programmer tool that still needs installation, configuring with certificates and device profiles, and still uses the same publishing system. I think PhoneGap Build in particular is actually even easier than this approach - what could be simpler than uploading a zip? I also don't feel like this is a compelling enough reason to spend years of engineering effort on native exporters, just to try and get around this. That's not to say we couldn't make improvements, though. For the time being I think it's fair to say building and publishing requires a little experience and know-how, and once you get your head around it, it's not too much of a hurdle.

People often ask for a simple APK exporter. That's very nearly what PhoneGap Build does, and the Intel XDK also can do it free of charge. It also does not solve all the problems around configuring certificates and your publisher account. Alternatively people ask us to run our own build system. These are difficult to set up and expensive to run. For a small company I feel this would be too much of a distraction, and we'd be competing with a free service by Intel, so I think it would be very hard to offset the cost by charging for it.

Console support

Construct 2 can now publish Xbox One games

We already cover Xbox One and Wii U - so that only leaves PS4. Currently we're blocked by Sony not providing an easy way to publish HTML5 games to the PS4. Given how well our Xbox One support worked - requiring no significant changes to our engine to work on a console - we are keen to see Sony supporting HTML5 on their console too.

Bugs

All software has bugs - and we've closed nearly 5000 bug reports so far, representing the maturity of the Construct 2 engine

All software - in fact all technology - has bugs. All software has both easy bugs and hard bugs. All software uses third-party dependencies too, whether it's the OS, drivers, language libraries, framework libraries, compilers or a browser, and can be affected by bugs in third-party code. Sometimes people suggest native exporters as a fix to a bug. This really is out of the question. Moving technologies will simply trade one set of bugs for another. It is obviously better to just try to fix or at least work around the bugs that there are. There is no platform that works flawlessly.

We also have the existing investment of years of fixes to the current Construct 2 engine. With nearly 5000 bug reports closed so far, it's a daunting prospect to repeat all that work again with any new technologies.

Native engines are also much more vulnerable to what are in my experience pretty much the worst kind of bugs: GPU driver bugs. These are often severe, device-specific, and extremely difficult to track down, to the extent that the debugging process is reduced to making random changes in the hope it will be fixed by luck. I have direct experience of this with both the Construct Classic engine and the Construct 2 editor. Most other graphics programmers are pretty sympathetic about this, it's a well-known issue that GPU drivers are crap. The fact that browser makers effectively do this work for us and implement workarounds or (as a last resort) software rendering is a massively underappreciated benefit of working with HTML5.

V-sync quality

Modern browsers have good v-sync quality - here Chrome hits within 0.1ms of v-sync for the whole sbperftest duration; the dropped frames counted are probably just inaccuracies in the test timers

A while ago Chrome had a bug that made V-sync quality choppy. This was subsequently fixed, but became quite infamous in the process. It was a bad bug, but it never affected Firefox, IE/Edge, or Safari. It affected NW.js for longer, but only because their release cycle at that point meant it took a few months to catch up with Chrome. (As of recently, NW.js updates come within a day or two of a Chrome stable update.)

While a bad case, in this argument I think it amounts to the same reasons listed under "Bugs". There could easily be equally bad problems on other platforms. And ultimately, as I always expected, it was fixed - so it was pointless to use as motivation for starting native engines. The problem would have been resolved long before we had any significant amount of native code written. Many browsers can now v-sync to within 0.1ms of the target time according to engine measurements.

These days people occasionally complain of single-frame skips (aka jank/jitter/micro-stutter). These proved difficult to eliminate entirely even with our old native engine in Construct Classic. Perfectly smooth display requires issuing a new frame every 16.6 milliseconds, without fail, or you see a skip. Despite having a C++/DirectX 9 engine in Classic, the nature of operating system scheduling means that sometimes you just get bad luck. The OS may have hundreds of threads to schedule, and it may just end up doing a bit more work on other threads which means the game thread can't quite meet its deadline. Often fullscreen games are not affected, because the OS boosts the priority of fullscreen content on the assumption you won't be multi-tasking. Construct 2 games can, of course, go fullscreen as well. If you have a lot of browser tabs open it's also probably slightly worse, since the OS has to occasionally service the background content too.

Browser developers are keen to make browser content perfectly smooth, so are still working hard to make this as good as possible, and the Chrome developers are still tuning this too. IE/Edge in particular has had almost perfect v-sync scheduling, showing it's entirely possible in HTML5 and not some kind of fundamental limitation of browsers.

What would be better about native?

I think there really is only one thing that stands to improve with a native engine: the performance of running events. These are constrained to a single core (and that is very difficult to change, even in a native engine) so are subject to single-thread CPU performance. Consequently this is also one of the most carefully and closely optimised areas of the Construct 2 engine. Note by this point we're excluding physics, collisions and pathfinding - this is just the general-purpose logic of handling the game.

The reason I don't feel this justifies building native engines is because I rarely see games that are actually bottlenecked on it. It seems that for most games, the general-purpose logic of games is not that intensive. It's much more common that a user finds a game is slow, blames HTML5, then it turns out to be a GPU fillrate limitation or something else. There's a real knee-jerk reaction to blame web technology for every problem regardless of if it's actually responsible. Events are also entirely in the user's hands, so they can optimise, refactor, and regularly test across devices to make sure they get it right. For example one way to optimise this is deactivating entire event groups when they are not necessary, since then their entire contents are entirely skipped, saving all the CPU time of checking them at all (even to find they are false). Construct 2 also provides tools to track down demanding areas, such as with the profiler.

I'll also repeat the call I regularly make on the forums: if you have a project where the events are not running fast enough, send it to me and I'll see if the engine can be optimised. I very rarely get sent anything, even after specifically asking for it. Often I'm sent something which is clearly a different problem. For example just recently a user was blaming Construct 2 for being slow and asking about native exporters, and it turned out they had disabled WebGL and asm.js physics and forgotten about it. This kind of thing is very common. In a few cases I have been sent something and have actually managed to optimise the engine accordingly, or at least suggest faster alternatives. The rarity of this combined with the frequency of mistakes or the real cause being something that would be no different with native tech is a powerful motivation for us to stay with HTML5 technology.

Render cells demo with 30,000 sprites. Not many games look like this! Note Construct 2 could still render the green viewport area at 60 FPS.

There are some extreme cases though where a native engine really would benefit. Inevitably someone will try to create something like a gigantic effect with ten thousand particles all each individually reacting to some input as a key game mechanic, and it might not work out super-fast. However I don't think that many games are designed like this. If you really need this kind of mechanic, maybe you do need a more specialist tool. For the vast majority of games though, I feel that the engine we have will perform well.

Some people want everything to be as fast as humanly possible simply because it's cool. I used to think like that too! However it was a mistake we made with Construct Classic: in a mad effort to make everything insanely fast, we added so many twists and tricks in the engine that in the end it was pretty buggy and never really worked properly. A user whose game runs 10% faster is a bit happier, but a user whose game fails to run at all is incredibly unhappy. We learned the hard way: reliable software is better than faster but less reliable software.

Even if events could be considerably faster with a native engine, it's worth looking at the downsides.

What would be worse about native?

The big issue here is we are a small team, and a native exporter per platform would mean maintaining perhaps 7 separate codebases (HTML5, Windows/Win32, Windows/UWP, Mac, Linux, iOS, Android), assuming we dropped some of the lesser-used options. I think it is reasonable to assume this would take 5 times more work to develop: in many cases the platforms require different programming languages, APIs and tooling, and a very significant portion of development work is bug fixes (remember those nearly 5000 closed reports) which will have to be handled per-platform - there will only be a few cases of being able to share identical code. For example even just on Windows, the UWP APIs are considerably different to tranditional Win32. Say we release updates typically every 2-3 weeks; this would be equivalent to updates coming maybe 4 times a year instead; we could still be on Construct 2 r46, not r230. I honestly think this would have had a transformative negative impact on the product development. There simply would not be the broad, deep set of features Construct 2 has today.

On top of that, some other tools try to gloss over the difficult bits and simply don't even try to support all the built-in features on all platforms. This is even moreso the case with third-party developers, who often will release an addon for one or two platforms, and nothing else - who could expect them to do more if they're working in their spare time? This can end up with nightmarish support matrices where each platform is a completely different patchwork of supported features. This can make porting your work between platforms a huge headache, or just impossible, despite the advertised cross-platform capability. On the other hand in Construct 2 everything from WebGL shaders to your favourite third-party plugin will work everywhere from iOS to Xbox One. (Some features do depend on browser support still - but where support is missing, often it is only a matter of time for a browser maker to fill in the gap.)

New platforms would deepen the problems above, and involve months of up-front development time to add support in the first place. Our Windows 10 exporter, and the subsequent Xbox One support, both came within weeks of the official release, giving you early access to the opportunity of a new platform. This kind of rapid reaction to the market is impossible with separate codebases.

Due to all this work, we'd probably have to adopt a similar pricing model to other tools, where you pay extra for every platform you want to export to. Construct 2 is far cheaper than many alternatives with the same export capabilities. In short, native exporters could make Construct 2 a lot more expensive.

I can't say the word "horrible" enough about GPU driver bugs. That would be horrible, horrible, horrible. You could expect bugs like "game renders as a black screen on all AMD Radeon HD 6000-series devices on Windows" which then take months to fix, and probably involve us buying one of those cards and building a system with it, just to investigate.

Some people suggest using some broad framework to cover multiple native platforms in one go. However HTML5 itself is still an essential feature even with native exporters, so this could still involve maintaining two parallel codebases - the one for native, and HTML5 separately. On top of that it's hard to see how such a large and comprehensive framework as to make cross-platform development possible wouldn't simply occupy the role of the browser with our current HTML5 engine: a large, complex, well-tested but ultimately imperfect piece of technology that sits between our engine and the OS. Curiously this very solution is sometimes raised in the same discussion where people are critical of relying on third parties (which is unavoidable with modern software development - the only question is who you choose to rely on). In effect, we have chosen the "cross-platform framework" approach: we just chose to use the web platform for that. Browsers are also developed by hundreds of full-time engineers at the top technology companies in the world - Google, Microsoft, Apple and Mozilla. I find it hard to see which other cross-platform technologies can match that level of support and development. Nor is it clear why any other frameworks would actually end up working better when taking in to account the full range of topics mentioned here.

Based on that enormous development input by browser makers hotly contesting for market share, the change we've seen since starting in 2011 has been simply unimaginable. I would never have dared hope Construct 2 games would run on Xbox One back then! We barely even hoped we could support mobile, instead aiming to compete with Flash on desktop. And we expect this stunning pace of improvement to continue in to the long term. I think this is another strong reason to stay right where we are. Few platorms see this pace of progress. I would hate to weigh ourselves down with multiple codebases, only to see another company take up the HTML5-only approach and reap the benefits of all the improvements to come in the next years.

A better solution

While it's not part of our immediate plans, I will throw in one third idea which people seem to rarely consider, but which I think is quite compelling. A native exporter could stand to improve the performance of the event system, which would be important for the most demanding games. I've already shown how asm.js brings physics performance close to the native equivalent. asm.js is progressing in to WebAssembly, a more mature technology (still in development) with enthusiasm from all major browser makers. WebAssembly makes improvements to memory management, code size, and startup time, making it more viable than asm.js for a core part of the engine. Rewriting the core event engine in WebAssembly could in theory bring the best of both worlds: native-grade event performance, and yet as easily cross-platform as normal Javascript.

I think this little-considered option is much more compelling than the heavy-handed approach of multiple native codebases. However it is still a huge engineering project, and would only really serve the relatively small number of games that suffer from poor event performance. WebAssembly also needs a lot of time to finish development and reach browser support, as well as develop the tooling for development with it.

So while I remain skeptical of calls for native exporters, I am actually pretty receptive to the idea of writing parts of the Construct 2 engine in WebAssembly. If you really want better performance, this is probably a better thing to ask for.

Conclusion

Given the regularity of the calls for a native exporter, I doubt this blog post will actually stop the requests, or pacify everyone. I am sure there will be contentious points and counter-arguments. However I think it is worthwhile outlining our case and what I see as some pretty solid reasons for our sometimes controversial choice to remain exclusively a HTML5-powered engine. Too often HTML5 is blamed when not responsible, and too often native technology is seen as a magic bullet that will solve everything. No technology is perfect - all involve a broad range of trade-offs, many of which are not obvious. There are also enormous up-front and lasting costs to Scirra if we make a change which will affect all customers in the long run, and that may not be so obvious from the outside.

As we've mentioned before, the focus of Construct 3 is to rebuild the editor for the future, and we intend to keep the same runtime and export methods as Construct 2 for the time being. (This also helps ensure Construct 2 games will port to Construct 3 smoothly.) However if there is significant demand for a WebAssembly runtime, that is something we could definitely consider in the Construct 3 time frame. In the mean time I hope everyone can appreciate the reasoning behind our technology choices, and takes this in to account when facing issues or requesting features.

Subscribe

Get emailed when there are new posts!