PhobosLab http://www.phoboslab.org/log Latest news en MPEG1 Video Decoder in JavaScript http://www.phoboslab.org/log/2013/05/mpeg1-video-decoder-in-javascript <p>With still no common video format for HTML5 in sight, I decided to implement an MPEG1 decoder in JavaScript. I know there's already an <a href="https://github.com/mbebenita/Broadway">h264 decoder for JavaScript</a> around, but it's huge, compiled with <a href="https://github.com/kripken/emscripten">emscripten</a> and quite complicated.</p> <p>An MPEG1 decoder sounded like a relatively simple and fun weekend project. While the real world use cases for this are of course a bit limited, I still learned a whole lot about video codecs in the process. The size of the source is just around 15kb gzipped and the performance is quite <em>okay-ish</em> - a 320x240 video easily plays with 30fps on the iPhone5.</p> <p><a href="http://www.phoboslab.org/log/2013/05/mpeg1-video-decoder-in-javascript">Read complete post &raquo;</a></p> Tue, 07 May 2013 18:09:53 +0200 http://www.phoboslab.org/log/2013/05/mpeg1-video-decoder-in-javascript How much Traffic is too much Traffic for CloudFlare? http://www.phoboslab.org/log/2013/02/how-much-traffic-is-too-much-traffic-for-cloudflare <p>Evidence suggests it's 100TB per month.</p> <p>Before I go into the details I want to state two things first:</p> <ul> <li>CloudFlare generously provided most of the bandwidth for our site for a year, without any hiccups.</li> <li>We (unknowingly) violated their TOS. However, I was assured that was not the reason we were kicked.</li> </ul> <p>So the reason I'm writing this is not because we were kicked (after all, CloudFlare was in the right to do so), but because of how shitty it went down. </p> <p><a href="http://www.phoboslab.org/log/2013/02/how-much-traffic-is-too-much-traffic-for-cloudflare">Read complete post &raquo;</a></p> Wed, 13 Feb 2013 18:39:03 +0100 http://www.phoboslab.org/log/2013/02/how-much-traffic-is-too-much-traffic-for-cloudflare Ejecta http://www.phoboslab.org/log/2012/09/ejecta <p>Ejecta is a fast JavaScript, Canvas &amp; Audio implementation for iOS. Today, I'm releasing it under the MIT Open Source license.</p> <iframe src="http://player.vimeo.com/video/50138422" width="500" height="281" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe> <p>Visit the <a href="http://impactjs.com/ejecta">Ejecta website</a> for more info on what it is and how to use it. I will talk a bit more about some implementation details for the Canvas API here.</p> <p>Implementing a general purpose drawing API, such as the HTML5 Canvas API, on top of OpenGL is by no means an easy endeavor. Before I decided to roll my own solution (you know, <a href="http://www.phoboslab.org/log/2010/10/i-have-a-problem">I have this problem</a>), I looked at a number of graphic libraries including Google's <a href="http://code.google.com/p/skia/">skia</a> and <a href="http://www.khronos.org/openvg/">OpenVG</a>.</p> <p>I discovered exactly what I feared beforehand: these libraries do way too much, are too large and too hard to implement. You can't just use them here and there to draw – instead they replace your whole drawing stack. Getting them to compile alone is a huge pain; getting them to compile on the iPhone and then get them do what you wanted to seemed close to impossible.</p> <p>So I began working on my own solution. Implementing the path methods for <code>moveTo()</code>, <code>lineTo()</code>, <code>bezierCurveTo()</code>, etc. was fairly straight forward: have an array of subpaths where each subpath is an array of points (x,y). Each call to the API methods pushes one or more points to the subpath or closes it.<br/></p> <p>However, I struggled a bit with getting bezier curves to behave in a manner that makes sense for the current scale; i.e. push more points for large bezier curves and at sharp corners, fewer points for smaller ones and straight lines. After a few days of reading and experimenting, I found this <a href="http://www.antigrain.com/research/adaptive_bezier/index.html">excellent article on adaptive bezier curves</a> and adopted its solution.</p> <p>The hard part was getting that array of points on the screen. For drawing lines (<code>.stroke()</code>) I didn't want to go with the obvious solution of just using GL_LINES, because it has a number of drawbacks, especially on iOS: no anti aliasing, limited line width and no miters or line caps.</p> <p>So instead of using GL_LINES to draw, I ended up creating 2 triangles for each line segment and calculate the miter values myself. This correctly honors the APIs <code>.miterLimit</code> property, though the bevel it then draws is still a bit off. The code I ended up with is a bit on the ugly side, because it handles a lot of edge cases, but all in all this solution worked very well and is extremely fast.</p> <p>Implementing <code>.fill()</code> proved to be yet another challenge. With OpenGL, before you can draw a primitive to the screen, you have to break it down into triangles first. This is quite easy to do for convex polygons, but not so much for concave ones that potentially have holes in them.</p> <p>I spent a few days looking for triangulation library and soon realized that this is serious business. <a href="http://www.cs.cmu.edu/~quake/triangle.html">Triangle</a> for instance, sports 16k loc – I'm quite allergic to libraries that need that much code to solve seemingly simple problems. <a href="http://code.google.com/p/poly2tri/">Poly2Tri</a> looked much more sane, but apparently has some stability problems.<br/></p> <p>After a bit of searching, I found <a href="http://digestingduck.blogspot.de/2009/07/libtess2.html">libtess2</a>, which is based on OpenGL's libtess and is supposed to be extremely robust and quite fast. The code base is excellent and I had no problem implementing it with Ejecta.</p> <p>However, some tests showed that it's much slower than I hoped it would be. Realtime triangulation of complex polygons isn't very feasible on the iPhone.</p> <p>In the end, I found a trick that lets you draw polygons in OpenGL without triangulating them first. It is so simple and elegant to implement, yet so ingenious: You can draw polygons with a simple triangle fan and mark those areas that you overdraw in the stencil buffer. See <a href="http://fly.cc.fer.hr/~unreal/theredbook/chapter13.html">Drawing Filled, Concave Polygons Using the Stencil Buffer</a>. It's a hacker's solution – thinking outside the box – and it fills me with joy.</p> <p>There's still some parts missing in my Canvas implementation, namely gradients, shadows and most notably: text. I believe the best solution for drawing text in OpenGL, while honoring the Canvas spec, would be drawing to a texture using the iPhone's CG methods. This will make it quite slow, but should be good enough for a few paragraphs of text.</p> <p>If you want to help out with anything grab the <a href="https://github.com/phoboslab/Ejecta">Ejecta source code on github</a> – I'd be honored. </p> Wed, 26 Sep 2012 15:58:15 +0200 http://www.phoboslab.org/log/2012/09/ejecta Drawing Pixels is Hard http://www.phoboslab.org/log/2012/09/drawing-pixels-is-hard <p>Way harder than it should be.</p> <p>Back in 2009 when I first started to work on what would become my HTML5 game engine <a href="http://impactjs.com/">Impact</a>, I was immediately presented with the challenge of scaling the game screen while maintaining crisp, clean pixels. This sounds like an easy problem to solve – after all Flash did this from day one and &quot;retro&quot; games are a big chunk of the market, especially for browser games, so it really should be supported – but it's not.</p> <p>Let's say I have a game with an internal resolution of 320×240 and I want to scale it up 2x to 640×480 when presented on a website. With the HTML5 Canvas element, there are essentially two different ways to do this.</p> <p><em>a)</em> Creating the Canvas element in the scaled up resolution (640×480) and draw all images at twice the size:</p> <pre> <span class="K">var</span> canvas = document.createElement(<span class="S">'canvas'</span>); canvas.width = <span class="N">640</span>; canvas.width = <span class="N">480</span>; <span class="K">var</span> ctx = canvas.getContext(<span class="S">'2d'</span>); ctx.scale( <span class="N">2</span>, <span class="N">2</span> ); ctx.drawImage( img, <span class="N">0</span>, <span class="N">0</span> ); </pre> <p><em>b)</em> Using CSS to scale the Canvas – In my opinion this is the cleaner way to do it. It nicely decouples the internal canvas size from the size at which it is presented:</p> <pre> <span class="K">var</span> canvas = document.createElement(<span class="S">'canvas'</span>); canvas.width = <span class="N">320</span>; canvas.width = <span class="N">240</span>; canvas.style.width = <span class="S">'640px'</span>; canvas.style.width = <span class="S">'480px'</span>; <span class="K">var</span> ctx = canvas.getContext(<span class="S">'2d'</span>); ctx.drawImage( img, <span class="N">0</span>, <span class="N">0</span> ); </pre> <p>Both methods have a problem though – they use a bilinear (blurry) filtering instead of nearest-neighbor (pixel repetition) when scaling.</p> <p><img class="center" src="http://www.phoboslab.org/files/images/biolab-scaled.png" alt=""/> <em>What I wanted (left) vs. what I got (right)</em></p> <p>For the internal scaling approach (method <em>a</em>), you can set the context's <code>imageSmoothingEnabled</code> property to <code>false</code> in order to have crisp, nearest-neighbor scaling. This has been supported in Firefox for a few years now, but Chrome only just recently implemented it and it is currently unsupported in Safari (including Mobile Safari) and Internet Explorer (<a href="http://jsfiddle.net/VAXrL/190/">test case</a>).</p> <p>When doing the scaling in CSS (method <em>b</em>), you can use the <a href="https://developer.mozilla.org/en-US/docs/CSS/Image-rendering">image-rendering</a> CSS property to specify the scaling algorithm the browser should use. This works well in Firefox and Safari, but all other browsers simply ignore it for the Canvas element (<a href="http://jsfiddle.net/VAXrL/21/">test case</a>).</p> <p>Of course Internet Explorer is the only browser that currently doesn't support any of these methods.</p> <p>Not having crisp scaling really bothered me when I initially started to work on Impact. Keep in mind that at the time no browser supported either of the two methods described above. So I experiment a lot to find a solution.<br/></p> <p>And I found one. It's incredibly backwards and really quite sad: I do the scaling in JavaScript. Load the pixel data of each image, loop through all pixels and copy and scale the image, pixel by pixel, into a larger canvas then throw away the original image and use this larger canvas as the source for drawing instead.</p> <pre> <span class="K">var</span> resize = <span class="K">function</span>( img, scale ) { <span class="C">// Takes an image and a scaling factor and returns the scaled image </span> <span class="C">// The original image is drawn into an offscreen canvas of the same size </span> <span class="C">// and copied, pixel by pixel into another offscreen canvas with the </span> <span class="C">// new size. </span> <span class="K">var</span> widthScaled = img.width * scale; <span class="K">var</span> heightScaled = img.height * scale; <span class="K">var</span> orig = document.createElement(<span class="S">'canvas'</span>); orig.width = img.width; orig.height = img.height; <span class="K">var</span> origCtx = orig.getContext(<span class="S">'2d'</span>); origCtx.drawImage(img, <span class="N">0</span>, <span class="N">0</span>); <span class="K">var</span> origPixels = origCtx.getImageData(<span class="N">0</span>, <span class="N">0</span>, img.width, img.height); <span class="K">var</span> scaled = document.createElement(<span class="S">'canvas'</span>); scaled.width = widthScaled; scaled.height = heightScaled; <span class="K">var</span> scaledCtx = scaled.getContext(<span class="S">'2d'</span>); <span class="K">var</span> scaledPixels = scaledCtx.getImageData( <span class="N">0</span>, <span class="N">0</span>, widthScaled, heightScaled ); <span class="K">for</span>( <span class="K">var</span> y = <span class="N">0</span>; y &lt; heightScaled; y++ ) { <span class="K">for</span>( <span class="K">var</span> x = <span class="N">0</span>; x &lt; widthScaled; x++ ) { <span class="K">var</span> index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * <span class="N">4</span>; <span class="K">var</span> indexScaled = (y * widthScaled + x) * <span class="N">4</span>; scaledPixels.data[ indexScaled ] = origPixels.data[ index ]; scaledPixels.data[ indexScaled+<span class="N">1</span> ] = origPixels.data[ index+<span class="N">1</span> ]; scaledPixels.data[ indexScaled+<span class="N">2</span> ] = origPixels.data[ index+<span class="N">2</span> ]; scaledPixels.data[ indexScaled+<span class="N">3</span> ] = origPixels.data[ index+<span class="N">3</span> ]; } } scaledCtx.putImageData( scaledPixels, <span class="N">0</span>, <span class="N">0</span> ); <span class="K">return</span> scaled; } </pre> <p>This worked surprisingly well and has been the easiest way to scale up pixel-style games in Impact from day one. The scaling is only done once when the game first loads, so the performance hit isn't that bad, but you still notice the longer load times on mobile devices or when loading big images. After all, it's a stupidly costly operation do to, even in native code. We usually use GPUs for stuff like that.</p> <p>All in all, doing the scaling in JavaScript is not the &quot;right&quot; solution, but the one that works for all browsers.<br/></p> <p>Or rather <em>worked</em> for all browsers.</p> <h2>Meet the retina iPhone</h2> <p>When Apple introduced the iPhone 4, it was the first device with a <em>retina</em> display. The pixels on the screen are so small, that you can't discern them. This also means, that in order to read anything on a website at all, this website has to be scaled up 2x.</p> <p>So Apple introduced the <code>devicePixelRatio</code>. It's the ratio of real hardware pixels to CSS pixels. The iPhone 4 has a device pixel ratio of 2, i.e. one CSS pixel is displayed with 2 hardware pixels on the screen.</p> <p>This also means that the following canvas element will be automatically scaled up to 640×480 hardware pixels on a retina device, when drawn on a website. Its internal resolution, however, still is 320×240.</p> <pre> &lt;canvas width=<span class="S">&quot;320&quot;</span> height=<span class="S">&quot;240&quot;</span>&gt; </pre> <p>This automatic scaling again happens with the bilinear (blurry) filtering by default.</p> <p>So, in order to draw at the native hardware resolution, you'd have to do your image scaling in JavaScript as usual but with twice the scaling factor, create the canvas with twice the internal size and then scale it <em>down</em> again using CSS.</p> <p>Or, in recent Safari's, use the <code>image-rendering: -webkit-optimize-contrast;</code> CSS property. Nice!</p> <p>This certainly makes things a bit more complicated, but <code>devicePixelRatio</code> was a sane idea. It makes sense.</p> <h2>Meet the retina MacBook Pro</h2> <p>For the new retina MacBook Pro (MBP), Apple had another idea. Instead of behaving in the same way as Mobile Safari on the iPhone, Safari for the retina MBP will automatically create a canvas element with twice the internal resolution than you requested. In theory, this is quite nice if you only want to draw shapes onto your canvas - they will automatically be in retina resolution. However, it significantly breaks drawing images.</p> <p>Consider this Canvas element:</p> <pre> &lt;canvas width=<span class="S">&quot;320&quot;</span> height=<span class="S">&quot;240&quot;</span>&gt;&lt;/canvas&gt; </pre> <p>On the retina MBP, this will actually create a Canvas element with an internal resolution of 640×480. It will still behave as if it had an internal resolution of 320×240, though. Sort of.<br/></p> <p>This ingenious idea is called <code>backingStorePixelRatio</code> and, you guessed it, for the retina MBP it is <code>2</code>. It's still <code>1</code> for the retina iPhone. Because… yeah…</p> <p>(Paul Lewis recently wrote a nice article about <a href="http://www.html5rocks.com/en/tutorials/canvas/hidpi/">High DPI Canvas Drawing</a>, including a handy function that mediates between the retina iPhone and MBP and always draws in the native resolution)</p> <p>Ok, so what happens if you now draw a 320×240 image to this 320×240 Canvas that in reality is a 640×480 Canvas? Yep, the image will get scaled using bilinear (blurry) filtering. Granted, if it wouldn't use bilinear filtering, this whole pixel ratio dance wouldn't make much sense. The problem is, there's no <em>opt-out</em>.</p> <p>Let's say I want to analyze the colors of an image. I'd normally just draw the image to a canvas element retrieve an array of pixels from the canvas and then do whatever I want to do with them. Like this:</p> <pre> ctx.drawImage( img, <span class="N">0</span>, <span class="N">0</span> ); <span class="K">var</span> pixels = ctx.getImageData( <span class="N">0</span>, <span class="N">0</span>, img.width, img.height ); <span class="C">// do something with pixels.data... </span></pre> <p>On the retina MBP you can't do that anymore. The pixels that <code>getImageData()</code> returns are interpolated pixels, not the original pixels of the image. The image you have drawn to the canvas was first scaled up, to meet the bigger backing store and then scaled down again when retrieved through <code>getImageData()</code>, because <code>getImageData()</code> still acts as if the canvas was 320×240.</p> <p>Fortunately, Apple also introduced a new <code>getImageDataHD()</code> method to retrieve the real pixel data from the backing store. So all you'd have to do is draw your image to the canvas with half the size, in order to draw it at the real size. Confused yet?</p> <pre> <span class="K">var</span> ratio = ctx.webkitBackingStorePixelRatio || <span class="N">1</span>; ctx.drawImage( img, <span class="N">0</span>, <span class="N">0</span>, img.width/ratio, img.height/ratio ); <span class="K">var</span> pixels = <span class="K">null</span>; <span class="K">if</span>( ratio != <span class="N">1</span> ) { pixels = ctx.webkitGetImageDataHD( <span class="N">0</span>, <span class="N">0</span>, img.width, img.height ); } <span class="K">else</span> { pixels = ctx.getImageData( <span class="N">0</span>, <span class="N">0</span>, img.width, img.height ); } </pre> <p>(Did I say it's called <code>getImageDataHD()</code>? I lied. You gotta love those vendor prefixes. Imagine how nice it would be if there also was a <code>moz</code>, <code>ms</code>, <code>o</code> and a plain variant!)</p> <h2>The &quot;Good&quot; News</h2> <p>Ok, take a deep breath, there are <em>only</em> 3 different paths you have to consider when drawing sharp pixels on a scaled canvas.</p> <ul> <li>Check the backingStorePixelRatio. If it's not <code>1</code>, divide your canvas size and the destination size of all image draw calls by it, then scale the canvas element up using CSS and the <code>image-rendering</code> property. (Safari)</li> <li>Check if the <code>imageSmoothingEnabled</code> property is available and if so, set it to false. Create your Canvas in the final, scaled size and draw all images with your scaling factor. Don't use CSS to scale the Canvas. (Chrome, Firefox)</li> <li>Use JavaScript to scale up all images at load time. (Internet Explorer)</li> </ul> <p><br/> The CSS <code>image-rendering</code> property and the Canvas' <code>imageSmoothingEnabled</code> really make things a bit easier, but it would be nice if they were universally supported. Especially Safari is in desperate need for <code>imageSmoothingEnabled</code>-support, with all the crazy retina stuff they have going on.</p> <p>Let me also go on record saying that <code>backingStorePixelRatio</code> was a bad idea. It would have been a nice <em>opt-in</em> feature, but it's not a good default. A <a href="http://disq.us/8bint4">comment from Jake Archibald</a> on Paul Lewis' article tells us why:</p> <blockquote> <p>&lt;canvas&gt; 2D is a bitmap API, it's pixel dependent. An api that lets you query individual pixels shouldn't be creating pixels you don't ask for.</p> </blockquote> <p>Apple's <code>backingStorePixelRatio</code> completely breaks the font rendering in Impact, makes games look blurry and breaks a whole bunch of other apps that use direct pixel manipulation. But at least Apple didn't have to update all their dashboard widgets for retina resolution. How convenient!</p> <p><em>Update September 18th 2012:</em> To demonstrate the bug in Safari, I build another <a href="http://www.phoboslab.org/crap/backingstore/">test case</a> and filed a report with Apple.</p> Fri, 14 Sep 2012 01:01:43 +0200 http://www.phoboslab.org/log/2012/09/drawing-pixels-is-hard Where the Innovation Stops http://www.phoboslab.org/log/2012/08/where-the-innovation-stops <p>Some time ago a rather large US entertainment company bought a few licenses for my HTML5 Game Engine <a href="http://impactjs.com/">Impact</a>. They used it for internal prototyping and, as they told me, were quite happy with it.<br/></p> <p>A few month later I got an email from the company's legal department. They asked me to sign an amendment to the Impact Software license agreement with three additional terms. I assume they wanted to publish a game they made with Impact.</p> <p>The first term stated that I would not be allowed to use their company's name in marketing material. Fine with me. I wouldn't do that without asking beforehand anyway.</p> <p>The other two terms however felt a <em>bit</em> strange.</p> <p>But let's back off for a second. As you may know, I'm the sole author of Impact. I'm based in Germany and self employed. It's a one man show. I'm providing Impact without any warranty and I'm not liable for any damages my software may cause (6. &amp; 7. in the <a href="http://impactjs.com/impact-commercial-software-license-agreement">license agreement</a>). Pretty standard.</p> <p>Now, the license amendment I was to sign stated that my software <em>&quot;does not use, embed or incorporate any software which is subject to any open source or other similar types of license terms&quot;</em>. What? Why? How? Is there <strong>any</strong> software out there that truly honors this term?</p> <p>At this point it's already clear that I can't sign this. Impact uses <a href="http://ejohn.org/blog/simple-javascript-inheritance/">John Resig's Simple Inheritance</a>, <code>Array.erase</code> and <code>Function.bind</code> as found in <a href="http://mootools.net/">MooTools</a>, parts of <code>DOMReady</code> as found in <a href="http://jquery.com/">jQuery</a> and some more snippets and boilerplate code that I would consider public domain.</p> <p>Typical Huge Company™ I thought. Kind of cute.</p> <p>The last term however is where it gets truly frightening. In short, I would be held liable for <em>&quot;all damages, liabilities, losses, costs and expenses (including attorneys' fees) relating to any claim, action, suit or proceeding brought by a third party based on any actual or alleged infringement or misappropriation of such third party's intellectual property rights in connection with the use of the software.&quot;</em> This goes on for a few more paragraphs.</p> <p>Let's ignore for a minute that this legalese is written so vaguely that I could be held liable if the company published a game, using my game engine, with art assets they stole from another company. Let's ignore that this multi-billion-dollar company only bought software worth a few hundred USD from me, yet they still want me to pay their legal fees. Ignore that.</p> <p>The core of the problem is another one: The US Legal System and Patent Law.</p> <p>Quite frankly, the US Legal System scares me. A Legal System that is vague enough, emotional enough to have spawned a whole subcategory of dramatic movies is not a good indicator for true justice. The notion that the party with more money wins a trial, the whole jury selection process, the fact that there even is a jury. It all seems so absurd from our perspective.</p> <p>Maybe I have watched too many Hollywood movies. But maybe it really <em>is</em> like this. Just look at the recent Apple vs. Samsung case – a supposedly boring patent trial made convoluted and emotional.</p> <p>If I ever get sued in the US, who knows what will happen. It's truly unpredictable.</p> <p>And in the US there's always a reason to get sued. Impact probably (unknowingly) infringes a whole lot of US software patents. All trivial, all with prior art. Yet, proving so in a court case would absolutely ruin my business and me financially.</p> <p>It's understandable that this entertainment company wanted me to sign their license amendment. It absolutely made sense from their perspective. They are deep in this circus and wanted a bit of certainty that their legal system couldn't provide.</p> <p>I told them I couldn't sign their amendment and never heard back.</p> <p>Sometimes I have the feeling that I'm missing out by not being in Silicon Valley, in the epicenter of Startup culture. I was there for a visit and it was extremely energizing and motivational. I loved the people, the mentality, the atmosphere. Instead, I'm in a small and boring town in Germany.</p> <p>But then again, I feel safer here; I have the freedom to experiment, to innovate. I'm glad that we don't have impeding software patents. Glad that our Legal System is still sane. Glad that my business is based in Germany and not in the US. </p> Fri, 31 Aug 2012 13:44:08 +0200 http://www.phoboslab.org/log/2012/08/where-the-innovation-stops Letter to John Carmack http://www.phoboslab.org/log/2012/08/letter-to-john-carmack <p><em>The following is an email I sent to <a href="https://twitter.com/id_aa_carmack">John Carmack</a> of <a href="http://www.idsoftware.com/">Id Software</a> after I had watched his <a href="http://www.youtube.com/watch?v=wt-iVFxgFWk">Quakecon 2012 Keynote Speech</a>.</em></p> <p><em>Edit: John Carmack posted his response <a href="http://www.phoboslab.org/log/2012/08/letter-to-john-carmack#comment36">below</a>.</em></p> <p>Hi John,</p> <p>I watched the live stream of your keynote speech yesterday, and – as every year – I enjoyed it very much. I applaud your openness; it's always refreshing to hear some insights from the gaming industry that is otherwise very secretive. Thank you!</p> <p>However, I believe your views on JavaScript and WebGL are short sighted.</p> <p>Nobody pretends that the next AAA-title will be written in JavaScript. The community understands that it's magnitudes slower than native code, but the thing is: it just doesn't matter.</p> <p>Many, many types of games don't need the absolute best and fastest environment to offer interesting gameplay and graphics. Take a look at <a href="http://www.phoboslab.org/xtype/">X-Type</a> on your iPad or iPhone for instance.</p> <p>You yourself talked about how frustrating it is that you need 200 lines of code just to set up your screen for rendering on Windows, whereas in the olden days you could just push pixels to the screen with 3 lines of code.</p> <p>We're there again: take 3 lines of JavaScript and you're drawing an image to a canvas element. Take 20 more lines and you have a rendering loop and a sprite that moves with the arrow keys. And all you need is a text editor and your favorite Browser. <strong>That</strong> is what JavaScript is all about.</p> <p>When you're new to programming and still figuring out whether you like it, you don't want to read thick books. You want to see results. Fast. You want pictures dancing on the screen.</p> <p>JavaScript is the language you should teach your son.</p> <p><br/></p> <p>I backed the <a href="http://www.kickstarter.com/projects/1523379957/oculus-rift-step-into-the-game">Oculus Rift Kickstarter</a> yesterday and I fully expect to see significant development for it going on with WebGL. It doesn't matter that JS is &quot;slow&quot; when you're just trying stuff; ease of development is far more important.</p> <p>Coupled with the openness of the Web, I wouldn't be surprised if much, if not most, of the research for applications of the Rift comes out of the JavaScript community.</p> <p><em>Right-Click -&gt; View Source</em> is what made the web so successful and it's awesome that we now have this for 3D graphics as well.</p> <p>Don't hate JavaScript. It's our generation's Apple II.</p> <p>Regards,<br/> Dominic </p> Fri, 03 Aug 2012 13:46:08 +0200 http://www.phoboslab.org/log/2012/08/letter-to-john-carmack What the Fucking Fuck, Apple? http://www.phoboslab.org/log/2012/07/what-the-fucking-fuck-apple <p><strong>Update Tuesday, July 16th 2012:</strong> The Bug appears to be fixed in iOS 6 Beta 3.</p> <p>Almost two years ago I <a href="https://twitter.com/phoboslab/status/6895333497896960">noticed</a> a strange multitouch problem in Mobile Safari with one of my games. This problem was new; it was introduced with the iOS 4.2.1 update and wasn't present on older iOS versions.<br/></p> <p>I built a <a href="http://www.phoboslab.org/crap/mt.html">test case</a> and filed a bug report with Apple. It was marked as a duplicate of a bug they already knew about. I have no idea what that original bug report said, because fuck you, Apple's bug tracker is private. That was almost two years ago.</p> <p>Two years.</p> <p>I re-submitted this bug report 4 more times, after it wasn't fixed in the subsequent iOS updates. It was marked as a duplicate and closed each time. It still hasn't been fixed in the iOS 6 Beta.</p> <p>Two fucking years.</p> <p>Annoyed by this bug, <a href="http://jaysonpotter.com/">Jayson Potter</a> recently contacted me, asking for the bug id and description of my report and re-submitted it by himself. This time it wasn't marked as a duplicate. Instead, he got this reply from Apple a week later:</p> <blockquote> <p>Please know that our engineers have not been able to reproduce this reported behavior with iOS 5.x</p> </blockquote> <p>WHAT THE FUCKING FUCK, APPLE? You need two fucking years to decide that you can't reproduce a bug with a test case so simple that a three year old could understand it?<br/></p> <p>This is beyond frustrating. And there's no alternative to Mobile Safari on iOS; Apple doesn't allow it. On the iPhone, you're as locked in with HTML5 as you were with Flash everywhere else before - you're completely at the mercy of one fucking company.<br/></p> <p>So I'm really desperate to get this bug fixed – I recorded a nice video and voice over, explaining the bug itself and why it's such a big deal. I re-submitted the bug report <strong>again</strong> this morning (Bug #11796586), with a link to it:</p> <iframe src="http://player.vimeo.com/video/45122240" width="500" height="281" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe> <div><em>Demonstrating the bug in the <a href="http://www.phoboslab.org/crap/mt.html">test case</a> and my game <a href="http://playbiolab.com/">Biolab Disaster</a></em></div> <p>All in all I probably spent about 40 hours dealing with this shit: Building the test case, reporting it to Apple, writing documentation that states where this bug is and why it exists, explaining to my customer's that it's not their fault, nor mine, that their on-screen button doesn't work and apologizing to them for the fact that Apple is a fucking ignorant piece of shit company.</p> <p>Fucking fix it, Apple.</p> Tue, 03 Jul 2012 13:42:00 +0200 http://www.phoboslab.org/log/2012/07/what-the-fucking-fuck-apple X-Type – Making Of http://www.phoboslab.org/log/2012/06/x-type-making-of <p>For this year's Google IO, Google asked me to do a <a href="http://www.chromeexperiments.com/mobile/">Chrome experiment for Mobile</a> for them. They initially wanted me to vamp up <a href="http://playbiolab.com">Biolab Disaster</a> – it's still a good game, but because of it's retro style it wouldn't be that impressive. Modern mobile browsers can do a lot more.</p> <p>I suggested I would try to take another game of mine – <a href="http://www.phoboslab.org/xtype/">X-Type</a> – and make it work on mobile browsers. The game was made with my <a href="http://impactjs.com/">JavaScript Game Engine</a>, so it mostly &quot;just worked&quot; on mobile browsers already. Yet, I still had a lot of work to do.</p> <p>Have a look at X-Type over at <a href="http://www.chromeexperiments.com/detail/x-type/">chromeexperiments.com</a>.</p> <iframe src="http://player.vimeo.com/video/44798528" width="500" height="281" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe> <div><em>X-TYPE running on various mobile devices</em></div> <h2>Screen Size</h2> <p>One of the most difficult things for HTML5 games is dealing with the vast amount of different screen sizes and aspect ratios out there. I experimented a lot with different solutions and ended with a fairly simple one: the internal width the game's viewport is always 480px. These 480px get scaled to whatever is available on the device. The height of the viewport is variable, so that it fills the screen with the same scaling factor as the width.</p> <pre> <span class="C">// The internal width for our canvas is fixed at 480px. </span><span class="C">// The internal height is set so that it fills the screen when scaled </span>canvas.width = <span class="N">480</span>; canvas.height = window.innerHeight * (canvas.width / window.innerWidth); <span class="C">// Scale the canvas via CSS to fill the screen </span>canvas.style.width = window.innerWidth + <span class="S">'px'</span>; canvas.style.height = window.innerHeight + <span class="S">'px'</span>; </pre> <p>In older browsers (Mobile and Desktop), scaling the <code>&lt;canvas&gt;</code> element was a horrible idea – it decreased performance to a tenth of what it would be unscaled. I'm happy to report that this is no longer true; Mobile Safari on iOS5 and the Chrome Beta on Android work just fine with scaled canvas. It still makes the game unplayable in Android's &quot;Browser&quot;, though.</p> <p>I also took care to only display the game in portrait mode and show a <em>&quot;Please Rotate the Device&quot;</em> message otherwise. Mobile Safari and Chrome both support the <code>orientationchange</code> event, which makes this easy. However, we can not rely on <code>window.orientation</code>, which reports the rotation in degrees (0, 90, 180 or 270), because some devices report 0° for portrait mode, while others report 0° for landscape. How convenient!<br/></p> <p>The solution is to just check if the window height is bigger than the width – if so, we're obviously in portrait mode! But as this would be too easy, Chrome's Browser offers another challenge for us: it only updates the window dimensions <em>after</em> it has fired the <code>orientationchange</code> event. So we listen for <code>orientationchange</code> <strong>and</strong> <code>resize</code> events. Sigh.</p> <pre> <span class="K">var</span> wasPortrait = -<span class="N">1</span>; <span class="K">var</span> checkOrientation = <span class="K">function</span>() { <span class="K">var</span> isPortrait = (window.innerHeight &gt; window.innerWidth); <span class="K">if</span>( isPortrait === wasPortrait ) { <span class="K">return</span>; <span class="C">// Nothing to do here } </span> wasPortrait = isPortrait; <span class="C">// Do your stuff... </span>}; window.addEventListener( <span class="S">'orientationchange'</span>, checkOrientation, <span class="K">false</span> ); window.addEventListener( <span class="S">'resize'</span>, checkOrientation, <span class="K">false</span> ); </pre> <h2>Performance</h2> <p>Since iOS 5 the <code>&lt;canvas&gt;</code> element is hardware accelerated and it really shows. You can draw hundreds of sprites on the screen without any slowdowns at all. The same is true for Chrome on Android – to a certain degree.</p> <p>All drawing is scheduled via <code>requestAnimationFrame</code> and thus bound to the display's refresh rate. This works nicely on iOS, but Chrome refuses to draw at the 60hz even for the simplest scenes. You can use <code>setInterval</code> to process more frames, but only a portion of them is really presented on the screen.<br/></p> <p>So while Chrome's JavaScript engine is fast enough to process <strong>and</strong> render 60 FPS, it fails to display all of the rendered frames. I no doubt that this bug(?) will get fixed.</p> <p>While the game works in a multitude of other browser's on Android, such as Firefox, Dolphin or Opera, none of them provided good performance. I suspect the <code>&lt;canvas&gt;</code> element is not hardware acclerated in any of these, but didn't investigate further.</p> <h2>Controls</h2> <p>In the desktop version of the game you move the player with the arrow keys and aim and shoot with the mouse. Of course this doesn't work on touch screens, so I opted for dual virtual analog sticks. This worked out surprisingly well – with a bit of practice, you can control your spaceship quite precisely.</p> <p>I also tried to make the analog sticks appear where you touch the screen first, so that you can always change the position. This made everything quite a bit confusing; it's easier to grasp the concept when the position of the analog sticks is fixed. Providing the dynamic positioning is probably more of &quot;pro gamer&quot; feature that should be optional, if at all.</p> <h2>Sound</h2> <p>This is the sad part. I <a href="http://www.phoboslab.org/log/2011/03/the-state-of-html5-audio">complained</a> about support for the <code>&lt;audio&gt;</code> element in mobile browsers last year already – and guess what: it's still the same shit. Apple hasn't done anything at all to improve the situation; same goes for Android's Browser. The Chrome Beta on Android seems to have <em>some</em> support for Audio, but it's not really usable for real time games at the moment. I'll investigate this further.</p> <p>As always, I have high hopes though. Never give up, never surrender!</p> <p>All in all, I'm very pleased with the results. Rendering performance in modern mobile browsers is really awesome and the quirks I encountered were workaroundable.<br/></p> <p>I learned a lot with this project and will use this new gained knowledge to make mobile browser support much more easy in the next version of Impact.</p> Wed, 27 Jun 2012 21:08:28 +0200 http://www.phoboslab.org/log/2012/06/x-type-making-of