<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5103406499726689782</id><updated>2012-01-30T06:24:35.677+02:00</updated><title type='text'>fhtr</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default?start-index=101&amp;max-results=100'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>310</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-2161122633791961047</id><published>2012-01-29T05:22:00.001+02:00</published><updated>2012-01-29T06:52:18.394+02:00</updated><title type='text'>Shooting high ISO in broad daylight</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-nCTBjE1YABA/TyQeYd__q9I/AAAAAAAAINg/2JHS9Jbdz8I/s1600/DSC_5134.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="422" src="http://3.bp.blogspot.com/-nCTBjE1YABA/TyQeYd__q9I/AAAAAAAAINg/2JHS9Jbdz8I/s640/DSC_5134.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;DSLRs these days get usable results even at super-high ISO (sensor sensitivity to light, higher it is the less light you need). But, um, what's the use of ISO 25600 if most of your shooting happens in bright daylight (or anything other than pitch-black darkness). Let's think!&lt;br /&gt;&lt;br /&gt;What you get from cranking up ISO is the capability to use faster shutter speeds and tighter apertures. So you get photos with less motion blur and more of the image in focus. So you could shoot in motion and not need to focus. This is starting to sound useful. Shoot while walking without having to stop.&lt;br /&gt;&lt;br /&gt;How about framing though? Having your camera up on your eye when you're walking up stairs sounds like a recipe for broken bones. Shooting from the hip would be nicer, but now you can't see what's in frame. Wide-angle lenses to the rescue! Get enough of the scene in the image that you'll likely have your subject in the frame, and do the framing in post.&lt;br /&gt;&lt;br /&gt;It's really fast to take photos if you don't have worry about focus or framing. Point camera at what you're interested in, press shutter, done.&lt;br /&gt;&lt;br /&gt;High ISO looks like crap in color though. Go black and white and you'll get smooth usable results at 6400 and noisy results at 25600 on a Nikon D5100 / D7000 / Sony NEX-5N (all have the same sensor AFAIK. I have a D5100). I'd kinda like to try a NEX-5N with a pancake lens for a small setup.&lt;br /&gt;&lt;br /&gt;To recap: set ISO to 6400 or 25600, shoot in black and white, use manual focus (set to near-infinity or 20 meters or something), set shutter speed to 1/1000 or 1/500, aperture to f/16, use a 24mm lens or wider, snap away while walking!&lt;br /&gt;&lt;br /&gt;Here's a &lt;a href="https://plus.google.com/photos/115293744081058969329/albums/5702716122491664273"&gt;gallery of my results&lt;/a&gt; from yesterday. They're not all pure examples of this technique, for some I brought the camera to my eye to do framing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-2161122633791961047?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/2161122633791961047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=2161122633791961047' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2161122633791961047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2161122633791961047'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2012/01/using-high-iso-in-broad-daylight.html' title='Shooting high ISO in broad daylight'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-nCTBjE1YABA/TyQeYd__q9I/AAAAAAAAINg/2JHS9Jbdz8I/s72-c/DSC_5134.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5206633365205146781</id><published>2012-01-27T14:24:00.003+02:00</published><updated>2012-01-27T14:24:43.032+02:00</updated><title type='text'>Animating a million letters with WebGL</title><content type='html'>&lt;a href="http://3.bp.blogspot.com/-jUuACuTswSs/TyKVnwZ1xBI/AAAAAAAAIEg/UJczyYilN00/s1600/Screen+shot+2012-01-19+at+11.53.28+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;br /&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-jUuACuTswSs/TyKVnwZ1xBI/AAAAAAAAIEg/UJczyYilN00/s320/Screen+shot+2012-01-19+at+11.53.28+AM.png" width="289" /&gt;&lt;/a&gt;&lt;a href="http://1.bp.blogspot.com/-AeehzN7ue8I/TyKVqQqjkPI/AAAAAAAAIEo/RgUE_llRMkk/s1600/Screen+shot+2012-01-19+at+11.53.55+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-AeehzN7ue8I/TyKVqQqjkPI/AAAAAAAAIEo/RgUE_llRMkk/s320/Screen+shot+2012-01-19+at+11.53.55+AM.png" width="275" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;Here's an &lt;a href="http://fhtr.org/webglreader"&gt;WebGL animated book demo&lt;/a&gt;! It's got just 150000 letters, but it does scale up to two million.&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b id="internal-source-marker_0.17733236611820757"&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;Writing efficient WebGL is a bit funny. The basic idea is to collect as many operations as possible into a single draw call, as changing the WebGL state machine state and doing WebGL calls is relatively expensive. If you want to draw more than a couple thousand objects at once, you need to adopt a quite different strategy for drawing.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;The usual way of drawing with WebGL is to set up your uniforms, buffers and shaders for each object, followed by a call to draw the object. Unless your object is very complex, the time taken in this way of drawing is dominated by the state setup. To draw in a fast way, you can either do some buffer editing in JavaScript, followed by re-uploading the buffer and the draw call. If you need to go even faster, you can push more computation to the shaders.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;My goal in this article is to draw a million animated letters on the screen at a smooth framerate. This task should be quite possible with modern GPUs. Each letter consists of two textured triangles, so we're only talking about two million triangles per frame. &lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;Ok, let's start. First I'm going to create a texture with the letter bitmaps on it. I'm using the 2D canvas for this. The resulting texture has all the letters I want to draw. Then I'm going to create a buffer with texture coordinates to the letter sprite sheet. While this is an easy and straightforward method of setting up the letters, it’s a bit wasteful as it uses two floats per vertex for the texcoords. A shorter way would be to pack the letter index and corner index into one number and convert that back to texture coordinates in the vertex shader.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;I also upload a two-million triangle array to the GPU. These vertices are used by the vertex shader to put the letters on the screen. The vertices are set to the letter positions in the text so that if you render the triangle array as-is, you get a basic layout rendering of the text.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;With a simple vertex shader, I get a flat view of the text. Nothing fancy. Runs well, but if I want to animate it, I need to do the animation in Javascript. And JavaScript is kinda slow for animating the six million vertices involved, especially if you want to do it on every frame. Maybe there is there a faster way.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;Why yes, we can do procedural animation. What that means is that we do all our position and rotation math in the vertex shader. Now I don't need to run any JavaScript to update the positions of the vertices. The vertex shader runs very fast and I get a smooth framerate even with a million triangles being individually animated every frame. To address the individual triangles, I round down the vertex coordinates so that all four points of a letter quad map to a single unique coordinate. Now I can use this coordinate to set the animation parameters for the letter in question.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;The only problem now is that JavaScript doesn’t know about the particle positions. If you really need to know where your particles are, you could duplicate the vertex shader logic in JavaScript and update them in, say, a web worker every time you need the positions. That way your rendering thread doesn’t have to wait for the math and you can continue animating at a smooth frame rate.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Arial; font-size: 15px; font-weight: normal; vertical-align: baseline; white-space: pre-wrap;"&gt;For more controllable animation, we could use render-to-texture functionality to tween between the JavaScript-provided positions and the current positions. First we render the current positions to the texture, then tween from the JS array towards these positions, updating the texture on each frame. The nice thing about this is that we can update a small fraction of the JS positions per frame and still continue animating all the letters every frame. The vertex shader is tweening the positions. &lt;span id="goog_1859815404"&gt;&lt;/span&gt;&lt;span id="goog_1859815405"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5206633365205146781?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5206633365205146781/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5206633365205146781' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5206633365205146781'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5206633365205146781'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2012/01/animating-million-letters-with-webgl.html' title='Animating a million letters with WebGL'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-jUuACuTswSs/TyKVnwZ1xBI/AAAAAAAAIEg/UJczyYilN00/s72-c/Screen+shot+2012-01-19+at+11.53.28+AM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7263708654162622858</id><published>2012-01-27T14:14:00.002+02:00</published><updated>2012-01-27T14:14:20.935+02:00</updated><title type='text'>Using MediaStream API</title><content type='html'>&lt;span style="background-color: white; font-family: arial, sans-serif; font-size: 13px; line-height: 18px;"&gt;Using the MediaStream API to access webcam from JavaScript:&lt;/span&gt;&lt;br style="background-color: white; font-family: arial, sans-serif; font-size: 13px; line-height: 18px;" /&gt;&lt;br style="background-color: white; font-family: arial, sans-serif; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;navigator.webkitGetUserMedia("video,audio",&amp;nbsp;&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; function(stream) {&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; var url = webkitURL.createObjectURL(stream);&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; videoTag.src = url;&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; videoTag.onerror = function() {&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; stream.stop();&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; alert('camera error');&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; };&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; },&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; function(error) {&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; &amp;nbsp; alert(error.code);&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;&amp;nbsp; }&lt;/span&gt;&lt;br style="background-color: white; font-size: 13px; line-height: 18px;" /&gt;&lt;span style="background-color: white; font-size: 13px; line-height: 18px;"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7263708654162622858?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7263708654162622858/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7263708654162622858' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7263708654162622858'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7263708654162622858'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2012/01/using-mediastream-api.html' title='Using MediaStream API'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7957804100859580256</id><published>2012-01-27T04:06:00.002+02:00</published><updated>2012-01-29T06:47:28.480+02:00</updated><title type='text'>Very basic math</title><content type='html'>I was playing around with the idea of presenting fractions in the same way as negative numbers. Instead of &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;1/x&lt;/span&gt;, you'd write &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/x&lt;/span&gt;. Just like instead of &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;0-x&lt;/span&gt;, you write &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;-x&lt;/span&gt;. And since multiplication with single-letter symbols is often annotated with putting the symbols next to each other, marking the inverse with &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/x&lt;/span&gt; looks quite natural:&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt; A x /B = A/B, 9 x /7 = 9/7&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;It also makes you think of the inverse in less magical terms. Consider the addition rule for fractions:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;A &amp;nbsp; C &amp;nbsp; AD &amp;nbsp; BC &amp;nbsp; AD + BC&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;- + - = -- + -- = -------&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;B &amp;nbsp; D &amp;nbsp; BD &amp;nbsp; BD &amp;nbsp; &amp;nbsp; BD&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There's some crazy magic happening right there. The literal meaning is&amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;(A x D x 1/B x 1/D) + (C x B x 1/D x 1/B)&lt;/span&gt;, but you wouldn't know from looking at that formula. And it gets even more confusing when you start multiplying and dividing with fractions. Think about the following for a moment:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;A &amp;nbsp; C &amp;nbsp; AD&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;- / - = --&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;B &amp;nbsp; D &amp;nbsp; BC&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;Right?&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;In linear notation with &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/B&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; and &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/D&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; and suchlike, this all actually sort of makes sense in a non-magical way. Here's the first of the above two examples (with intermediate phases written out):&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;(A x /B) + (C x /D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= [1 x (A x /B)] + [1 x (C x /D)]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= [(D x /D) x (A x /B)] + [(B x /B) x (C x /D)]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= [(A x D) x (/B x /D)] + [(B x C) x (/B x /D)]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (/B x /D) x [(A x D) + (B x C)]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;[here's where you go: "oh right, &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/7 x /4 = /28&lt;/span&gt;", analogous to &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;7 x 4 = 28&lt;/span&gt;]&lt;br /&gt;&lt;br /&gt;And the second one:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;A x /B x /(C x /D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= A x /B x /C x D&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (A x D) x (/B x /C)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;Note the similarity with addition:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;A + -B + -(C + -D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= A + -B + -C + D&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (A + D) + (-B + -C)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;Now, you might notice that there is a bit of magic there. How does &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(C x /D)&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; magically turn into &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;(/C x D)&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;? Or &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;-(C + -D)&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; to &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;(-C + D)&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; for that matter. Let's find out! Here's how it works:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(C x /D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= 1 x /(C x /D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= [(/C x D) x /(/C x D)] x /(C x /D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (/C x D) x /(/C x C x D x /D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (/C x D) x /(1 x 1)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (/C x D) x /1 &lt;/span&gt;&lt;span style="font-family: inherit;"&gt;-- Remember the axioms&lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt; 1 x N = N &lt;/span&gt;&lt;span style="font-family: inherit;"&gt;and&lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt; N x /N = 1. &lt;/span&gt;&lt;span style="font-family: inherit;"&gt;Since&lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt; 1 x /1 = 1 &lt;/span&gt;&lt;span style="font-family: inherit;"&gt;we get&lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt; /1 = 1&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;= (/C x D) x 1 = (/C x D)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;For the &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;-(C + -D)&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; case, replace &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; with &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;-,&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;x&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; with &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;+&lt;/span&gt;&lt;span style="font-family: inherit;"&gt;&amp;nbsp;and 1 with 0.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;And there you have it, my small thought experiment. And derivations for some basic arithmetic rules. I kinda like how breaking the magic bits down into the &lt;a href="http://mathworld.wolfram.com/FieldAxioms.html"&gt;basic field axioms&lt;/a&gt; makes things clearer.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;[edit]&lt;br /&gt;&lt;br /&gt;Why is &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/A x /B = /(A x B)&lt;/span&gt;?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(A x B) x (A x B) = 1&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;1 x (/A x /B) = (/A x /B)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(A x B) x (A x B) x (/A x /B) = (/A x /B)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(A x B) x (A x /A) x (B x /B) = (/A x /B)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(A x B) x 1 x 1 = (/A x /B)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;/(A x B) = (/A x /B)&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7957804100859580256?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7957804100859580256/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7957804100859580256' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7957804100859580256'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7957804100859580256'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2012/01/very-basic-math.html' title='Very basic math'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3075229846365971165</id><published>2012-01-21T18:55:00.000+02:00</published><updated>2012-01-22T15:09:01.025+02:00</updated><title type='text'>Fast code</title><content type='html'>I was thinking of the characteristics of high-performance language runtimes (read: execution times close to optimal for hardware) and came up with this list:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Flat data structures (err, like an array of structs where struct N is in memory right before struct N+1)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;streaming memory reads are prefetcher-friendly,&amp;nbsp;spend less time chasing pointers&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Tightly-packed data&lt;/li&gt;&lt;ul&gt;&lt;li&gt;memory fetches happen in cache line -sized chunks, tightly-packed data gives you more payload per memory fetch&lt;/li&gt;&lt;li&gt;fit more payload into cache, faster subsequent memory accesses&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Reusable memory&lt;/li&gt;&lt;ul&gt;&lt;li&gt;keep more of the working set of data in cache&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Unboxed values&lt;/li&gt;&lt;ul&gt;&lt;li&gt;spend less time chasing pointers&lt;/li&gt;&lt;li&gt;generate tight code for data manipulation because data type known (float/double/int/short/byte/vector)&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Vector instructions&lt;/li&gt;&lt;ul&gt;&lt;li&gt;more bytes manipulated per instruction, data moves faster through an execution unit&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Parallel execution&lt;/li&gt;&lt;ul&gt;&lt;li&gt;more bytes manipulated per clock cycle, data moves faster through the processor&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Keep data in registers when possible&lt;/li&gt;&lt;ul&gt;&lt;li&gt;less time spent waiting for caches&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Keep data in cache when possible&lt;/li&gt;&lt;ul&gt;&lt;li&gt;less time spent waiting for memory&lt;/li&gt;&lt;li&gt;instead of going over the full data set several times end-to-end, split it into cache-sized chunks and process each one fully before moving onto the next one&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Minimize amount of unnecessary data movement between processing units&lt;/li&gt;&lt;ul&gt;&lt;li&gt;keep data close to processor until you're done with it, even more important with GPUs&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Flat code layout&lt;/li&gt;&lt;ul&gt;&lt;li&gt;low amount of jumps per byte processed&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Tight code&lt;/li&gt;&lt;ul&gt;&lt;li&gt;keep more of the program in cache&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Interleaved I/O&lt;/li&gt;&lt;ul&gt;&lt;li&gt;work on already loaded data while loading in new data&lt;/li&gt;&lt;li&gt;minimum amount of time spent waiting for I/O&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;You might notice that the major theme is optimizing memory use. I started thinking of program execution as a way to read in the input data set and write out the output data set. The sizes of the input and output data give you a nice optimum execution time by dividing the data set size by memory bandwidth (or I/O bandwidth if you're working on big things). The flow of the program then becomes pushing this river of data through the CPU.&lt;br /&gt;&lt;br /&gt;Suck in the data in cache line -sized chunks, process entire cache line before moving to the next, preload next cache line while processing the current one. Use vector instructions to manipulate several bytes of data at the same time, use parallelism to manipulate several streams of data at the same time. Make your processing kernel fit into L1 instruction cache&lt;br /&gt;&lt;br /&gt;Gnnnn ok, back to writing JavaScript.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3075229846365971165?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3075229846365971165/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3075229846365971165' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3075229846365971165'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3075229846365971165'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2012/01/fast-code.html' title='Fast code'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-350675140805051301</id><published>2011-11-08T18:16:00.001+02:00</published><updated>2011-11-08T18:21:43.206+02:00</updated><title type='text'>Favicon notify</title><content type='html'>Hack of the day from a few weeks back: show small notification bubble in the favicon. Check out the code at &lt;a href="https://github.com/kig/faviconNotify"&gt;https://github.com/kig/faviconNotify&lt;/a&gt; and go to &lt;a href="http://fhtr.org/faviconNotify"&gt;http://fhtr.org/faviconNotify&lt;/a&gt; for a demo.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Usage&lt;/h3&gt;&lt;pre&gt;&lt;br /&gt;  FaviconNotify.set(number);&lt;br /&gt;  FaviconNotify.clear();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Longer example of use in HTML&lt;/h3&gt;&lt;pre&gt;&lt;br /&gt;  &amp;lt;html&gt;&lt;br /&gt;    &amp;lt;head&gt;&lt;br /&gt;      &amp;lt;link rel="icon" href="favicon.ico"&gt;&lt;br /&gt;      &amp;lt;script src="faviconNotify.js"&gt;&amp;lt;/script&gt;&lt;br /&gt;      &amp;lt;script&gt;&lt;br /&gt;        window.onblur = function() {&lt;br /&gt;          FaviconNotify.set(1);&lt;br /&gt;        };&lt;br /&gt;        window.onfocus = function() {&lt;br /&gt;          FaviconNotify.clear();&lt;br /&gt;        };&lt;br /&gt;      &amp;lt;/script&gt;&lt;br /&gt;    &amp;lt;/head&gt;&lt;br /&gt;  &amp;lt;/html&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Riffing on the work of +&lt;a href="https://plus.google.com/106413090159067280619"&gt;Michael Mahemoff&lt;/a&gt; &lt;a href="http://softwareas.com/dynamic-favicons"&gt;http://softwareas.com/dynamic-favicons&lt;/a&gt;&lt;br /&gt;And &lt;a href="http://userscripts.org/scripts/show/24430"&gt;http://userscripts.org/scripts/show/24430&lt;/a&gt;&lt;br /&gt;And +&lt;a href="https://plus.google.com/116487224183206677154"&gt;Mathieu Henri&lt;/a&gt; &lt;a href="http://www.p01.org/releases/DEFENDER_of_the_favicon/"&gt;http://www.p01.org/releases/DEFENDER_of_the_favicon/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;See also &lt;a href="http://faviconist.com/favicon-library"&gt;http://faviconist.com/favicon-library&lt;/a&gt; (updated dynamic favicon library to incorporate badge-setting).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-350675140805051301?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/350675140805051301/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=350675140805051301' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/350675140805051301'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/350675140805051301'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/11/favicon-notify.html' title='Favicon notify'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7194760463792307672</id><published>2011-11-08T17:54:00.002+02:00</published><updated>2011-11-08T18:08:31.223+02:00</updated><title type='text'>Updated Three.js deck</title><content type='html'>I updated the &lt;a href="http://fhtr.org/BasicsOfThreeJS"&gt;"Basics of Three.js"&lt;/a&gt;-presentation to work with the latest version of the library. My Google Developer Day &lt;a href="http://gdd11-webgl.appspot.com"&gt;"Introduction to WebGL"&lt;/a&gt;-presentation is also online, and updated to match the latest changes to Three.js.&lt;br /&gt;&lt;br /&gt;In more detail, the changes were:&lt;ul&gt;&lt;li&gt;$FOO.addChild and $FOO.addLight were merged into a single method, $FOO.add&lt;br /&gt;&lt;li&gt;Camera was deprecated and split into a PerspectiveCamera and OrthographicCamera&lt;br /&gt;&lt;li&gt;Camera no longer has a target that it tracks, you need to use camera.lookAt(vec3) on every frame to accomplish that.&lt;br /&gt;&lt;li&gt;MeshShaderMaterial was deprecated and renamed to ShaderMaterial&lt;br /&gt;&lt;li&gt;material.ambient now needs an AmbientLight in the scene to work (AmbientLight color is multiplied by material.ambient in the shader)&lt;br /&gt;&lt;li&gt;ColladaLoader was renamed to THREE.ColladaLoader&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7194760463792307672?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7194760463792307672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7194760463792307672' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7194760463792307672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7194760463792307672'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/11/updated-threejs-deck.html' title='Updated Three.js deck'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1322587116754917356</id><published>2011-09-27T17:54:00.002+03:00</published><updated>2011-09-27T17:55:37.374+03:00</updated><title type='text'>Basics of Three.js Presentation</title><content type='html'>&lt;a href="http://1.bp.blogspot.com/-DjO2UGE28pM/ToHj4OMxJDI/AAAAAAAAC68/c8UOrBT-qDA/s1600/Screen%2Bshot%2B2011-09-25%2Bat%2B10.15.32%2BPM.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 319px;" src="http://1.bp.blogspot.com/-DjO2UGE28pM/ToHj4OMxJDI/AAAAAAAAC68/c8UOrBT-qDA/s400/Screen%2Bshot%2B2011-09-25%2Bat%2B10.15.32%2BPM.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5657053161977881650" /&gt;&lt;/a&gt;&lt;br /&gt;The slides for my Basics of Three.js talk are now up at &lt;a href="http://fhtr.org/BasicsOfThreeJS"&gt;fhtr.org/BasicsOfThreeJS&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;They take you through getting started with Three.js, building small apps and using custom shaders. There's a lot of live examples to play with, including the world's worst 3D modeler (pictured). You can fork the deck at &lt;a href="https://github.com/kig/BasicsOfThreeJS"&gt;github.com/kig/BasicsOfThreeJS&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you want to show off your awesome Three.js skills, add a new section to the deck and send me a pull request. Remember to add your name to the credits if you do!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1322587116754917356?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1322587116754917356/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1322587116754917356' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1322587116754917356'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1322587116754917356'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/09/basics-of-threejs-presentation.html' title='Basics of Three.js Presentation'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-DjO2UGE28pM/ToHj4OMxJDI/AAAAAAAAC68/c8UOrBT-qDA/s72-c/Screen%2Bshot%2B2011-09-25%2Bat%2B10.15.32%2BPM.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3975782518363277915</id><published>2011-08-29T02:20:00.002+03:00</published><updated>2011-08-29T02:22:41.848+03:00</updated><title type='text'>Canvas image filter library</title><content type='html'>Here's a small image filter library for the canvas element: &lt;a href="https://github.com/kig/canvasfilters"&gt;github.com/kig/canvasfilters&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's based on the HTML5Rocks &lt;a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/"&gt;Canvas Image Filters&lt;/a&gt; article.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3975782518363277915?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3975782518363277915/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3975782518363277915' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3975782518363277915'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3975782518363277915'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/08/canvas-image-filter-library.html' title='Canvas image filter library'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3370236061154905763</id><published>2011-08-27T15:23:00.005+03:00</published><updated>2011-08-28T01:42:46.254+03:00</updated><title type='text'>WebGL Filesystem Visualizer</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-hb-qjZn-ngc/TljiFzUiUrI/AAAAAAAACfs/pZ9N3jEcZPk/s1600/wfsv.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 218px;" src="http://4.bp.blogspot.com/-hb-qjZn-ngc/TljiFzUiUrI/AAAAAAAACfs/pZ9N3jEcZPk/s400/wfsv.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5645510722212483762" /&gt;&lt;/a&gt;&lt;br /&gt;I put the &lt;a href="http://html5wow.googlecode.com"&gt;HTML5WOW&lt;/a&gt; 3D filesystem visualizer demo online at &lt;a href="http://fhtr.org/wfsv"&gt;fhtr.org/wfsv&lt;/a&gt;. Works only on Chrome as it uses the file input &lt;i&gt;webkitdirectory&lt;/i&gt; attribute to recursively list directory contents.&lt;br /&gt;&lt;br /&gt;Use the file input button in the top-left corner to select a directory to visualize. The visualizer tries to generate a model of the full directory tree, so it only works on small dir trees (less than 500 files or so). Use the console to navigate by clicking on file names. You can also use some shell commands like ls, cp, rm and mv. The filesystem contents aren't modified though, only the visualization.&lt;br /&gt;&lt;br /&gt;Here's a video of the visualizer in action from the Google I/O 2011 "HTML5: The Wow and the How"-presentation:&lt;br /&gt;&lt;br /&gt;&lt;center&gt;&lt;iframe width="640" height="390" src="http://www.youtube.com/embed/WlwY6_W4VG8#t=2092s" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;/center&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3370236061154905763?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3370236061154905763/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3370236061154905763' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3370236061154905763'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3370236061154905763'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/08/webgl-filesystem-visualizer.html' title='WebGL Filesystem Visualizer'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-hb-qjZn-ngc/TljiFzUiUrI/AAAAAAAACfs/pZ9N3jEcZPk/s72-c/wfsv.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8108680636381192036</id><published>2011-08-19T15:54:00.006+03:00</published><updated>2011-08-26T13:36:17.728+03:00</updated><title type='text'>Ken Burns effect using CSS</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://fhtr.org/kenburns/"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 272px;" src="http://2.bp.blogspot.com/-97Ias3DvZL0/Tld2--BdmYI/AAAAAAAACfg/1l32N8Yrg7M/s400/kenburns.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5645111482105305474" /&gt;&lt;/a&gt;&lt;br /&gt;The &lt;a href="http://en.wikipedia.org/wiki/Ken_Burns_effect"&gt;Ken Burns effect&lt;/a&gt; is a special effect used in documentaries when you only have a static photograph of an interesting item. To add some movement and life to the photograph, you zoom into the photo and pan towards a point of interest. It's named the Ken Burns effect because it was used a lot by a documentary film maker named Ken Burns.&lt;br /&gt;&lt;br /&gt;Anyhow.&lt;br /&gt;&lt;br /&gt;You can achieve the Ken Burns effect using CSS animations. It's not even particularly difficult. Just create a div with overflow:hidden to hold the image, then change the image's CSS transform property. Or if you want to be totally retro and backwards-compatible, you could also achieve the effect by changing the image's top, left, width and height using a JS setInterval.&lt;br /&gt;&lt;br /&gt;So, CSS:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;.kenburns {&lt;br /&gt;  overflow: hidden;&lt;br /&gt;  display: inline-block;&lt;br /&gt;}&lt;br /&gt;.kenburns img {&lt;br /&gt;  transition-duration: 5s;&lt;br /&gt;  transform: scale(1.0);&lt;br /&gt;  transform-origin: 50% 50%;&lt;br /&gt;}&lt;br /&gt;.kenburns img:hover {&lt;br /&gt;  transform: scale(1.2);&lt;br /&gt;  transform-origin: 50% 0%; /* pan towards top of image */&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the corresponding HTML:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;div class="kenburns" style="width:640px; height:480px;"&gt;&lt;br /&gt;  &amp;lt;img src="image.jpg" width="640" height="480"&gt;&lt;br /&gt;&amp;lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you hover over the image, it will slowly zoom in and pan towards its top edge.&lt;br /&gt;&lt;br /&gt;You can see the effect in action &lt;a href="http://fhtr.org/kenburns/howto/Demo1.html"&gt;here&lt;/a&gt;. And a more complex version with a JS-driven lightbox &lt;a href="http://fhtr.org/kenburns/"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The (quick, hacky) &lt;a href="http://github.com/kig/kenburns"&gt;code is on GitHub&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8108680636381192036?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8108680636381192036/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8108680636381192036' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8108680636381192036'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8108680636381192036'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/08/ken-burns-effect-using-css.html' title='Ken Burns effect using CSS'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-97Ias3DvZL0/Tld2--BdmYI/AAAAAAAACfg/1l32N8Yrg7M/s72-c/kenburns.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7809228199969975550</id><published>2011-08-11T22:27:00.003+03:00</published><updated>2011-08-11T23:25:53.513+03:00</updated><title type='text'>Spinner library</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-83ddwmsB4Uw/TkQ4LXpkR1I/AAAAAAAACcI/xhY2ImlpcuU/s1600/spinner.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 266px;" src="http://4.bp.blogspot.com/-83ddwmsB4Uw/TkQ4LXpkR1I/AAAAAAAACcI/xhY2ImlpcuU/s400/spinner.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5639694401352779602" /&gt;&lt;/a&gt;&lt;br /&gt;Ok, uploaded the &lt;a href="http://github.com/kig/spinner"&gt;spinner library to GitHub&lt;/a&gt;. There's a &lt;a href="http://fhtr.org/spinner"&gt;demo page&lt;/a&gt; too. It's a wee bit buggy though. I think the transition events are not firing when the page is hidden. &lt;br /&gt;&lt;br /&gt;Even getting it to the current phase was a pain, the interaction between CSS transitions and animations, the DOM, JavaScript and events is flaky at the edges. Once I get an animation or transition going, it works well. But if I want to change some CSS properties in JS with the intent to trigger an animation or transition, it's pretty much guesswork whether it will actually work without glitches.&lt;br /&gt;&lt;br /&gt;Hmm... As a workaround, I think I could just create a new element on every show/hide-cycle. That way the animation can't possibly fire one 100% frame before starting from 0%, right?? (This happened on Firefox... or maybe it was an unanimated frame or something. I ended up switching from animations to transitions because of that.)&lt;br /&gt;&lt;br /&gt;On Chrome Canary, border-radius and overflow:hidden don't behave like you'd expect. Instead of clipping the contents to the rounded box, it clips on the rectangular box. I don't know why. And sometimes the rendering flickers when using transforms that toggle HW compositing for the page.&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7809228199969975550?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7809228199969975550/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7809228199969975550' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7809228199969975550'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7809228199969975550'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/08/spinner-library.html' title='Spinner library'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-83ddwmsB4Uw/TkQ4LXpkR1I/AAAAAAAACcI/xhY2ImlpcuU/s72-c/spinner.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5120106742640613253</id><published>2011-07-19T00:24:00.003+03:00</published><updated>2011-07-19T00:31:26.397+03:00</updated><title type='text'>Story of Tomte's New Hat</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://plus.google.com/photos/115293744081058969329/albums/5628980450573253521"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 369px;" src="http://1.bp.blogspot.com/-_aUZtAQuk2g/TiSl0dt5PCI/AAAAAAAACRU/_NIDFmy1PRE/s400/watis23.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5630807754868866082" /&gt;&lt;/a&gt;&lt;br /&gt;To break the monotonic programmingification, here's a story about &lt;a href="https://plus.google.com/photos/115293744081058969329/albums/5628980450573253521"&gt;Tomte's new hat&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5120106742640613253?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5120106742640613253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5120106742640613253' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5120106742640613253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5120106742640613253'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/07/story-of-tomtes-new-hat.html' title='Story of Tomte&apos;s New Hat'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-_aUZtAQuk2g/TiSl0dt5PCI/AAAAAAAACRU/_NIDFmy1PRE/s72-c/watis23.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3133082727136396530</id><published>2011-07-06T12:34:00.002+03:00</published><updated>2011-07-06T13:38:05.548+03:00</updated><title type='text'>Microbenchmark findings</title><content type='html'>&lt;a href="http://jsperf.com/canvas-pixel-loop"&gt;Looping through canvas ImageData pixels&lt;/a&gt;: 1D traversal &lt;code&gt;for (var i=0; i&amp;lt;data.length; i+=4) ...&lt;/code&gt; is slightly faster than 2D traversal &lt;code&gt;for (var y=0; y&amp;lt;height; y++) { for (var x=0; x&amp;lt;width; x++) ... }&lt;/code&gt;. Caching the ImageData width, height and data properties to local variables is slightly faster than not (except in the 2D case, where it's a good bit faster). Stay away from crazy counting hacks, they're more likely to slow you down than speed you up (probably because they make the compiler's job harder and it can't optimize the code properly).&lt;br /&gt;&lt;br /&gt;Based on the above, my preferred pixel loop format is:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var width = id.width;&lt;br /&gt;var height = id.height;&lt;br /&gt;var data = id.data;&lt;br /&gt;for (var y=0; y&amp;lt;height; y++) {&lt;br /&gt;  for (var x=0; x&amp;lt;width; x++) {&lt;br /&gt;    var off = (y*width + x) * 4;&lt;br /&gt;    var r = data[off];&lt;br /&gt;    var g = data[off+1];&lt;br /&gt;    var b = data[off+2];&lt;br /&gt;    var a = data[off+3];&lt;br /&gt;    // ...&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And if you just need to map over the pixels and don't care about the coords, the simple 1D loop:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var data = id.data; // or just use id.data directly, no big perf impact&lt;br /&gt;for (var i=0; i&amp;lt;data.length; i+=4) {&lt;br /&gt;    var r = data[i];&lt;br /&gt;    var g = data[i+1];&lt;br /&gt;    var b = data[i+2];&lt;br /&gt;    var a = data[i+3];&lt;br /&gt;    // ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-blitting"&gt;Sparse blitting&lt;/a&gt; vs. &lt;a href="http://jsperf.com/canvas-blitting-full"&gt;dense blitting&lt;/a&gt;: If all you need to do is change the values of a small number of pixels, use fillRect. If you need to fill the entire canvas or a significant portion (say, 256x256) of it with custom data, use putImageData. There's no major performance difference between clearing an ImageData buffer before use vs. allocating a new one, so I'd go with clearing the buffer just to avoid extra GC work.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-filltext-stroketext"&gt;Text drawing&lt;/a&gt;: fillText is significantly faster than strokeText. Whether the text is aligned on an integer pixel only seems to matter on Opera's fillText.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-path/3"&gt;Path drawing&lt;/a&gt;: Nothing very conclusive, path point count doesn't seem to have a major impact on path point throughput.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-spritesheets"&gt;Spritesheets&lt;/a&gt;: For best performance, cache your spritesheet frames to separate canvases.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-drawimage"&gt;Image drawing&lt;/a&gt;: Align your images to integer pixels, don't transform them, use canvas elements instead of IMGs. Transformations perform very badly on non-accelerated canvases. Just offsetting an image by fractional pixels causes the browser to use the slow path. On accelerated canvases, transformations don't really matter all that much. You still get the best perf by doing aligned non-transformed draws though.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-clear-speed"&gt;Clearing the canvas&lt;/a&gt;: Just use clearRect.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/texture-sources"&gt;WebGL texture sources&lt;/a&gt;: Use premultiplied textures if possible. Use ImageData if possible, just don't specify it as premultiplied (same goes for typed arrays). Canvases are faster than images on Chrome, images are faster than canvases on Firefox. Typed arrays are about as fast as canvases.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3133082727136396530?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3133082727136396530/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3133082727136396530' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3133082727136396530'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3133082727136396530'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/07/microbenchmark-findings.html' title='Microbenchmark findings'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5972066708515711888</id><published>2011-06-08T16:53:00.002+03:00</published><updated>2011-06-08T16:56:49.570+03:00</updated><title type='text'>New articles</title><content type='html'>I have two new articles up at &lt;a href="http://html5rocks.com"&gt;HTML5 Rocks&lt;/a&gt;. The first is about &lt;a href="http://www.html5rocks.com/tutorials/canvas/imagefilters/"&gt;making image filters using the canvas element&lt;/a&gt; and the second one is about &lt;a href="http://www.html5rocks.com/tutorials/webgl/demoloop/"&gt;building a kiosk-style app for showcasing Chrome Experiments&lt;/a&gt; at this year's Google I/O.&lt;br /&gt;&lt;br /&gt;Go check them out and let me know how you like them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5972066708515711888?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5972066708515711888/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5972066708515711888' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5972066708515711888'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5972066708515711888'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/06/new-articles.html' title='New articles'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1076357180678372608</id><published>2011-06-02T16:09:00.003+03:00</published><updated>2011-06-02T17:34:47.622+03:00</updated><title type='text'>Canvas &amp; WebGL microbenchmarks</title><content type='html'>I've been writing some microbenchmarks for canvas and WebGL over the past few weeks. They're at &lt;a href="http://jsperf.com"&gt;jsPerf&lt;/a&gt;, but jsPerf being what it is, I'm going to collect all the benchmark links here. So, without further ado:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Canvas 2D tests&lt;/h3&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-clear-speed"&gt;Clearing the canvas&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-drawimage"&gt;Drawing images&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-spritesheets"&gt;Spritesheets vs. individual sprites&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-path"&gt;Path creation&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-filltext-stroketext"&gt;Filling and stroking text&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-fontsizes"&gt;Drawing text at different sizes&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-blitting"&gt;Sparse blitting&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-blitting-full"&gt;Dense blitting&lt;/a&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/canvas-pixel-loop"&gt;Looping through ImageData pixels&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;There's also &lt;a href="https://github.com/kig/gfxmicrobench"&gt;this test&lt;/a&gt; on the speed of the different canvas composite operations over at GitHub, I probably should port it over to jsPerf as well.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;WebGL tests&lt;/h3&gt;&lt;br /&gt;&lt;a href="http://jsperf.com/texture-sources"&gt;Texture sources and pixelStorei settings&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1076357180678372608?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1076357180678372608/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1076357180678372608' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1076357180678372608'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1076357180678372608'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/06/canvas-webgl-microbenchmarks.html' title='Canvas &amp; WebGL microbenchmarks'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7539595309869826569</id><published>2011-04-24T00:10:00.010+03:00</published><updated>2011-04-26T23:22:17.767+03:00</updated><title type='text'>Runfield &amp; Remixed Reality</title><content type='html'>Oh right, I did these demos for Mozilla a couple months back:&lt;br /&gt;&lt;br /&gt;&lt;a href="https://demos.mozilla.org/en-US/#runfield"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 201px;" src="http://4.bp.blogspot.com/-Ab2n-g6YCDk/TbNCcoNssnI/AAAAAAAACCA/4xXNUXTb4yI/s400/screenshot_rf.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5598891821350302322" /&gt;&lt;br /&gt;Runfield&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Runfield is a Canabalt clone with painted graphics (I made &lt;a href="https://mozillademos.org/demos/runfield/template/instructions.html"&gt;a guide&lt;/a&gt; on how to do your own graphics for it, but it's a bit "First you sketch a good-looking picture and then you paint it! Done!"). The graphics were painted in MyPaint and GIMP. The renderer is done with Canvas 2D and uses drawImage to draw thin vertical slices from the background images to make up the undulating ground.&lt;br /&gt;&lt;br /&gt;The main things I wanted to communicate with Runfield were speed and polish. Showing that you can make a fast 2D game with JS and have it look good. Accordingly, most of the dev time was spent making the graphics and optimizing the engine (:&lt;br /&gt;&lt;br /&gt;Optimization tips: draw images aligned to the pixel grid, eliminate overdraw (if you know that a part of an image is not going to show, don't draw that part), use the first couple seconds to detect the framerate and drop down to a lighter version if the framerate is low.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://demos.mozilla.org/en-US/#remixingreality"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 201px;" src="http://1.bp.blogspot.com/-_Wjt5v6zzIo/TbNCL72c6lI/AAAAAAAACB4/LKAiBlaqN3k/s400/screenshot.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5598891534563732050" /&gt;&lt;br /&gt;Remixing Reality&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Remixing Reality is another demo to showcase what you can do with JavaScript today. It's processing video frames in real-time to locate AR markers and uses WebGL to draw 3D models on top of the markers. If you click the play button on the side, music starts playing and there's a 3D music visualizer powered by BeatDetektor2 and the Mozilla audio API, again analyzing the audio in real-time.&lt;br /&gt;&lt;br /&gt;The AR library powering the thing is &lt;a href="http://github.com/kig/JSARToolKit"&gt;JSARToolKit&lt;/a&gt;, a pure-JS port of the Flash FLARToolKit (which uses NyARToolKitAS3, which is a port of the Java NyARToolKit, which is a port of the C ARToolKit. Whew.) Porting it over to JS was pretty quick, since the AS3 syntax is close enough to JS syntax that I could write a good-enough syntax translation script in a couple days. Then I implemented the AS3 class semantics in JS and off we go. &lt;br /&gt;&lt;br /&gt;Well, it wasn't quite that easy. The syntax translator is a hack and I had to go and manually fix things. And implement the pertinent parts of Flash's BitmapData. And write a shim to make it work with Canvas. But hey, 14 kloc port in a week!&lt;br /&gt;&lt;br /&gt;The job didn't end there though. It was slow. The biggest slowdown was that the library was reading a couple pixels at a time from the canvas, and each of those reads called getImageData. So, cache it, problem solved. &lt;br /&gt;&lt;br /&gt;It was still a bit slow, mostly due to FLARToolKit using BitmapData's color bbox queries to do feature detection. I.e. find the smallest rectangle in the bitmap that includes all pixels of a certain color. Each call to BitmapData#getColorBoundsRect needs to go through the pixels in the bitmap and find the first row where the wanted color is found, then the bottom row, then scan the rows in between to find the left-most and right-most columns. This process was not too fast in JS.&lt;br /&gt;&lt;br /&gt;But NyARToolKit, the library which FLARToolKit is based on, was doing the feature detection in an entirely different way. Its algo was running on RLE-compressed images (Run-Length Encoding: pack data as [value][number of repetitions], e.g. aaabbbb becomes a3b4). And since the images in question are thresholded to black and white, RLE works very well. Expected result: smaller images =&gt; less work for JS =&gt; faster.&lt;br /&gt;&lt;br /&gt;So... I made the JS version use the NyARToolKit version. And hey, it was 5x faster! Nice!&lt;br /&gt;&lt;br /&gt;Another thing that helped performance on Firefox 4 was using typed arrays instead of normal JS arrays. Fx4's JIT generates more efficient machine code for typed arrays. On Chrome 10(? IIRC), typed arrays and normal arrays didn't have much of a performance difference, but the code ran fast enough on normal arrays already.&lt;br /&gt;&lt;br /&gt;For the 3D stuff I used my &lt;a href="http://github.com/kig/magi"&gt;Magi&lt;/a&gt; library. With a &lt;a href="https://gist.github.com/856848"&gt;Blender export script&lt;/a&gt; to get the models in. And a slightly tweaked lighting shader to make it fill the unlit areas with some ambient. Fun times.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7539595309869826569?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7539595309869826569/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7539595309869826569' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7539595309869826569'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7539595309869826569'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/04/runfield-remixed-reality.html' title='Runfield &amp; Remixed Reality'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-Ab2n-g6YCDk/TbNCcoNssnI/AAAAAAAACCA/4xXNUXTb4yI/s72-c/screenshot_rf.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5565124734210038202</id><published>2011-04-01T02:19:00.003+03:00</published><updated>2011-04-01T04:05:40.202+03:00</updated><title type='text'>Browser rendering loop</title><content type='html'>The browser rendering loop is how the browser displays the web page to you.&lt;br /&gt;&lt;br /&gt;The main stages of the rendering loop are:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Updating the DOM.&lt;/li&gt;&lt;li&gt;Rendering the individual elements.&lt;/li&gt;&lt;li&gt;Compositing the rendered elements to the browser window.&lt;/li&gt;&lt;li&gt;Displaying the browser window to the user.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;The DOM updates happen in JavaScript or in CSS transitions and animations. They include things like "Hey, I'd like that header to turn red." and "Please draw a thick line on the canvas."&lt;br /&gt;&lt;br /&gt;If you're drawing to a canvas, you might expect the browser to draw as soon as you issue a drawing command. Which is what actually happened in earlier browser versions. But in the latest browsers, it doesn't quite work that way. Nowadays the browser queues up the drawing commands and only starts drawing when it absolutely needs to. Which is usually just before compositing, in the second stage of the rendering loop.&lt;br /&gt;&lt;br /&gt;However, if you want to force the browser to finish drawing before continuing JS execution, you can try doing getImageData on the 2D Canvas and readPixels in WebGL. As they need to return the finished image, they should force the browser to flush its draw queue. This comes in handy if you ever need to figure out the time it took for the browser to execute your drawing commands.&lt;br /&gt;&lt;br /&gt;Once all the individual elements are drawn, the browser composites them together to create the final browser window image. And finally, the browser window image is shown to the user via the OS window manager.&lt;br /&gt;&lt;br /&gt;The frame rate perceived by the user is the frequency at which step 4 is repeated. In other words, how often the updated browser window is shown to the user.&lt;br /&gt;&lt;br /&gt;As most flat panel displays can only update 60 times per second (the TV frequency), the browser tries to display only up to 60 frames per second. Going over 60 when your display can't take advantage of it would only burn more CPU and reduce battery life, so it makes sense to clamp the update frequency to the display's update frequency.&lt;br /&gt;&lt;br /&gt;Optimally, the browser would finish all its drawing before doing a new composite, but current browser implementations have slight problems with that. So it's really rather difficult to figure out the actual framerate visible to the user. If you have a high-speed video camera, you could record the display and see how fast it's updating. &lt;br /&gt;&lt;br /&gt;But if you want to do it all in the browser, you could try something like this. First, hook up to the frame loop with requestAnimationFrame. Second, flush the drawing queue when your frame is done. Third, measure time from flush to flush. Hopefully browsers will move towards requestAnimationFrame only being called after flushing the previous frame. &lt;br /&gt;&lt;br /&gt;Firefox and Chrome actually make this a bit easier for you by providing some built-in framerate instrumentation. Chrome dev channel has an about:flags FPS counter that (sadly) only works for accelerated content. Firefox 4 has the window.mozPaintCount property that keeps track of how many times the browser window has been redrawn.&lt;br /&gt;&lt;br /&gt;References:&lt;br /&gt;&lt;a href="http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome"&gt;GPU Accelerated Compositing in Chrome&lt;/a&gt;&lt;br /&gt;&lt;a href="http://blog.mozilla.com/joe/2010/11/10/fx4b7-hwaccel/"&gt;Hardware Acceleration in the latest Firefox 4 beta&lt;/a&gt;&lt;br /&gt;&lt;a href="http://weblogs.mozillazine.org/roc/archives/2010/11/measuring_fps.html"&gt;ROC: Measuring FPS&lt;/a&gt;&lt;br /&gt;&lt;a href="http://blog.sethladd.com/2011/03/measuring-html5-browser-fps-or-youre.html"&gt;Measuring HTML5 Browser FPS, or, You're Not Measuring What You Think You're Measuring&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Please send me a note if anything above is wrong / misguided / an affront to your values and I'll try and fix it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5565124734210038202?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5565124734210038202/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5565124734210038202' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5565124734210038202'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5565124734210038202'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/04/browser-rendering-loop.html' title='Browser rendering loop'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1074848845285401921</id><published>2011-03-31T21:37:00.003+03:00</published><updated>2011-03-31T22:10:59.821+03:00</updated><title type='text'>Detecting new globals in JavaScript</title><content type='html'>JavaScript has this annoying little feature where if you forget the 'var' keyword when assigning to a variable, it creates a new global. This has a tendency to cause some veeery interesting bugs. So. It would be nice to detect those new globals. And I happen to have just the thing for that.&lt;br /&gt;&lt;br /&gt;Paste this snippet to your webpage (preferably after creating all the globals you intended to create):&lt;br /&gt;&lt;pre&gt;&amp;lt;script type="text/javascript"&gt;&lt;br /&gt;  if (true /* MONITOR_GLOBALS */) {&lt;br /&gt;    (function(){&lt;br /&gt;      var globals = {};&lt;br /&gt;      var startGlobals = [];&lt;br /&gt;      for (var j in window) {&lt;br /&gt;        globals[j] = true;&lt;br /&gt;        startGlobals.push(j);&lt;br /&gt;      }&lt;br /&gt;      if (false /* PRINT_INITIAL_GLOBALS */)&lt;br /&gt;        console.log("Initial globals: "+startGlobals.sort().join(', '));&lt;br /&gt;      setInterval(function() {&lt;br /&gt;        var newGlobals = [];&lt;br /&gt;        for (var j in window) {&lt;br /&gt;          if (!globals[j]) {&lt;br /&gt;            globals[j] = true;&lt;br /&gt;            newGlobals.push(j);&lt;br /&gt;          }&lt;br /&gt;        }&lt;br /&gt;        if (newGlobals.length &gt; 0)&lt;br /&gt;          console.log("NEW GLOBALS: "+newGlobals.sort().join(', '));&lt;br /&gt;      }, 1000);&lt;br /&gt;    })();&lt;br /&gt;  }&lt;br /&gt;&amp;lt;/script&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now whenever a script creates a new global, you get a notification in your JS console. Hopefully sparing you from  some agonizing hours of debugging.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1074848845285401921?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1074848845285401921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1074848845285401921' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1074848845285401921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1074848845285401921'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/03/detecting-new-globals-in-javascript.html' title='Detecting new globals in JavaScript'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3536918051345293444</id><published>2011-02-05T20:22:00.007+02:00</published><updated>2011-02-07T21:50:20.338+02:00</updated><title type='text'>Trying to make sense of this whole cooling thing</title><content type='html'>I kind of find CPU heat sinks interesting. Probably because of the noisy fans messing with my concentration. Why do we need those things anyhow?&lt;br /&gt;&lt;br /&gt;I'm not a physicist so if I'm saying Very Silly Things below, I'd appreciate if you left a comment to set me straight.&lt;br /&gt;&lt;br /&gt;Fans are devices to move ambient-temperature coolant fluid (read: air) into contact with heat sink fins, so as to maximize heat conduction from the heat sink to the environment. Heat conduction = thermal conductivity * area * temperature difference / distance between the different temperatures &lt;sup&gt;[&lt;a href="http://en.wikipedia.org/wiki/Thermal_conductivity"&gt;according to Wikipedia&lt;/a&gt;]&lt;/sup&gt;. If you think of the heat sink surface&amp;ndash;ambient air -system in terms of that model, a fan would be trying to minimize the distance between the hot heat sink and the cool air. In practice, pushing off the boundary layer of hot air and replacing it with cooler air. &lt;br /&gt;&lt;br /&gt;There are also other methods for optimizing the heat conduction equation, here's a small roundup. Endothermic reactions for soaking up heat and transporting it away: heat pipes. Maximize the temperature difference: dry ice, LN2 (plus the boiling is endothermic). Increase thermal conductivity of the coolant fluid: water cooling, mineral oil cooling. Increase conducting area: finned heat sinks, larger heat sinks - and I guess the increased mass acts as a thermal buffer that soaks up spikes in heat production. Decrease distance between the different temperatures: fan pushing in cold air, chimney enhancing natural convection. (Natural convection: hot air expands, making it less dense than cool air, which lets cool air fall below hot air. As long as you have gravity, that is.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Sci-fi, where our Author reveals his lack of understanding in The Art of Physicks&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You could also get cold air close to the heat sink fins by ionizing air and charging the heat sink with reverse polarity to attract the cool air to the heat sink (hopefully neutralizing the air in the process). Maybe that could get cold air more effectively through the boundary layer  than mechanical pushing with a fan? They're doing some &lt;a href="http://www.technologyreview.com/computing/22668/?a=f"&gt;commercial research&lt;/a&gt; into that, and there's also a &lt;a href="http://inventgeek.com/Projects/IonCooler3/page6.aspx"&gt;DIY Ion Cooler&lt;/a&gt;. &lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Some sort of channels on the heat sink surface to require less airflow to replace the boundary layer. Funnel the air to a higher velocity to push through the boundary layer more efficiently. &lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;This is neat: &lt;a href="http://www.thermacore.com/news/active-heat-sink-technology-in-high-power-electronic-devices.aspx"&gt;vibrating piezoelectric heat sink fins&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Heat travels as &lt;a href="http://web.mit.edu/newsoffice/2010/explained-phonons-0706.html"&gt;phonons&lt;/a&gt;, maybe you could somehow create a heat waveform and sap it out through destructive interference with an audio source. Sort of like noise cancelling headphones. Audible noise is in the kilohertz range, heat is in the gigahertz range. Apparently these guys at MIT are &lt;a href="http://www.physorg.com/news188466491.html"&gt;making phononic mirrors&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Make the heat do work, cooling the heat sink down in the process. Stuff a heat sink full of piezoelectric crystals to convert the thermal expansion of the heat sink into electricity, conduct the electricity away to cool the heat sink.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Can heat be focused? Heat up a small region of the heat sink to a high enough temperature for blackbody radiation to really kick in, use mirrors to transport the resulting photons away. (Intensity of blackbody radiation grows as fourth power of absolute temperature, temperature grows roughly linearly with power input (apart from phase changes).&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;A meter-high 0.1x0.1m chimney with 50C internal air and 20C room temperature could generate 0.01 m^3/s airflow, or 22 CFM? Plugging in values to &lt;a href="http://en.wikipedia.org/wiki/Flue_gas_stack#The_flue_gas_flow_rate_induced_by_the_draught"&gt;Q = C*A*sqrt(2*g*H*(Ti/T0-1))&lt;/a&gt;: 0.7 * 0.01m^2 * sqrt(2*9.81m/s^2*1m*(323K/293K-1)) = 0.0099 m^3/s.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Rotate the chimney, get a &lt;a href="http://en.wikipedia.org/wiki/Fire_whirl"&gt;fire tornado&lt;/a&gt;! &lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://ludens.cl/Electron/Thermal.html"&gt;An interesting page on thermal design&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3536918051345293444?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3536918051345293444/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3536918051345293444' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3536918051345293444'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3536918051345293444'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/02/trying-to-make-sense-of-this-whole.html' title='Trying to make sense of this whole cooling thing'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5582391326060351230</id><published>2011-02-05T11:24:00.002+02:00</published><updated>2011-02-05T20:22:19.138+02:00</updated><title type='text'>More half-baked ideas to keep the year rolling</title><content type='html'>Tone down flashing ads by rendering the web page to an accumulation buffer at a low opacity, so that there'll be a motion blur effect that evens out the flashing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5582391326060351230?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5582391326060351230/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5582391326060351230' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5582391326060351230'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5582391326060351230'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/02/more-half-baked-ideas-to-keep-year.html' title='More half-baked ideas to keep the year rolling'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-213749429076077915</id><published>2011-01-27T10:34:00.006+02:00</published><updated>2011-01-27T16:38:26.158+02:00</updated><title type='text'>Intel X25-V SSD</title><content type='html'>Bought an Intel X25-V 40GB SSD to do some random read benchmarking. Don't stick it into a PATA controller driven SATA port, as that limits the bandwidth to 70 MB/s and random access times to 5 ms.&lt;br /&gt;&lt;br /&gt;Sequential read speed on the unused disk was limited by the SATA bus. So I guess it's not actually reading anything for unused blocks, just generating a string of zeroes on the controller and sending it back. Would be pretty amusing to have a special disk driver that keeps a list of the unused blocks in system RAM and generates the data on the CPU for any read accesses to them. You could probably keep it all in L1 and get nice bogus 4k random read benchmark numbers. "1 ns access latency!?? 100 GB/s random read bandwidth??? Whaaaat?" (An allocation bitfield for 80 gigs in 512k blocks is about 20 kB. Increase blocksize or add RLE for bigger volumes.)&lt;br /&gt;&lt;br /&gt;Read speed on actual data was around 200 MB/s. Average random read time for a 4k block was 0.038 ms with 128 threads and 0.31 ms with a single thread. So the controller can do 8 reads in parallel. Which is nice for a cheapo SSD.&lt;br /&gt;&lt;br /&gt;Didn't really test write performance apart than doing a cursory check. And yes, it is low bandwidth. 40 MB/s streaming writes. So it's best used as a random read drive.&lt;br /&gt;&lt;br /&gt;Might make a nice random read array with 6 drives or somesuch. Hypothetically: 48 parallel reads, 160 000 4k reads per second (650 MB/s). And then all you need is software that can take advantage of that.&lt;br /&gt;&lt;br /&gt;Getting flash chip latencies lower would be good.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-213749429076077915?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/213749429076077915/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=213749429076077915' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/213749429076077915'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/213749429076077915'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/01/intel-x25-v-ssd.html' title='Intel X25-V SSD'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3631960263826521286</id><published>2011-01-17T04:00:00.004+02:00</published><updated>2011-01-17T04:32:08.905+02:00</updated><title type='text'>Stupid ideas to kick off 2011</title><content type='html'>They have those spinning disks with a couple gigs of flash as read cache, right. So, how about spending about 30e to put a bunch of fast flash chips and a couple gigs of DRAM on a motherboard to act as SATA read cache. Read the flash into DRAM during boot checks (I guess you have around 5 seconds to do it, so 0.5-1GB/s should suffice.) Ooh, mysteriously computer boots as if from ramdisk.&lt;br /&gt;&lt;br /&gt;Or alternatively, if you like software more than hardware, use the fast flash as the OS disk, suck it to RAM during boot checks, write OS to be able to utilize that. &lt;br /&gt;&lt;br /&gt;Also, maybe they could make a computer that's not actually a computer but a rock. And then you could have a mouse that's not a mouse but a rat and it'd bite your fingers off and give you rabies.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3631960263826521286?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3631960263826521286/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3631960263826521286' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3631960263826521286'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3631960263826521286'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/01/stupid-ideas-to-kick-off-2011.html' title='Stupid ideas to kick off 2011'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1649861214066267392</id><published>2011-01-15T10:38:00.012+02:00</published><updated>2011-01-17T03:15:02.197+02:00</updated><title type='text'>How to take advantage of that memory?</title><content type='html'>&lt;b&gt;[edit]&lt;/b&gt; Here's an rc.d init script I'm using to prefetch the FS to page cache: &lt;a href="https://gist.github.com/781535"&gt;usr-prefetch&lt;/a&gt;. Copy to /etc/init.d and run `update-rc.d usr-prefetch start 99 2 .` to add it to runlevel 2 init scripts. I currently have 4GB of RAM, and the total size of the prefetched stuff is 3.7GB, so it might actually help. Maybe I should go buy an extra 2GB stick. It takes about a minute to do the prefetch run with cold cache, so the average read speed is around 60 MB/s. Which is pretty crappy compared to the 350 MB/s streaming read speed, maybe there's some way to make the prefetch a streaming read.&lt;br /&gt;&lt;br /&gt;Ubuntu's ureadahead reads in the files accessed during boot in disk order. If you add all your OS files to it, it should be possible to stream the whole root filesystem to page cache in about ten seconds. I dunno. &lt;br /&gt;&lt;br /&gt;And now I'm reading through ureadahead's sources to figure out how it sorts the files in disk order. It's using fiemap ioctl to get the physical extents for the file inodes, then sorts the files according to the first physical block used by the file. To read the files to the page cache, it uses readahead. If there was some way to readahead physical blocks instead of files, you could collapse the physical blocks into spans with &gt; x percent occupancy, do a single streaming read per span (and cache only the blocks belonging to the files to cache).&lt;br /&gt;&lt;br /&gt;Another easy way to do fast caching would be to put all system files on a separate partition, then cache the entire partition with a streaming read. Or allocate a ramdisk, dd the root fs onto it at boot, use it through UnionFS with writes redirected to the non-volatile fs. But whether that's worth the bother is another thing.&lt;br /&gt;&lt;b&gt;[/edit]&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In the previous post I put together a hypothetical machine with 16GB RAM and 1.5GB/s random access IO bandwidth. Which is total overkill for applications that were built for machines with 100x less resources. What is one to do? Is this expensive hardware a complete waste of money?&lt;br /&gt;&lt;br /&gt;The first idea I had on taking advantage of that amount of RAM was to preload the entire OS disk to page cache. If the OS+apps are around 10GB in total, it'll take less than 10s to load them up at 1.5GB/s. And you'll still have 6GB unused RAM left, so the page cache isn't going to get pushed out too easily. With 10s boot time, you'd effectively get a 20GB/s file system 20 seconds after pushing the power button.&lt;br /&gt;&lt;br /&gt;Once you start tooling around with actual data (such as video files), that is going to push OS files out of page cache, which may not be nice. But what can you do? Set some sort of caching policy? Only cache libs and exes, use the 1.5GB/s slow path for data files? Dunno.&lt;br /&gt;&lt;br /&gt;If you add spinning disks to the memory hierarchy, you could do something ZFS-like and have a two-level cache hierarchy for that there filesystem. 10GB L1 in system RAM, 240GB L2 on SSDs, a couple TB on spinning disks. But again, how sensible is it to cache large media files (which will likely be the primary use of the spinning disks.) IT IS A MYSTERY AARGH&lt;br /&gt;&lt;br /&gt;It'd also be nice to have a 1-2GB of faster RAM between the CPU caches and the system RAM (with GPU-style 150GB/s bandwidth), but maybe you can only have such things if you have a large number of cores and don't care about latency. &lt;br /&gt;&lt;br /&gt;But hmm, the human tolerance for latency is around 0.2s for discrete stuff, 0.015-0.03s for continuous movement. In 0.2s you can do 4GB worth of memory reads and 0.3GB of IO, in 0.02s the numbers are 0.4GB and 0.03GB respectively. On a 3.5 GHz quad-core CPU, you can execute around 3 billion instructions in 0.2s, which'd give you a ratio of 0.75 ops per byte for scalar stuff and 6 ops per byte for 8 op vector instructions. On GPUs the ops/byte ratio is larger, at close to 20 ops/byte for vector ops, which makes them require more computation-heavy algorithms than CPUs.&lt;br /&gt;&lt;br /&gt;In 4GB you can fit 60 thousand uncompressed 128x128 images with some metadata. Or hmm, 6 thousand if each has ten animation frames.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1649861214066267392?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1649861214066267392/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1649861214066267392' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1649861214066267392'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1649861214066267392'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/01/how-to-take-advantage-of-that-memory.html' title='How to take advantage of that memory?'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3945360603255776548</id><published>2011-01-13T23:00:00.007+02:00</published><updated>2011-01-14T01:11:00.940+02:00</updated><title type='text'>The computer hardware of 2011</title><content type='html'>Here's a 1500e computer for 2011. It's a build where I tried to minimize the bottlenecks and get a nice memory pyramid with GPU cache 2 TB/s, GPU RAM 300GB/s, CPU cache 200GB/s, RAM 20GB/s, IO 2GB/s. Throw in 100e worth of spinning disks there in RAID-0 for a trailing 0.2GB/s disk IO. &lt;br /&gt;&lt;br /&gt;CPU: 4-6 cores, 3.5GHz, 150e. &lt;br /&gt;Motherboard: 150e.  &lt;br /&gt;Case: 50e steel box with razor-sharp edges and PSU from hell.&lt;br /&gt;&lt;br /&gt;Total: 350e.&lt;br /&gt;&lt;br /&gt;Memory: 8GB @ 150e.&lt;br /&gt;Memory bandwidth: 20GB/s.&lt;br /&gt;&lt;br /&gt;Total: 500e.&lt;br /&gt;&lt;br /&gt;IO subsystem: 4xSSD RAID-0 @ 500e.&lt;br /&gt;2GB/s streaming IO bandwidth.&lt;br /&gt;1.5GB/s random access IO bandwidth at 4k block size.&lt;br /&gt;&lt;br /&gt;Total: 1000e.&lt;br /&gt;&lt;br /&gt;GPU: two upper middle-class things @ 500e.&lt;br /&gt;Computing power: 3 TFLOPS.&lt;br /&gt;Memory bandwidth: 300GB/s.&lt;br /&gt;&lt;br /&gt;Total: 1500e.&lt;br /&gt;&lt;br /&gt;(Optionally 2x 500GB disks, 0.2GB/s IO bandwidth @ 100e for a total of 1600e.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Reasons for the selections:&lt;br /&gt;&lt;br /&gt;The performance difference between a 150e CPU and a 300e CPU is ~15%, and around 25% with overclocking. The point is more to get high memory bandwidth with minimal cost. Non-bargain mobo selected for 6Gbps SATA and dual GPUs. Only 8GB memory to cut costs. All money saved was tossed on getting random access IO onto the order-of-magnitude curve and the GPUs to cap the top end of the memory bandwidth curve (and for a disproportionate amount of computing power). The SSD numbers are based on recently announced SATA 6Gbps drives. If you don't care about GPU performance, get a 300e CPU, use IGP, double the RAM and buy an extra SSD.&lt;br /&gt;&lt;br /&gt;Now, programming this thing is going to be interesting. Not only do the CPU and GPU require parallelism to extract reasonable performance, but the random access IO subsystem does too. The IO subsystem latencies are probably in the 0.2ms range but it can do 64 parallel accesses. &lt;br /&gt;&lt;br /&gt;A single-threaded C program will achieve maybe 1% of peak performance of the hardware, if that.&lt;br /&gt;&lt;br /&gt;For legacy software, you could buy a 300e computer and it'd perform just as well.&lt;br /&gt;&lt;br /&gt;CPU: 70e, mobo: 60e, case: 50e, RAM: 20e, SSD: 90e. Toss a 40e disk in there for good measure.&lt;br /&gt;&lt;br /&gt;Or better yet, quit computers altogether and spend all the money on furniture.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3945360603255776548?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3945360603255776548/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3945360603255776548' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3945360603255776548'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3945360603255776548'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2011/01/computer-hardware-of-2011.html' title='The computer hardware of 2011'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5749320053459435190</id><published>2010-12-26T14:42:00.004+02:00</published><updated>2010-12-27T08:54:44.557+02:00</updated><title type='text'>The Space Campaign</title><content type='html'>Space Captain, First Galactic Baron, Royal Ring Knight, First Solar King and Third Moon Prince of the Space Planet New New Zealand, Second Master of the Holy Star Order, Imperial Great Lord of Space and Independent Space General, Sergeant Major Lieutenant Stanislaw Stanisgreat stood up from his space chair on the space deck to stare across the spacebow of the space ultrabattledestroyer HMS Stiff Upper Lip with his spacescope. There! In the distant depths of space, near the space planet of New South Macedonia, a blacker than black space cutter was cutting space towards the space merchant fleet sailing the space waves towards the space port of New North America. The weak, defenseless merchantmen were sitting in their space deckchairs just waiting to be picked off from space by the evil boat of space.&lt;br /&gt;&lt;br /&gt;Space Captain Stanislaw cursed a terrible space curse under his space breath to no one in particular, which was just as well for there is no sound in space. He gave the order to turn the space wheel of the space ultrabattledestroyer towards the space cutter, hoist the main space sail to the space mast and blow the space horn to sound the space crew to readiness. The space sails billowed in the space wind and started the ultrabattledestroyer towards the nefarious space cutter at infinite velocity. Even at infinite velocity, Stanislaw knew he'd be too late to stop the space cutter from sinking the space merchantmen into the depths of space and feeding their space crews to the space fishes. &lt;br /&gt;&lt;br /&gt;An endless instant later, the space ultrabattledestroyer closed to engagement range from the space cutter and turned its right side towards the dastardly space cutter. Stanislaw ordered a full thirty-fold space broadside alpha strike to be fired at the space cutter at his mark. Mark! The crewmen of the space cutter shouted in great alarm at the ultrabattledestroyer but their shouts were never heard in space before the report of the space guns of the ultrabattledestroyer drowned them out under a terrible booming fury of space. &lt;br /&gt;&lt;br /&gt;Millions of space cannonballs flew across space in slow motion, tearing gaping space holes in the space sails of the space cutter. One space cannonball hit the cutter right in the middle and split it in two. The mangled space cutter turned its cowardly tail and tried to run across space to the space asteroid field in the middle of space to escape the space pursuit of the space ultrabattledestroyer. &lt;br /&gt;&lt;br /&gt;Too bad for the space cutter, the ultrabattledestroyer had its space sensors deployed in space, giving them full knowledge of space. Space Captain Stanislaw set his magnificent space plan in motion and deployed a single space rowboat right in the space path of the space cutter. The space rowboat was carrying a deadly cargo of space gunpowder, which the spacemen aboard it set to explode with a slow-burning space fuse. The spacemen jumped to space off the space rowboat and space swam back to the welcoming space arms of the space ultrabattledestroyer.&lt;br /&gt;&lt;br /&gt;The space captain of the space cutter, Shining Diamond Vizier Democratically Elected Big Cat Corporate Chief Communist Politruck Liberal Nazi Warlord Space President of Badmanistan, General Colonel of Three Stripes and One Star Badstar Badmanis noticed the space rowboat a mere picosecond too late. His bellowed order to space duck was cut short in space as the space rowboat exploded with thunderous space roar and shot three million nuclear space lightnings everywhere in space, shattering the space cutter into five trillion pieces. An endless shower of space blood colored the space red. That was one space cutter that would not cut space again, thought the entire space crew of the space ultrabattledestroyer in unison in space.&lt;br /&gt;&lt;br /&gt;Space Captain Stanislaw Stanisgreat turned to face his space crew and congratulated them for a space job well done. His lily-white space suit and space tophat had been splattered crimson from the space blood and his visage was like that of a butchered space goat. The Space Admiral Peerless Thirty-fold Queen Emperor, Defender of Space, Attacker of the Realm, First Dame of Spacistan, Royala Bigshotikins climbed the ladder aboard the ultrabattledestroyer from her million-mile long gigaspacehyperdreadnoughtcarrier space flag ship and bestowed the space medal of Biggest Imperial Glory of Space upon the beaming Space Captain Stanislaw Stanisgreat. Stanislaw fought back a single space tear of infinite boundless space pride and saluted for hours at the Space Admiral.&lt;br /&gt;&lt;br /&gt;THE END&lt;br /&gt;THESE BOOKS MAKE ME ANGRY PUBLISHING, LTD.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5749320053459435190?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5749320053459435190/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5749320053459435190' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5749320053459435190'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5749320053459435190'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/12/space-campaign.html' title='The Space Campaign'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4863081760960767641</id><published>2010-12-19T08:56:00.004+02:00</published><updated>2010-12-19T09:48:24.676+02:00</updated><title type='text'>WebGL Orrery</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TQ2z8FSDl1I/AAAAAAAAB0g/DPHnDR5e4Ow/s1600/planets_sat.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 192px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TQ2z8FSDl1I/AAAAAAAAB0g/DPHnDR5e4Ow/s400/planets_sat.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5552291760409122642" /&gt;&lt;/a&gt;&lt;br /&gt;I wrote a small &lt;a href="http://fhtr.org/planets"&gt;WebGL orrery&lt;/a&gt; a month back or so, using planet and orbit data from Wikipedia. The orbits came out a bit wrong (planet positions on orbits are wrong, the planet rotation axis rotates along the orbit, planets velocities are constant, the orbits / axes may well be flipped somewhere), but it's a passably "realistic" orrery. Well, realistic apart from the whole having to scale down orbits by a factor of thousand to make the planets visible -thing. With 1:1 orbits, the planets are smaller than a pixel in size when viewed from other planets and the sun is about a pixel in diameter when viewed from Neptune.&lt;br /&gt;&lt;br /&gt;It's quite extensible too, it should be possible to add in all the moons just by  filling in their data. The coordinate system and z-buffer aren't accurate enough to do anything fancy like a spaceship sim with 1:1 orbits though :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4863081760960767641?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4863081760960767641/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4863081760960767641' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4863081760960767641'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4863081760960767641'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/12/webgl-orrery.html' title='WebGL Orrery'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_yZA36-WQFmM/TQ2z8FSDl1I/AAAAAAAAB0g/DPHnDR5e4Ow/s72-c/planets_sat.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6498317902573374022</id><published>2010-12-01T14:57:00.012+02:00</published><updated>2010-12-02T14:45:23.229+02:00</updated><title type='text'>A deck of cards</title><content type='html'>If not for the order property, you could cram a deck of cards into a 64-bit int. Though if you had a RNG cyclical in full_deck_size, that'd work to preserve the order property. Which would be enough for poker and other stackless or write-only stack card games. &lt;br /&gt;&lt;br /&gt;With the cyclical RNG you could pop a card off a full deck in O(1). With incomplete decks (that is, a deck with non-RNG-generated cards removed) you'd need to go through random cards until the generated card is in the deck. The worst-case time-complexity for popping all cards off an incomplete deck would be O(full_deck_size) and the average complexity for popping one card off an incomplete deck would be O(full_deck_size/incomplete_deck_size). Anyhow, incomplete decks would be as fast or slow as full decks, with an extra existence check slowing them down a bit further. The RNG would need a seed with log&lt;sub&gt;2&lt;/sub&gt;(fac(full_deck_size)) bits, so log&lt;sub&gt;2&lt;/sub&gt;(52!)  = 226 bits for a deck without jokers.&lt;br /&gt;&lt;br /&gt;A naive version of the RNG would be to generate a random number between 0 and 52!-1, then convert the number into 52 digits in factorial base. Divide first by 51!, the result is the first card. Divide the remainder by 50!, the result is the second card (note that you have to remove the first card from consideration for the second card index). Continue dividing the remainders until you have 52 cards. Or just generate a shuffled 52 element array of numbers and go through that :P&lt;br /&gt;&lt;br /&gt;... I should get back to work now.&lt;br /&gt;&lt;br /&gt;Source&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#include &amp;lt;inttypes.h&gt;&lt;br /&gt;#include &amp;lt;stdlib.h&gt;&lt;br /&gt;#include &amp;lt;stdio.h&gt;&lt;br /&gt;#include &amp;lt;time.h&gt;&lt;br /&gt;&lt;br /&gt;typedef int64_t card_mask;&lt;br /&gt;typedef int8_t card;&lt;br /&gt;&lt;br /&gt;typedef struct deck {&lt;br /&gt;  card cards[55];&lt;br /&gt;  int8_t length;&lt;br /&gt;  card_mask mask;&lt;br /&gt;} deck_t;&lt;br /&gt;&lt;br /&gt;#define suit_size 13&lt;br /&gt;&lt;br /&gt;static const card_mask hearts = suit_size*0;&lt;br /&gt;static const card_mask spades = suit_size*1;&lt;br /&gt;static const card_mask clubs = suit_size*2;&lt;br /&gt;static const card_mask diamonds = suit_size*3;&lt;br /&gt;static const card_mask jokers = suit_size*4;&lt;br /&gt;&lt;br /&gt;static const char* suits[5] = {"Hearts", "Spades", "Clubs", "Diamonds", "Jokers"};&lt;br /&gt;static const char* ranks[13] = {&lt;br /&gt;  "Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",&lt;br /&gt;  "Jack", "Queen", "King"&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;card_mask remove_cards_from_mask(card_mask deck, card_mask cards)&lt;br /&gt;{&lt;br /&gt;  return deck &amp; ~cards;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card_mask add_cards_to_mask(card_mask deck, card_mask cards)&lt;br /&gt;{&lt;br /&gt;  return deck | cards;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card_mask query_mask(card_mask deck, card_mask cards)&lt;br /&gt;{&lt;br /&gt;  return deck &amp; cards;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card_mask mask_of_card(card c)&lt;br /&gt;{&lt;br /&gt;  return (card_mask)1 &amp;lt;&amp;lt; (card_mask)c;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card card_for_suit_and_rank(card_mask suit, card_mask rank)&lt;br /&gt;{&lt;br /&gt;  return suit + rank;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int deck_initialize(deck_t *deck, int jokers)&lt;br /&gt;{&lt;br /&gt;  card c = 0;&lt;br /&gt;  if (jokers &amp;lt; 0 || jokers &gt; 3) &lt;br /&gt;    return 0;&lt;br /&gt;  deck-&gt;length = 52 + jokers;&lt;br /&gt;  deck-&gt;mask = ((card_mask)1 &amp;lt;&amp;lt; (card_mask)deck-&gt;length)-1;&lt;br /&gt;  for (c=0; c&amp;lt;deck-&gt;length; c++) {&lt;br /&gt;    deck-&gt;cards[c] = c;&lt;br /&gt;  }&lt;br /&gt;  return 1;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void deck_clear(deck_t *deck)&lt;br /&gt;{&lt;br /&gt;  deck-&gt;length = 0;&lt;br /&gt;  deck-&gt;mask = 0;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void deck_shuffle(deck_t *deck)&lt;br /&gt;{&lt;br /&gt;  int8_t i,j;&lt;br /&gt;  card tmp;&lt;br /&gt;  for (i=deck-&gt;length-1; i&gt;0; i--) {&lt;br /&gt;    j = rand() % (i+1);&lt;br /&gt;    tmp = deck-&gt;cards[j];&lt;br /&gt;    deck-&gt;cards[j] = deck-&gt;cards[i];&lt;br /&gt;    deck-&gt;cards[i] = tmp;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card_mask deck_query_mask(deck_t *deck, card_mask m)&lt;br /&gt;{&lt;br /&gt;  return query_mask(deck-&gt;mask, m);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card_mask deck_query_card(deck_t *deck, card c)&lt;br /&gt;{&lt;br /&gt;  return deck_query_mask(deck, mask_of_card(c));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card_mask deck_query_deck(deck_t *deck, deck_t *deck2)&lt;br /&gt;{&lt;br /&gt;  return deck_query_mask(deck, deck2-&gt;mask);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int deck_add(deck_t *deck, card c)&lt;br /&gt;{&lt;br /&gt;  card_mask m = mask_of_card(c);&lt;br /&gt;  if (query_mask(deck-&gt;mask, m)) {&lt;br /&gt;    return 0;&lt;br /&gt;  } else {&lt;br /&gt;    deck-&gt;mask = add_cards_to_mask(deck-&gt;mask, m);&lt;br /&gt;    deck-&gt;cards[deck-&gt;length] = c;&lt;br /&gt;    deck-&gt;length++;&lt;br /&gt;    return 1;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int deck_remove(deck_t *deck, card c)&lt;br /&gt;{&lt;br /&gt;  int8_t i=0, j=0;&lt;br /&gt;  card_mask m = mask_of_card(c);&lt;br /&gt;  if (!query_mask(deck-&gt;mask, m)) {&lt;br /&gt;    return 0;&lt;br /&gt;  } else {&lt;br /&gt;    deck-&gt;mask = remove_cards_from_mask(deck-&gt;mask, m);&lt;br /&gt;    for (i=0,j=0; i&amp;lt;deck-&gt;length; i++) {&lt;br /&gt;      if (deck-&gt;cards[i] != c) {&lt;br /&gt;        deck-&gt;cards[j] = deck-&gt;cards[i];&lt;br /&gt;        j++;&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    deck-&gt;length = j;&lt;br /&gt;    return 1;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;card deck_pop(deck_t *deck) &lt;br /&gt;{&lt;br /&gt;  card c;&lt;br /&gt;  if (deck-&gt;length &amp;lt; 1) {&lt;br /&gt;    return -1;&lt;br /&gt;  } else {&lt;br /&gt;    deck-&gt;length--;&lt;br /&gt;    c = deck-&gt;cards[deck-&gt;length];&lt;br /&gt;    deck-&gt;mask = remove_cards_from_mask(deck-&gt;mask, mask_of_card(c));&lt;br /&gt;    return c;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;const char* suit_string(card s)&lt;br /&gt;{&lt;br /&gt;  if (s &gt;= suit_size*5) &lt;br /&gt;    return "Invalid";&lt;br /&gt;  return suits[s / suit_size];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;const char* rank_string(card s)&lt;br /&gt;{&lt;br /&gt;  return ranks[s % suit_size];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void print_card(card c)&lt;br /&gt;{&lt;br /&gt;  printf("%s of %s\n", rank_string(c), suit_string(c));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void print_deck(deck_t *deck)&lt;br /&gt;{&lt;br /&gt;  int8_t i;&lt;br /&gt;  printf("Deck %p:\n", (void*)deck);&lt;br /&gt;  for (i=0; i&amp;lt;deck-&gt;length; i++) {&lt;br /&gt;    print_card(deck-&gt;cards[i]);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void print_mask(card_mask m)&lt;br /&gt;{&lt;br /&gt;  int i;&lt;br /&gt;  const char* symbols[5] = {"♥", "♠", "♣", "♦", "☻"};&lt;br /&gt;  for (i=0; i&amp;lt;64; i++) {&lt;br /&gt;    if (i%suit_size == 0) printf(" %s", symbols[i/suit_size]);&lt;br /&gt;    if ((m&gt;&gt;i) &amp; 1) putchar('#');&lt;br /&gt;    else putchar('.');&lt;br /&gt;  }&lt;br /&gt;  putchar('\n');&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main() &lt;br /&gt;{&lt;br /&gt;  int i=0;&lt;br /&gt;  deck_t deck, hand;&lt;br /&gt;  card c;&lt;br /&gt;  srand(time(NULL));&lt;br /&gt;  deck_initialize(&amp;deck,2);&lt;br /&gt;  deck_initialize(&amp;hand,0);&lt;br /&gt;  deck_clear(&amp;hand);&lt;br /&gt;  deck_shuffle(&amp;deck);&lt;br /&gt;  printf("Deck %p has %d cards and mask %ld.\n", (void*)&amp;deck, deck.length, deck.mask);&lt;br /&gt;  print_mask(deck.mask);&lt;br /&gt;  for (c=0; c&amp;lt;54; c++) {&lt;br /&gt;    if (!deck_query_card(&amp;deck, c)) {&lt;br /&gt;      printf("Error: Deck doesn't have a card.\n");&lt;br /&gt;      print_card(c);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  printf("Hand %p has %d cards and mask %ld.\n", (void*)&amp;hand, hand.length, hand.mask);&lt;br /&gt;  print_mask(hand.mask);&lt;br /&gt;  for (c=0; c&amp;lt;54; c++) {&lt;br /&gt;    if (deck_query_card(&amp;hand, c)) {&lt;br /&gt;      printf("Error: Hand has a card it shouldn't have.\n");&lt;br /&gt;      print_card(c);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  deck_remove(&amp;deck, hearts);&lt;br /&gt;  if (deck_query_card(&amp;hand, hearts)) &lt;br /&gt;    { printf("Error: Hand has a card it shouldn't have.\n"); print_card(hearts); }&lt;br /&gt;  deck_remove(&amp;deck, spades);&lt;br /&gt;  if (deck_query_card(&amp;hand, spades)) &lt;br /&gt;    { printf("Error: Hand has a card it shouldn't have.\n"); print_card(spades); }&lt;br /&gt;  deck_remove(&amp;deck, clubs);&lt;br /&gt;  if (deck_query_card(&amp;hand, clubs)) &lt;br /&gt;    { printf("Error: Hand has a card it shouldn't have.\n"); print_card(clubs); }&lt;br /&gt;  deck_remove(&amp;deck, diamonds);&lt;br /&gt;  if (deck_query_card(&amp;hand, diamonds)) &lt;br /&gt;    { printf("Error: Hand has a card it shouldn't have.\n"); print_card(diamonds); }&lt;br /&gt;  printf("Deck %p has %d cards and mask %ld.\n", (void*)&amp;deck, deck.length, deck.mask);&lt;br /&gt;  print_mask(deck.mask);&lt;br /&gt;  for (i=0; i&amp;lt;5; i++) {&lt;br /&gt;    c = deck_pop(&amp;deck);&lt;br /&gt;    print_card(c);&lt;br /&gt;    deck_add(&amp;hand, c);&lt;br /&gt;  }&lt;br /&gt;  printf("Deck %p has %d cards and mask %ld.\n", (void*)&amp;deck, deck.length, deck.mask);&lt;br /&gt;  print_mask(deck.mask);&lt;br /&gt;  printf("Hand %p has %d cards and mask %ld.\n", (void*)&amp;hand, hand.length, hand.mask);&lt;br /&gt;  print_mask(hand.mask);&lt;br /&gt;  print_deck(&amp;hand);&lt;br /&gt;  if (deck_query_deck(&amp;deck, &amp;hand) || deck_query_deck(&amp;hand, &amp;deck)) {&lt;br /&gt;    printf("Error: Deck and Hand intersect.\n");&lt;br /&gt;  } else {&lt;br /&gt;    printf("Deck and Hand are disjoint.\n");&lt;br /&gt;  }&lt;br /&gt;  for (i=0; i&amp;lt;hand.length; i++) {&lt;br /&gt;    if (deck_query_card(&amp;deck, hand.cards[i])) {&lt;br /&gt;      printf("ERROR ");&lt;br /&gt;    } else {&lt;br /&gt;      printf("OK ");&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  printf("\n");&lt;br /&gt;  for (i=0; i&amp;lt;deck.length; i++) {&lt;br /&gt;    if (deck_query_card(&amp;hand, deck.cards[i])) {&lt;br /&gt;      printf("Error: Found a card in Deck that is in Hand.\n");&lt;br /&gt;      print_card(deck.cards[i]);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  print_card(suit_size*5);&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Output&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ./a.out &lt;br /&gt;Deck 0x7fff98907250 has 54 cards and mask 18014398509481983.&lt;br /&gt; ♥############# ♠############# ♣############# ♦############# ☻##..........&lt;br /&gt;Hand 0x7fff98907210 has 0 cards and mask 0.&lt;br /&gt; ♥............. ♠............. ♣............. ♦............. ☻............&lt;br /&gt;Deck 0x7fff98907250 has 50 cards and mask 18013848686551038.&lt;br /&gt; ♥.############ ♠.############ ♣.############ ♦.############ ☻##..........&lt;br /&gt;Eight of Hearts&lt;br /&gt;Seven of Clubs&lt;br /&gt;Ten of Diamonds&lt;br /&gt;Six of Diamonds&lt;br /&gt;Nine of Hearts&lt;br /&gt;Deck 0x7fff98907250 has 45 cards and mask 17714777228828286.&lt;br /&gt; ♥.######..#### ♠.############ ♣.#####.###### ♦.####.###.### ☻##..........&lt;br /&gt;Hand 0x7fff98907210 has 5 cards and mask 299071457722752.&lt;br /&gt; ♥.......##.... ♠............. ♣......#...... ♦.....#...#... ☻............&lt;br /&gt;Deck 0x7fff98907210:&lt;br /&gt;Eight of Hearts&lt;br /&gt;Seven of Clubs&lt;br /&gt;Ten of Diamonds&lt;br /&gt;Six of Diamonds&lt;br /&gt;Nine of Hearts&lt;br /&gt;Deck and Hand are disjoint.&lt;br /&gt;OK OK OK OK OK &lt;br /&gt;Ace of Invalid&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6498317902573374022?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6498317902573374022/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6498317902573374022' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6498317902573374022'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6498317902573374022'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/12/deck-of-cards.html' title='A deck of cards'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4598100507456496515</id><published>2010-11-23T17:35:00.004+02:00</published><updated>2010-11-23T18:13:11.672+02:00</updated><title type='text'>Yet more stupid ideas</title><content type='html'>Amateur astronomer telescopes, live video streaming, combine the different observations into high-res frames: live 24/7 HD webcams of the planets.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Speckle_imaging"&gt;Speckle imaging&lt;/a&gt;&lt;br /&gt;&lt;a href="http://nuit-blanche.blogspot.com/2010/03/why-compressed-sensing-is-not-csi.html"&gt;Compressed sensing&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.cs.ust.hk/~quan/publications/yuan-deblur-siggraph07.pdf"&gt;Image Deblurring with Blurred/Noisy Image Pairs&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4598100507456496515?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4598100507456496515/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4598100507456496515' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4598100507456496515'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4598100507456496515'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/11/yet-more-stupid-ideas.html' title='Yet more stupid ideas'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3880042798478187570</id><published>2010-11-23T11:52:00.003+02:00</published><updated>2010-11-23T12:54:02.202+02:00</updated><title type='text'>More stupid ideas</title><content type='html'>Take &lt;a href="http://ec.europa.eu/dgs/secretariat_general/citizens_initiative/index_en.htm"&gt;The European Citizens' Initiative&lt;/a&gt;, add a public forum, link it up with EU proceedings, build a Citizens' Parliament.&lt;br /&gt;&lt;br /&gt;The ECI proposal says that you need a million signatories to send a citizens' initiative to the European Commission. The EC is &lt;a href="http://ec.europa.eu/atwork/basicfacts/index_en.htm#leg"&gt;the main proposal-generating entity&lt;/a&gt; in the EU. The European Parliament, in contrast, is &lt;a href="http://www.europarl.europa.eu/parliament/public/staticDisplay.do?id=46&amp;pageRank=2&amp;language=EN"&gt;the proposal-filtering entity&lt;/a&gt; (now with new proposal-generating powers granted to it by the Lisbon Treaty). In that sense, the ECI impacts the very top of the EU legislative system.&lt;br /&gt;&lt;br /&gt;The million signatories must include 0.2% of the voting-age citizens for a third of the member states to be valid. And after collecting 300 000 statements of support, you must submit it to the EC for an admissibility acid check where the EC figures out whether they can legally do it or not. After two months, you should get back a yay/nay. And you can collect the statements of support online as long as your mechanism is secure (further details pending).&lt;br /&gt;&lt;br /&gt;After that, 3 months of checking the signatures for validity by the member states using random sampling. After that, 4 months of examination by the EC, at the end of which it "would then be required to set out its conclusions on the initiative and the action it intends to take in a communication, which would be notified to the organiser as well as to the European Parliament and Council and would be made public."&lt;br /&gt;&lt;br /&gt;Not the most blisteringly fast method of democratic participation, is it? Still, it's very interesting.&lt;br /&gt;&lt;br /&gt;I wonder how much governance is a problem of thinking power (in terms of "need more heads to think better solutions!") and how much a sensor network problem ("need to know what's wrong with the world to fix it!") and how much a problem of integrating the two into a working system. How would this kind of a wide-ranging massively large parliament work best?&lt;br /&gt;&lt;br /&gt;A large and diverse population has experts from all the different walks of life. Which is probably where the thinking power of the citizens' parliament would best manifest. But you need to find the best proposals from the noise of mediocre proposals. Then you have the sensor network side, where a large population is again an asset. But again, there's a large amount of noise with a large population. So perhaps the important thing is making a good network to filter the signal.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3880042798478187570?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3880042798478187570/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3880042798478187570' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3880042798478187570'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3880042798478187570'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/11/more-stupid-ideas.html' title='More stupid ideas'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-809720851527769111</id><published>2010-11-18T10:06:00.005+02:00</published><updated>2010-11-18T12:54:14.509+02:00</updated><title type='text'>Stupid ideas</title><content type='html'>#1&lt;br /&gt;Use toString, Hungarian notation and a JS parser to compile JS functions to GLSL. To run the JS shaders in JS, implement the GLSL built-ins as a JS library and write a pure-JS GL implementation to laugh at. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// Pseudo-code time!&lt;br /&gt;&lt;br /&gt;var myShader = {&lt;br /&gt;  vertex: {&lt;br /&gt;    attributes: {v3Vertex: buffer, v2TexCoord: anotherBuffer},&lt;br /&gt;    uniforms: {m4PMatrix: cameraMatrix, m4MVMatrix: worldMatrix},&lt;br /&gt;    varyings: ['v2TexCoord0'],&lt;br /&gt;&lt;br /&gt;    // the hypothetical JS GLSL runtime would do something like &lt;br /&gt;    // shader.void_main.apply(GLSLContext, [])&lt;br /&gt;    void_main: function() {&lt;br /&gt;      var v4v = this.vec4(this.v3Vertex, 1.0);&lt;br /&gt;      this.v2TexCoord0 = this.v2TexCoord;&lt;br /&gt;      this.gl_Position = this.m4PMatrix.mulV4(this.m4MVMatrix.mulV4(v4v));&lt;br /&gt;    }&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  fragment: {&lt;br /&gt;    varyings: ['v2TexCoord0'],&lt;br /&gt;    uniforms: {s2DTexture0: myTexture, fRadius: 0.1},&lt;br /&gt;    void_main: function() {&lt;br /&gt;      var fd = this.fRadius / 5.0;&lt;br /&gt;      var v4Color = this.vec4(0.0);&lt;br /&gt;      for (var fi=0.0; fi&amp;lt;5.0; fi++) {&lt;br /&gt;        var v2tc = this.v2TexCoord0.add(this.vec2(fi*fd,fi*fd));&lt;br /&gt;        v4Color = v4Color.add(this.texture2D(this.s2DTexture0, v2tc));&lt;br /&gt;      }&lt;br /&gt;      this.gl_FragColor = v4Color;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;puts(GLSL.convert(myShader.vertex));&lt;br /&gt;=&gt;&lt;br /&gt;precision highp float;&lt;br /&gt;attribute vec3 v3Vertex;&lt;br /&gt;attribute vec2 v2TexCoord;&lt;br /&gt;uniform mat4 m4PMatrix;&lt;br /&gt;uniform mat4 m4MVMatrix;&lt;br /&gt;varying vec2 v2TexCoord0;&lt;br /&gt;void main() {&lt;br /&gt;  vec4 v4v = vec4(v3Vertex, 1.0);&lt;br /&gt;  v2TexCoord0 = v2TexCoord;&lt;br /&gt;  gl_Position = m4PMatrix * (m4MVMatrix * v4v);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;puts(GLSL.convert(myShader.fragment));&lt;br /&gt;=&gt;&lt;br /&gt;precision highp float;&lt;br /&gt;varying vec2 v2TexCoord0;&lt;br /&gt;uniform sampler2D s2DTexture0;&lt;br /&gt;uniform float fRadius;&lt;br /&gt;void main() {&lt;br /&gt;  float fd = fRadius / 5.0;&lt;br /&gt;  vec4 v4Color = vec4(0.0);&lt;br /&gt;  for (float fi=0.0; fi&amp;lt;5.0; fi++) {&lt;br /&gt;    vec2 v2tc = v2TexCoord0 + vec2(fi*fd, fi*fd);&lt;br /&gt;    v4Color = v4Color + texture2D(s2DTexture0, v2tc);&lt;br /&gt;  }&lt;br /&gt;  gl_FragColor = v4Color;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Maybe you could do an OpenCL implementation like that too.&lt;br /&gt;&lt;br /&gt;#2&lt;br /&gt;3D software should come with sculpt-friendly pre-rigged model kits with UV maps (yes, having some trouble with the technical aspects of 3D modeling). Also, &lt;a href="http://www.3d-coat.com/gallery/is-it-cool/"&gt;voxel sculpt&lt;/a&gt; looks amazing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-809720851527769111?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/809720851527769111/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=809720851527769111' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/809720851527769111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/809720851527769111'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/11/stupid-ideas.html' title='Stupid ideas'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7136910833908138951</id><published>2010-10-22T06:45:00.002+03:00</published><updated>2010-10-22T07:44:08.047+03:00</updated><title type='text'>Storage visions</title><content type='html'>Some sort of user-friendly UI to ZFS+Plan9. One big main filesystem that spans several drives and automatically does striping, mirroring and versioning. Fast drives are used as cache, big drives as mirrors and history.&lt;br /&gt;&lt;br /&gt;Plug in a new internal drive and usable space, redundancy and total performance grows by some percentage of the new drive's specs. Bring a new computer in the local area network and the filesystems on the computers are merged into a network-wide filesystem, adding usable space, redundancy and performance.&lt;br /&gt;&lt;br /&gt;Pull out a drive and the file system rebalances itself over the remaining drives. If some files require the pulled out drive, display notification and freeze the files until the drive is restored. Use a disk utility to separate a drive from the filesystem into a new filesystem, the utility copies files unique to the drive to other drives and disconnects and formats the separated drive when done. &lt;br /&gt;&lt;br /&gt;Computers on the local network are used as page cache and as mirrors of the filesystem. Cloud service is used as a backup mirror and remote sync. Treat the entire memory hierarchy and network as parts of the same single filesystem. When on the network, it doesn't matter which computer or which disk has the file, apart from performance.&lt;br /&gt;&lt;br /&gt;Transient devices carry a small local cache of the important things on the filesystem, and access the rest of the filesystem over the network. Either by connecting to the home network or by going through the cloud service. The local cache of the filesystem prioritizes metadata over the actual data and access to the actual data can usually be done by streaming a low-bandwidth version.&lt;br /&gt;&lt;br /&gt;*mirrorshades*&lt;br /&gt;*techno soundtrack*&lt;br /&gt;*sunrise*&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7136910833908138951?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7136910833908138951/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7136910833908138951' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7136910833908138951'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7136910833908138951'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/storage-visions.html' title='Storage visions'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1754274804505339695</id><published>2010-10-20T03:55:00.006+03:00</published><updated>2010-10-20T04:52:53.883+03:00</updated><title type='text'>How many dwarfs live in Mt. Everest</title><content type='html'>Dwarfs, those earth-dwelling little buggers, grand masters of volume. What if they burrowed the entire Mount Everest into a big dwarf-warren? Let's find out!&lt;br /&gt;&lt;br /&gt;First, some assumptions.&lt;br /&gt;&lt;br /&gt;Dwarfs are clearly good at cramped living as they live in mostly closed-off subterranean cities. They have also mastered the art of stonecraft to a magical level, so their buildings may well compare favorably to modern ferro-concrete towers. Let's assume that a dwarven city boasts a similar population density as urban Hong Kong, i.e. &lt;a href="http://www.huawei.com/success_stories/umts/simpleres.do?id=662&amp;type=general_catalog"&gt;250000&lt;/a&gt; dwarfs per square kilometer with an average building height of 45 meters. A cubic kilometer of dwarvopolis would consist of 20 such layers and have a population of 5 million dwarfs.&lt;br /&gt;&lt;br /&gt;Mount Everest is kinda pointy, but not all that pointy. Let's suppose that its slope is 45 degrees and that it approximates a cone. That would give it around 720 cubic kilometers in volume. But don't forget that dwarfs are subterranean, so we could reasonably assume that the dwarven city of Sagarmatha extends an extra kilometer under the surface with dwarven suburbia sprawling in a 10 kilometer radius. With the subterranean suburbs added, our dwarven city would have a total volume of around one thousand cubic kilometers.&lt;br /&gt;&lt;br /&gt;From which a simple multiplication leads us to a total Sagarmatha dwarvopolitan population of 5 billion. &lt;br /&gt;&lt;br /&gt;But why stop at Everest? Cast your net wider! The entire population of the Great Himalayan Dwarven Realm would be... the area of Himalayas is around 610000km^2, Tibetan plateau height is 4.5km, assume 0.5% of Everest population density for the sticks, i.e. 25000 dwarfs per cubic kilometer, and put it all together for a grand total of 69 billion Himalayan dwarfs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1754274804505339695?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1754274804505339695/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1754274804505339695' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1754274804505339695'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1754274804505339695'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/how-many-dwarfs-live-in-mt-everest.html' title='How many dwarfs live in Mt. Everest'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8118764390921538224</id><published>2010-10-20T00:23:00.008+03:00</published><updated>2010-10-21T17:37:11.542+03:00</updated><title type='text'>Ubuntu 10.10 nits</title><content type='html'>Nice fonts.&lt;br /&gt;Nice boot screen.&lt;br /&gt;Nice boot screen doesn't work with fglrx.&lt;br /&gt;Dark theme bg &amp; window style screws up labels with dark text.&lt;br /&gt;Dark title bar &amp; dark menu bar occasionally look silly with the light-grey window bg. Especially Firefox looks bad. The thing with dark bg themes is that the amount of apps designed to work properly with one is damn close to zero. (Hence, I suppose, the light-grey window bg.)&lt;br /&gt;The active window titlebar gradient looks kinda sloppy. Ditto for popup menu gradients.&lt;br /&gt;Flat window buttons walk a tightrope between sucking and being just boring.&lt;br /&gt;GTK widget theme buttons are very nice.&lt;br /&gt;Dropdown widget has the triangle vertically off-center by 1px?&lt;br /&gt;The salmon active color in the widget theme is not very nice.&lt;br /&gt;Scrollbar widget looks different from the rest of the theme and a bit weird. Exacerbated by rounded buttons but no alpha channel, so Firefox textarea scrollbar buttons look icky.&lt;br /&gt;New Rhythmbox icon is nice.&lt;br /&gt;Rhythmbox itself is still nowhere near Amarok 1.4. &lt;br /&gt;Amarok 2.0 is still nowhere near Rhythmbox.&lt;br /&gt;Gnome Terminal icon is vertically off-center in top panel.&lt;br /&gt;Notification area in top panel looks nice with the monochrome theme.&lt;br /&gt;App icons in top panel are too tall and look bad.&lt;br /&gt;Weather applet rainy icon has a black border. Other widget icons (other weather icons too) don't have borders, making the rainy icon look out-of-place.&lt;br /&gt;The speech bubble icon in top panel is one pixel too low (more like one pixel too high).&lt;br /&gt;The networking applet icon is super-confusing. I keep looking at it and wondering "Wtf was that icon for again? Check updates? No.. oh right, networking."&lt;br /&gt;The (very low-visibility) round corners in panel dropdown menus look nice. But suffer from lack of text padding.&lt;br /&gt;Window title color is actually orange instead of dark grey (try Chromium with GTK theme).&lt;br /&gt;1px wide window resize handles make me cry.&lt;br /&gt;The purple color works surprisingly well.&lt;br /&gt;Default desktop bg is nice.&lt;br /&gt;Popup text labels have too little horizontal padding. Otherwise they look nice.&lt;br /&gt;Date &amp; time applet text has too little horizontal padding as well.&lt;br /&gt;The panel applets have crap spacing so you need to manually move them around by a couple pixels to make the layout look less horrible.&lt;br /&gt;PulseAudio is glitchy and the volume control does not work (you can only set it between mute, medium volume and super-loud volume, no low volume setting available).&lt;br /&gt;The new fancy volume control doubling as a music player remote is nice.&lt;br /&gt;udev screws up md RAID arrays by being clueless (starts only one disk of a RAID-1 -&gt; stops boot process thx to unable mount disk &amp; dirty array after reassembly -&gt; need resync -&gt; FFFFUUUUUUU. How about using mdadm -A --scan like sane people do?) [edit: mdadm -A --scan wants to have an md device name like /dev/md0 instead of /dev/md/MyRAID, I dunno if that sunk udev or if it's just doing silly things.]&lt;br /&gt;Nautilus is slow so I very much try not to manage files with it (instead terminal for quick &amp; programmable things, Filezoo for overviews and visual sorting)&lt;br /&gt;The CPU speed indicator applet could show all the CPUs at once instead of requiring running several copies of it. &lt;br /&gt;I don't get what the Social Networks thing does. It doesn't seem to do anything. Maybe that's what it does. &lt;br /&gt;What is Ubuntu One? Perhaps these things should be explained as one starts them up? &lt;br /&gt;Why is this Gwibber thing so slow? Or perhaps it just lacks a progress indicator, making it feel slow.&lt;br /&gt;Evolution feels like a really fiddly and slow interface to Gmail.&lt;br /&gt;&lt;br /&gt;I do really like it for the most part, at least after a year in Debian's permabroken Gnome-land. I'd use FVWM or something cool and edgy and configurable like that for window management but compiz no worky on non-retarded WMs and theming FVWM (while infinitely easier than theming metacity) is limited and painful. Not that I've done any theming for a couple years. Plus you don't have a nice GUI to make F1 start a new terminal (just a nice thousand-line config file). &lt;br /&gt;&lt;br /&gt;Maybe there should be a WebKit-based desktop environment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8118764390921538224?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8118764390921538224/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8118764390921538224' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8118764390921538224'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8118764390921538224'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/ubuntu-1010-nits.html' title='Ubuntu 10.10 nits'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-2993910322780736178</id><published>2010-10-18T01:00:00.007+03:00</published><updated>2010-10-20T02:37:45.403+03:00</updated><title type='text'>Fast SSD is fast</title><content type='html'>The PCI-E SSD I bought is a 120GB &lt;a href="http://www.ocztechnology.com/products/solid-state-drives/pci-express/revodrive/ocz-revodrive-pci-express-ssd-.html"&gt;OCZ RevoDrive&lt;/a&gt;. Which is basically two 60 GB OCZ Vertex 2 SSDs mounted on a PCI-E fakeRAID controller.&lt;br /&gt;&lt;br /&gt;Works nicely in Ubuntu 10.10, though &lt;a href="http://www.ocztechnologyforum.com/forum/showthread.php?76486-Revodrive-Ubuntu-10.04-install-issues"&gt;apparently less nicely&lt;/a&gt; on earlier versions (problems booting from fakeRAID). &lt;br /&gt;&lt;br /&gt;The amusing thing about the SSD is that the controller is compressing all the data it writes to the drive. So you get 540MB/s speeds for reading a file full of zeroes but just 300MB/s for reading a file full of MP3. Write speeds are 475MB/s for zeroes, 300MB/s for MP3s. &lt;br /&gt;&lt;br /&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;" src="http://1.bp.blogspot.com/_yZA36-WQFmM/TL4rZQhjnuI/AAAAAAAABwo/SF-LqmUllpA/s1600/disk_util.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5529905105389985506" /&gt;&lt;br /&gt;&lt;br /&gt;A random access 4kiB read takes around 0.26 ms, for a 16MB/s random read speed. But you can do around 8 random reads in parallel, which gets you a 117MB/s random read speed. My 7200 RPM disks can do around 75MB/s with a streaming read, so 117MB/s random access speed is absolutely nuts.&lt;br /&gt;&lt;br /&gt;Parallel reads are kinda hard to program though. You could spawn a bunch of threads and do a gather into a shared array I suppose, though that feels a bit heavy (hah, right. I expect the threading overhead to be negligible.) Do the reads from a shared mmap of the file, each thread reading its own segments off it? [edit: no, mmap apparently serializes its page faults or OpenMP doesn't run the threads in parallel]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-2993910322780736178?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/2993910322780736178/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=2993910322780736178' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2993910322780736178'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2993910322780736178'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/fast-ssd-is-fast.html' title='Fast SSD is fast'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_yZA36-WQFmM/TL4rZQhjnuI/AAAAAAAABwo/SF-LqmUllpA/s72-c/disk_util.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4112873517064135971</id><published>2010-10-16T17:46:00.003+03:00</published><updated>2010-10-16T20:34:37.637+03:00</updated><title type='text'>Little benchmark</title><content type='html'>Got my computer put together yesterday, and now's the time to benchmark it! On a Saturday afternoon...&lt;br /&gt;&lt;br /&gt;Image correlation algorithm benchmark:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;OpenCL&lt;br /&gt;  240 GBps -- Athlon II X4 640, 3GHz (12GHz aggregate), 2MB L2&lt;br /&gt;   85 GBps -- Core 2 Duo E6400, 2.1GHz (4.3GHz aggregate), 2MB L2 &lt;br /&gt;OpenMP+SSE optimized&lt;br /&gt;  103 GBps -- Athlon II X4&lt;br /&gt;   45 GBps -- Core 2 Duo&lt;br /&gt;OpenMP+SSE naive&lt;br /&gt;   13 GBps -- Athlon II X4&lt;br /&gt;    5 GBps -- Core 2 Duo&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Pretty much linear scaling with clock frequency in OpenCL. Both have a 3 cycle L1 latency and the algorithm is very much an L1 cache benchmark, so this isn't too surprising. The SSE version has some bandwidth / load-balancing bottleneck going on, and the naive version is pretty much a pure memory bandwidth benchmark.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4112873517064135971?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4112873517064135971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4112873517064135971' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4112873517064135971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4112873517064135971'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/little-benchmark.html' title='Little benchmark'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5305152992431780647</id><published>2010-10-13T00:49:00.002+03:00</published><updated>2010-10-13T04:40:50.181+03:00</updated><title type='text'>New hardware!</title><content type='html'>In our cheap (and not-so-cheap) upgrade category, got me an Athlon II X4 + mobo + RAM (300€) and a PCI-E SSD (300€). Which should get me roughly 3x more CPU power, double the memory bandwidth, 5x the IO bandwidth and something like 700x random access performance. Now just have to piece it together and benchmark!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5305152992431780647?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5305152992431780647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5305152992431780647' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5305152992431780647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5305152992431780647'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/new-hardware.html' title='New hardware!'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7171554613242871508</id><published>2010-10-10T13:20:00.004+03:00</published><updated>2010-10-12T19:36:45.833+03:00</updated><title type='text'>WebGL presentation editor prototype</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_yZA36-WQFmM/TLGWkH_KXhI/AAAAAAAABwc/p9ypEndaqN4/s1600/editor3.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 266px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/TLGWkH_KXhI/AAAAAAAABwc/p9ypEndaqN4/s400/editor3.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5526363765123735058" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://fhtr.org/editor"&gt;Try it out here&lt;/a&gt;, requires a WebGL capable browser. It's a bit buggy, so watch your head.&lt;br /&gt;&lt;br /&gt;You can check out the sources on &lt;a href="http://github.com/kig/magi"&gt;GitHub&lt;/a&gt;. What's still missing is &lt;strike&gt;making the editor work properly without WebGL&lt;/strike&gt; DONE (though it's not Mobile Safari -friendly), making the slide editing more efficient, and &lt;strike&gt;fixing some markup parser bugs&lt;/strike&gt; fixed some. At that point it'd be a decent barebones slideshow app. Add themes, fancy filters and animations and stir on low heat.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7171554613242871508?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7171554613242871508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7171554613242871508' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7171554613242871508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7171554613242871508'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/webgl-presentation-editor-prototype.html' title='WebGL presentation editor prototype'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_yZA36-WQFmM/TLGWkH_KXhI/AAAAAAAABwc/p9ypEndaqN4/s72-c/editor3.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6704107182132131855</id><published>2010-10-04T15:17:00.006+03:00</published><updated>2010-10-04T16:47:11.403+03:00</updated><title type='text'>Desk drawer coding</title><content type='html'>Tally of different sorts of crap I've done to pass the time:&lt;br /&gt;&lt;br /&gt;Music players written: 4 (with mplayer / &amp;lt;audio&amp;gt; / Flash for playback, so maybe these are more "playlist players"). &lt;br /&gt;Image slideshows written: 5, three of which on arbitrarily long lists.&lt;br /&gt;Video players /w playlist: 1 (I just repurposed one slideshow).&lt;br /&gt;Document readers: 3, though they just show images of pages.&lt;br /&gt;Scene graph libraries for different retarded purposes: 7.&lt;br /&gt;GUI toolkits: 1. But it didn't go too well.&lt;br /&gt;LaTeX-style text justifier: 1.&lt;br /&gt;Layout text inside a polygon: 1. And it seems like there's zero literature on text layout algorithms.&lt;br /&gt;Tilemap zoomers: 3.&lt;br /&gt;Infinite tree hierarchy zoomers: 1.&lt;br /&gt;File managers: 1. And 3 web-based file managers. &lt;br /&gt;File thumbnailer libraries: 2.&lt;br /&gt;Thumbnail cache systems: 3.&lt;br /&gt;OpenGL-based presentation apps: 3.&lt;br /&gt;File annotation systems: 1.&lt;br /&gt;Metadata editing forms: 3.&lt;br /&gt;File metadata extraction libraries: 1.&lt;br /&gt;Standard library rewrites: 1.&lt;br /&gt;Array combinator libraries: 2. Well, maybe 3.&lt;br /&gt;Search query parsers: 2, the second time Doing It Right with a parser generator.&lt;br /&gt;Search query matchers: 1.&lt;br /&gt;DOM helper libraries: 2.&lt;br /&gt;Context-sensitive help in REPL: 1.&lt;br /&gt;Distributed message-passing libraries: 2.&lt;br /&gt;Network service discovery libraries: 1.&lt;br /&gt;Media players with networked storage and playback (controller on mobile, storage across the network, playback on HTPC): 1, but durr.&lt;br /&gt;Converter graph with pathfinder to convert files: 1 (IIRC plugged to the media player too, so it played PDFs with text-to-speech).&lt;br /&gt;Animation libraries with tweens and timelines: 1.&lt;br /&gt;SQL databases (tables and indices, not database engines): 5? Not very much. &lt;br /&gt;Memcached to cache results of expensive queries: 1.&lt;br /&gt;HTML fragment caches: 1.&lt;br /&gt;Tar parsers: 1.&lt;br /&gt;3D model parsers: 3 of varying levels of completeness.&lt;br /&gt;Huffman encoder &amp; decoder: 1 (yay, coursework).&lt;br /&gt;Image file formats: One vector drawing hack, plus three that basically concatenate JPEGs into a bigger file.&lt;br /&gt;Toy drawing programs: 3.&lt;br /&gt;Games: 1.&lt;br /&gt;&lt;br /&gt;Editors: 0.&lt;br /&gt;Compilers: 0.&lt;br /&gt;Desk calculators: 0.&lt;br /&gt;Programming languages: 0 (unless you count an OO Brainfuck implementation).&lt;br /&gt;Something useful: 0.&lt;br /&gt;&lt;br /&gt;Hmm, that was more than I initially thought :&amp;lt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6704107182132131855?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6704107182132131855/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6704107182132131855' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6704107182132131855'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6704107182132131855'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/10/desk-drawer-coding.html' title='Desk drawer coding'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7374656212636542705</id><published>2010-09-27T10:31:00.003+03:00</published><updated>2010-09-27T10:53:07.852+03:00</updated><title type='text'>Visitor stats for September 2010</title><content type='html'>&lt;h3&gt;Browser market share&lt;/h3&gt;&lt;br /&gt;43% Firefox&lt;br /&gt;31% Chrome&lt;br /&gt;10% Safari&lt;br /&gt;10% Explorer&lt;br /&gt;4% Opera&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;OS market share&lt;/h3&gt;&lt;br /&gt;56% Windows&lt;br /&gt;22% Linux&lt;br /&gt;19% OS X&lt;br /&gt;1.6% iOS&lt;br /&gt;0.7% Android&lt;br /&gt;&lt;br /&gt;More than 90% of visitors had a screen width of 1280 or higher. &lt;br /&gt;&lt;br /&gt;94% had Flash, 1% had a Flash version other than 10. 85% had Java.&lt;br /&gt;&lt;br /&gt;Most search engine visitors wanted to know about SSE matrix multiplication. Clearly a hot topic.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7374656212636542705?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7374656212636542705/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7374656212636542705' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7374656212636542705'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7374656212636542705'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/visitor-stats-for-september-2010.html' title='Visitor stats for September 2010'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5814856210988973291</id><published>2010-09-25T14:29:00.002+03:00</published><updated>2010-09-25T17:59:04.648+03:00</updated><title type='text'>Composing shaders</title><content type='html'>I haven't found a good way to do modular GLSL shaders, so here's some blather on that subject.&lt;br /&gt;&lt;br /&gt;Shaders themselves are strings of source code, compiled to a binary by the OpenGL drivers. A shader has a &lt;code&gt;void main()&lt;/code&gt; that is called to execute the shader. There are also global definitions in the form of attributes, varyings and uniforms. And a precision qualifier like &lt;code&gt;precision highp float;&lt;/code&gt;, and C preprocessor macros.&lt;br /&gt;&lt;br /&gt;A shader also has a type, it can either be a vertex shader or a fragment shader (or a geometry shader). The difference between the two is that a vertex shader determines what areas to consider for modification, whereas a fragment shader determines the output value for each element in the modified areas. Imagine you're drawing a filled circle: the vertex shader would output the bounding box of the circle and the fragment shader would test each element in the bbox to see if it lies inside the circle, setting them to the fill color if they do.&lt;br /&gt;&lt;br /&gt;But back to the composing part. If you have some common helper functions that you'd like to use in your different shaders (e.g. defaultTransform(), blend functions), how do you reuse them? I tried to link different shader objects together but it didn't work for whatever the reason. So now I'm just concatenating strings. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ShaderSource = Klass({&lt;br /&gt;  initialize: function(type, source) {&lt;br /&gt;    this.type = type;&lt;br /&gt;    this.text = toArray(arguments).slice(1).map(function(s) {&lt;br /&gt;      return s.text ? s.text : s;&lt;br /&gt;    }).join("\n");&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Maybe you want to further split your shaders into functional segments and turn those segments on and off for different uses. One way is to put some #ifdefs in the shader and prepend a block of #defines to your source.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ShaderSource = Klass({&lt;br /&gt;  initialize: function(type, source) {&lt;br /&gt;    this.type = type;&lt;br /&gt;    this.text = this.origText = toArray(arguments).slice(1).map(function(s) {&lt;br /&gt;      return s.text ? s.text : s;&lt;br /&gt;    }).join("\n");&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  setDefines : function(defines) {&lt;br /&gt;    this.defines = defines;&lt;br /&gt;    var defs = [];&lt;br /&gt;    for (var i in defines) {&lt;br /&gt;      defs.push("#define "+i+" "+defines[i]);&lt;br /&gt;    }&lt;br /&gt;    defs.push(this.origText);&lt;br /&gt;    this.text = defs.join("\n");&lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Another way would be to affix an identifier to the source strings and only use a string if it's enabled. This is like writing a feature-poor preprocessor by yourself.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ShaderSource = Klass({&lt;br /&gt;  initialize: function(type, source) {&lt;br /&gt;    this.type = type;&lt;br /&gt;    this.segments = toArray(arguments).slice(1);&lt;br /&gt;    this.text = this.segments.map(function(s) {&lt;br /&gt;      return s.text ? s.text : s;&lt;br /&gt;    }).join("\n");&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  setEnabled : function(enabled) {&lt;br /&gt;    this.enabled = enabled;&lt;br /&gt;    this.text = this.segments.map(function(s) {&lt;br /&gt;      if (!s.id || enabled[s.id])&lt;br /&gt;        return s.text ? s.text : s;&lt;br /&gt;      else&lt;br /&gt;        return "";&lt;br /&gt;    }).join("\n");  &lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  setDisabled : function(disabled) {&lt;br /&gt;    this.disabled = disabled;&lt;br /&gt;    this.text = this.segments.map(function(s) {&lt;br /&gt;      if (!s.id || !disabled[s.id])&lt;br /&gt;        return s.text ? s.text : s;&lt;br /&gt;      else&lt;br /&gt;        return "";&lt;br /&gt;    }).join("\n");  &lt;br /&gt;  }&lt;br /&gt;});&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Or some mix of the above. I don't really know what's the super-best way to do this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5814856210988973291?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5814856210988973291/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5814856210988973291' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5814856210988973291'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5814856210988973291'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/composing-shaders.html' title='Composing shaders'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1697284231429322351</id><published>2010-09-25T06:28:00.005+03:00</published><updated>2010-09-25T17:06:14.795+03:00</updated><title type='text'>Easy inheritance in JavaScript</title><content type='html'>JavaScript inheritance is cumbersome. You have a constructor function and it has a prototype. And inheritance is done by setting the prototype to a newly created parent object? What? That means that you better not do anything in the constructor, otherwise you'll have a royal pain deriving new prototypes from it.&lt;br /&gt;&lt;br /&gt;So I did what I do and wrote my own inheritance system, based on mixins. It still functions pretty much like the JavaScript system, but is more convenient for building the constructor and the prototype from existing classes.&lt;br /&gt;&lt;br /&gt;Here's the infamous OO geometry demo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Rectangle = Klass({&lt;br /&gt;  initialize : function(w, h) {&lt;br /&gt;    this.w = w;&lt;br /&gt;    this.h = h;&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  getArea : function() {&lt;br /&gt;    return this.w * this.h;&lt;br /&gt;  }&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Square = Klass(Rectangle, {&lt;br /&gt;  initialize : function(s) {&lt;br /&gt;    Rectangle.initialize.call(this, s, s);&lt;br /&gt;  }&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Moving = Klass({&lt;br /&gt;  mass : 1,&lt;br /&gt;&lt;br /&gt;  initialize : function(n) {&lt;br /&gt;    this.s = new Array(n);&lt;br /&gt;    this.a = new Array(n);&lt;br /&gt;    this.v = new Array(n);&lt;br /&gt;    this.f = new Array(n);&lt;br /&gt;    for (var i=0; i&amp;lt;n; i++){ &lt;br /&gt;      this.s[i] = this.a[i] = this.v[i] = this.f[i] = 0;&lt;br /&gt;    }&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  move : function(t) {&lt;br /&gt;    for (var i=0; i&amp;lt;this.s.length; i++) {&lt;br /&gt;      var a = this.a[i], s = this.s[i], v = this.v[i], f = this.f[i];&lt;br /&gt;      var newA = f/this.mass;&lt;br /&gt;      var newV = v + a*t + (1/2)*(newA-a)*t*t;&lt;br /&gt;      var newS = s + v*t + (1/2)*a*t*t + (1/3)*(newA-a)*t*t*t;&lt;br /&gt;      this.a[i] = newA;&lt;br /&gt;      this.v[i] = newV;&lt;br /&gt;      this.s[i] = newS;&lt;br /&gt;    }&lt;br /&gt;    this.previousMass = this.mass;&lt;br /&gt;  }&lt;br /&gt;})&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Printable = {&lt;br /&gt;  valueString : function() {&lt;br /&gt;    var a = [];&lt;br /&gt;    for (var i in this)&lt;br /&gt;      if (typeof this[i] != 'function') a.push(i);&lt;br /&gt;    a.sort();&lt;br /&gt;    for (var i=0; i&amp;lt;a.length; i++)&lt;br /&gt;      a[i] = a[i] + ": " + this[a[i]];&lt;br /&gt;    return a.join(", ");&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  print : function() {&lt;br /&gt;    console.log(this.valueString());&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;MovingRectangle = Klass(Rectangle, Moving, Printable, {&lt;br /&gt;  density : 0.19,&lt;br /&gt;  height : 1,&lt;br /&gt;  initialize : function(w,h,d) {&lt;br /&gt;    Rectangle.initialize.call(this,w,h);&lt;br /&gt;    Moving.initialize.call(this,2);&lt;br /&gt;    if (d)&lt;br /&gt;      this.density = d;&lt;br /&gt;    this.mass = this.getVolume() * this.density;&lt;br /&gt;  },&lt;br /&gt;&lt;br /&gt;  getVolume : function() {&lt;br /&gt;    return this.getArea() * this.height;&lt;br /&gt;  }&lt;br /&gt;})&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Klass is a function that builds a constructor. The returned constructor calls its prototype's initialize method. &lt;br /&gt;&lt;br /&gt;The constructor's prototype is built by merging together all the arguments given in the Klass call. If an argument has a prototype property defined, the prototype is used for merging. Otherwise the argument is merged as itself.&lt;br /&gt;&lt;br /&gt;The built prototype is then finally merged into the constructor function, so that you have access to the prototype functions and properties from the constructor object itself. Which is useful for doing calls to other classes, as you can do e.g. Parent.someMethod.call(this) instead of Parent.prototype.someMethod.call(this).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Klass = function() {&lt;br /&gt;  var c = function() {&lt;br /&gt;    this.initialize.apply(this, arguments);&lt;br /&gt;  }&lt;br /&gt;  c.ancestors = toArray(arguments);&lt;br /&gt;  c.prototype = {};&lt;br /&gt;  for(var i = 0; i&amp;lt;arguments.length; i++) {&lt;br /&gt;    var a = arguments[i];&lt;br /&gt;    if (a.prototype) {&lt;br /&gt;      Object.extend(c.prototype, a.prototype);&lt;br /&gt;    } else {&lt;br /&gt;      Object.extend(c.prototype, a);&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  Object.extend(c, c.prototype);&lt;br /&gt;  return c;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Object.extend merges the src object's attributes with the dst object, ignoring errors.  Ignoring the errors is useful in merging native objects (e.g. styles) as they've got some stuff that can't be overridden.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Object.extend = function(dst, src) {&lt;br /&gt;  for (var i in src) {&lt;br /&gt;    try{ dst[i] = src[i]; } catch(e) {}&lt;br /&gt;  }&lt;br /&gt;  return dst;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;toArray creates a new array from an object that has a length property. Useful for dealing with all the silly Array-like objects such as the function arguments object and the lists returned by the DOM API.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;toArray = function(obj) {&lt;br /&gt;  var a = new Array(obj.length);&lt;br /&gt;  for (var i=0; i&amp;lt;obj.length; i++)&lt;br /&gt;    a[i] = obj[i];&lt;br /&gt;  return a;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1697284231429322351?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1697284231429322351/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1697284231429322351' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1697284231429322351'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1697284231429322351'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/easy-inheritance-in-javascript.html' title='Easy inheritance in JavaScript'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4285293784181666933</id><published>2010-09-22T08:00:00.003+03:00</published><updated>2010-09-22T08:37:46.544+03:00</updated><title type='text'>WebGL slides translated</title><content type='html'>I translated my &lt;a href="http://fhtr.org"&gt;WebGL slides&lt;/a&gt; to English and put them on GitHub Pages. Which is great, btw. Upgraded my account to Micro just to give them props for that.&lt;br /&gt;&lt;br /&gt;If you would like to buy an app to make fancy WebGL slideshows, &lt;a href="mailto:ilmari.heikkinen@gmail.com"&gt;send me a mail&lt;/a&gt; and I'll get back to you. &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_yZA36-WQFmM/TJmVup78nYI/AAAAAAAABv8/tHfxmv4fMN8/s1600/dots.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 178px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/TJmVup78nYI/AAAAAAAABv8/tHfxmv4fMN8/s400/dots.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5519607447083064706" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you'd like to subscribe to a web-based zoomable file management &amp; sync service instead, &lt;a href="mailto:ilmari.heikkinen@gmail.com"&gt;send me a mail&lt;/a&gt; and I'll get back to you.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_yZA36-WQFmM/TJmVpd8FQZI/AAAAAAAABv0/zOGmNwSozUs/s1600/foo.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 271px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/TJmVpd8FQZI/AAAAAAAABv0/zOGmNwSozUs/s400/foo.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5519607357963059602" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In other news, &lt;a href="http://twitter.com/ilmarihei"&gt;I'm back on Twitter&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4285293784181666933?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4285293784181666933/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4285293784181666933' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4285293784181666933'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4285293784181666933'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/webgl-slides-translated.html' title='WebGL slides translated'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_yZA36-WQFmM/TJmVup78nYI/AAAAAAAABv8/tHfxmv4fMN8/s72-c/dots.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1978152275593295921</id><published>2010-09-17T19:57:00.005+03:00</published><updated>2010-09-18T09:19:34.729+03:00</updated><title type='text'>Saturday Night Links</title><content type='html'>&lt;iframe src="http://player.vimeo.com/video/14458079" width="400" height="225" frameborder="0"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;a href="http://vimeo.com/14458079"&gt;ARTtech 2010: Fairlight's rendering secrets&lt;/a&gt; from &lt;a href="http://vimeo.com/assemblytv"&gt;AssemblyTV&lt;/a&gt;. The render graph approach is interesting. And the demo editor. &lt;br /&gt;&lt;br /&gt;&lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=276431"&gt;Yay! SVG in IMG coming in Firefox.&lt;/a&gt; Should enable fancy scalable UI images and easy SVG textures in WebGL.  (Edit: In 2010-09-18 nightly SVG in IMG works but can't be drawn to canvas or uploaded as WebGL texture.)&lt;br /&gt;&lt;br /&gt;&lt;object width="960" height="745"&gt;&lt;param name="movie" value="http://www.youtube.com/v/sdz_gfGi0GI?fs=1&amp;amp;hl=en_US&amp;amp;hd=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/sdz_gfGi0GI?fs=1&amp;amp;hl=en_US&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="960" height="745"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;Creature workflow in &lt;a href="http://area.autodesk.com/mudbox2011"&gt;Mudbox 2011&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;object width="960" height="565"&gt;&lt;param name="movie" value="http://www.youtube.com/v/-3meRFL9GKo?fs=1&amp;amp;hl=en_US&amp;amp;hd=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/-3meRFL9GKo?fs=1&amp;amp;hl=en_US&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="960" height="565"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;Auto-retopology in &lt;a href="http://www.3d-coat.com/"&gt;3D Coat&lt;/a&gt;. Take a high-poly model, paint over areas that you want at high resolution, draw lines to suggest edgeloop direction, done!&lt;br /&gt;&lt;br /&gt;Maybe one day 3D modeling won't require thinking about polygons. You'd just have a high-resolution form that you deform and then save a low-res version for interactive use. I dunno. The whole "create box, extrude, cut, unwrap, texture"-flow turns me off. It's like you're doing reverse origami: you start with a paper box, then you extrude it, bend it, cut bits off, glue bits back on, cut it open and splay it flat, draw a picture on it, put it back together, stuff a wire model inside, animate. And all the time you're worrying if you got the bends just right so that the joints don't look stupid.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1978152275593295921?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1978152275593295921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1978152275593295921' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1978152275593295921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1978152275593295921'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/saturday-night-links.html' title='Saturday Night Links'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6989408660373793986</id><published>2010-09-16T22:22:00.009+03:00</published><updated>2010-09-18T00:38:12.810+03:00</updated><title type='text'>Frontend Finland WebGL slides</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.cs.helsinki.fi/u/ilmarihe/jscene/demos/webgl_presentation.html"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 208px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/TJJ4rSSUWtI/AAAAAAAABvs/Prq_Z_Nev4Q/s400/slaidi.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5517605178520656594" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I gave a talk at the &lt;a href="http://frontendfinland.org"&gt;Frontend Finland&lt;/a&gt; meetup about WebGL. Had a great time, thanks to the organizers! My presentation slides are available &lt;a href="http://www.cs.helsinki.fi/u/ilmarihe/jscene/demos/webgl_presentation.html"&gt;here&lt;/a&gt; in glorious Finnish. Best experienced with a browser that has &lt;a href="http://www.khronos.org/webgl/wiki/Getting_a_WebGL_Implementation"&gt;a working WebGL implementation&lt;/a&gt; (fear not, there's a fallback to plain unadorned HTML). Writing the slideshow app (and the slides) took about three days, with four days of preliminary library work (and the app dev fed into the library too). &lt;br /&gt;&lt;br /&gt;Here are some links on the topics covered:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.khronos.org/webgl/wiki/Main_Page"&gt;WebGL&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/doc/spec/TypedArray-spec.html"&gt;Typed Arrays&lt;/a&gt; may also make JS &lt;a href="http://blog.vlad1.com/2010/07/30/fun-with-fast-javascript/"&gt;faster&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://learningwebgl.com"&gt;Learning WebGL&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://glge.org"&gt;GLGE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://spidergl.org"&gt;SpiderGL&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://o3d.googlecode.com"&gt;O3D&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://webglsamples.googlecode.com"&gt;Google's WebGL samples&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://planet-webgl.org"&gt;Planet WebGL&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.iquilezles.org/apps/shadertoy/"&gt;Shader Toy&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://web.chemdoodle.com/overview3D.php"&gt;ChemDoodle&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blog.tojicode.com/2010/08/rendering-quake-3-maps-with-webgl-demo.html"&gt;Rendering Quake 3 maps with WebGL&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://quake2-gwt-port.googlecode.com"&gt;Quake2 GWT port&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://angleproject.googlecode.com"&gt;ANGLE&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://videos.mozilla.org/serv/blizzard/audio-slideshow/"&gt;Firefox 4 Audio Data API demo slides&lt;/a&gt; (&lt;a href="http://www.youtube.com/watch?v=oJ1UsLoPX3E"&gt;video&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.w3.org/2005/Incubator/audio/"&gt;W3C Audio Incubator Group&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://wiki.mozilla.org/Audio_Data_API"&gt;Audio Data API Docs&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mouaif.wordpress.com/2009/01/05/photoshop-math-with-glsl-shaders/"&gt;Photoshop math with GLSL shaders&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The git repo for the utility library and the presentation can be found &lt;a href="http://github.com/kig/magi"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://frontendfinland-2010-09-15-svg.heroku.com/"&gt;The SVG presentation&lt;/a&gt; by &lt;a href="http://www.cs.helsinki.fi/u/paksula/"&gt;Matti Paksula&lt;/a&gt; was really nifty as well. After the show, we were brainstorming about some kind of a super slideshow where you'd write the slides in HTML (with a nice CSS), then they'd get rendered using SVG / Canvas / WebGL depending on the capabilities of your browser. (What my presentation is doing is parsing the DOM into scene graph nodes, then rendering them using WebGL. Doing a similar system that rendered using SVG wouldn't be too difficult.)&lt;br /&gt;&lt;br /&gt;Preserving the clickability of links and other such webness would be problematic with WebGL, but a mix of SVG, CSS 3D and WebGL might get you the best of both worlds: overlay SVG elems on top of the WebGL canvas, change the CSS 3D transform of the elems to match the WebGL scene graph. That'd be a lot like CAKE's mixed HTML/Canvas, except in 3D. If you want fancy FX on your SVG elems while preserving interactivity, you'd render them to textures and put the original element at opacity 0.01 on top of the fancy render, maybe? Or do shaders for SVG?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6989408660373793986?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6989408660373793986/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6989408660373793986' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6989408660373793986'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6989408660373793986'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/frontend-finland-webgl-slides.html' title='Frontend Finland WebGL slides'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_yZA36-WQFmM/TJJ4rSSUWtI/AAAAAAAABvs/Prq_Z_Nev4Q/s72-c/slaidi.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6828839459175117255</id><published>2010-09-11T04:11:00.002+03:00</published><updated>2010-09-11T04:14:57.375+03:00</updated><title type='text'>Saturday morning link</title><content type='html'>&lt;a href="http://pouet.net/prod.php?which=55795"&gt;Dust by Elude&lt;/a&gt;. Amiga AGA. Insane.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6828839459175117255?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6828839459175117255/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6828839459175117255' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6828839459175117255'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6828839459175117255'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/saturday-morning-link.html' title='Saturday morning link'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1104399832156199637</id><published>2010-09-10T06:12:00.002+03:00</published><updated>2010-09-10T06:14:54.190+03:00</updated><title type='text'>Dude!</title><content type='html'>&lt;object width="960" height="505"&gt;&lt;param name="movie" value="http://www.youtube.com/v/cNvJy0zoXOY?fs=1&amp;amp;hl=en_US&amp;amp;hd=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/cNvJy0zoXOY?fs=1&amp;amp;hl=en_US&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="960" height="505"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1104399832156199637?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1104399832156199637/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1104399832156199637' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1104399832156199637'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1104399832156199637'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/dude.html' title='Dude!'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7382535540487009457</id><published>2010-09-09T19:37:00.002+03:00</published><updated>2010-09-09T19:51:12.774+03:00</updated><title type='text'>SDO video of solar activity</title><content type='html'>&lt;object width="960" height="505"&gt;&lt;param name="movie" value="http://www.youtube.com/v/kS1eD3K6sMY?fs=1&amp;amp;hl=fi_FI&amp;amp;hd=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/kS1eD3K6sMY?fs=1&amp;amp;hl=fi_FI&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="960" height="505"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;a href="http://sdo.gsfc.nasa.gov/gallery/potw.php?v=item&amp;id=22"&gt;SDO video of solar activity over almost three months&lt;/a&gt; (via &lt;a href="http://www.vizworld.com/2010/09/85-days-solar-rotation/"&gt;VizWorld&lt;/a&gt;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7382535540487009457?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7382535540487009457/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7382535540487009457' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7382535540487009457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7382535540487009457'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/sdo-video-of-solar-activity.html' title='SDO video of solar activity'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7150087657626062760</id><published>2010-09-08T17:29:00.003+03:00</published><updated>2010-09-09T02:53:15.013+03:00</updated><title type='text'>Arithmetic</title><content type='html'>You have things, some things. Call these one-things. There is also a no-thing, call it 0. When you add one-thing to 0, we call that 1. Add a one-thing to 1 and you get 2. Add one-thing to 2 and you get 3. In that way we build a small list of numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.&lt;br /&gt;&lt;br /&gt;When you add one-thing to 9, we don't have a number for that here. But let's call it a ten-thing and mark it on left of the one-things. We have 1 ten-thing and 0 one-things, written in short as 10. Then we can add a one-thing to the 0 one-things to get 11. When we add a one-thing to 19, we get two ten-things: 20. When we have ten ten-things, we call that a hundred-thing and write it as 100: one hundred-thing, zero ten-things, zero one-things.&lt;br /&gt;&lt;br /&gt;In a similar fashion, we could have a shorter list of numbers, say, 0, 1, 2, 3, 4. Now when you add one-thing to 4, you get one five-thing and zero one-things, which we can write as 10&lt;sub&gt;5&lt;/sub&gt;. When we have five five-things, we call that a twenty-five-thing and write it as 100&lt;sub&gt;5&lt;/sub&gt;. &lt;br /&gt;&lt;br /&gt;There are also negative things. When a negative thing is added to a positive thing, they both disappear. Adding one negative thing to one positive thing makes both disappear, leaving you with zero things. If you add 5 negative things to 8 positive things, the 5 negative things and 5 positive things disappear, leaving 3 positive things. Adding 2 negative things to 3 more negative things gets you 5 negative things. Adding 4 negative things to 2 positive things makes the 2 positive things and 2 negative things disappear, leaving 2 negative things.&lt;br /&gt;&lt;br /&gt;When you add 5 negative one-things to 2 positive ten-things, you need to crack open one of the ten-things to get ten positive one-things. The 5 negative things and 5 positive one-things disappear, leaving you with 1 positive ten-thing and 5 positive one-things: 15.&lt;br /&gt;&lt;br /&gt;To add 5 negative one-things to 1 positive hundred-thing, you first crack open the hundred-thing to get ten ten-things, then crack open one of the ten-things to get ten one-things. The 5 negative one-things and 5 positive one-things disappear, leaving you with 9 positive ten-things and 5 positive one-things: 95.&lt;br /&gt;&lt;br /&gt;Adding a zero to a number results in the number itself. Adding 5 to 0 results in 5. Adding 0 to 5 results in 5.&lt;br /&gt;&lt;br /&gt;Negating a number turns a positive number into a negative number and a negative number into a positive number. Positive 2 negated is negative 2. Negative 2 negated is positive 2. Adding a number to its negation is zero. Positive 2 added to negative 2 is zero.&lt;br /&gt;&lt;br /&gt;We write positive numbers with a + in front of them and negative numbers with a - in front of them. Positive 2 is +2. Negative 2 is -2. We write addition with a + between the numbers. The + symbol is called plus. Negative 3 plus positive 2 is -3 + +2. We write negate as -(number). Positive 2 negated is -(+2). Negative 2 negated is -(-2).&lt;br /&gt;&lt;br /&gt;Two numbers are equal if they are the same. Equality is written with a =. 2 equals 2 is written 2 = 2 and amounts to true. If the two numbers are not the same, the equality operation returns false. If both sides of the equality operation are identical, the equality operation returns true. 2 = 2 is true. 2 = 3 is false. 1 + 1 = 2 is undecided, rewriting it as 2 = 2 or 1 + 1 = 1 + 1 makes it true.&lt;br /&gt;&lt;br /&gt;Subtraction is an operation where the first number is added to the negation of the second number. Subtraction is written with a - between the numbers. The - symbol is called minus. -3 minus +2 is written -3 - +2 and means -3 + -(+2).&lt;br /&gt;&lt;br /&gt;Multiplication is an operation where each of the things in the first number is replaced by the second number. Multiplying +3 by -2 replaces each of the 3 things with a -2, giving you +(-2) + +(-2) + +(-2) = -6. Multiplying +15 by +3 requires splitting open the ten-things to replace each of the one-things inside them with a +3, giving you +15 +3-things, which amount to +45 when added together. Multiplying -2 by -5 replaces the two things with -5-things, giving you -(-5) + -(-5), which simplifies to +5 + +5 and amounts to +10. Multiplying +0 by -2 gives you zero -2-things, which amounts to zero. Multiplying -2 by +0 replaces the two things with +0, giving you -(+0) + -(+0) = -0 + -0 = -0.&lt;br /&gt;&lt;br /&gt;Fractional things are parts of one-things, much like how one-things are parts of ten-things. Fractional things are defined by how many parts are needed to make up a one-thing. When you need two parts for one-thing, the fractional thing is called a half. When you need three parts, the fractional thing is one-third. With five parts needed, it's a one-fifth. Five one-fifths added together amount to one. Similarly three one-thirds amount to one. Fractional numbers are written as 1/parts. One-fifth is written +1/5. One-third is written as +1/3. Negative one-sixth is written -1/6.&lt;br /&gt;&lt;br /&gt;Adding 1/6 to 1/6 results in 2/6. Adding 1/6 to 5/6 results in 6/6, which equals 1. Adding 1/6 to 1 is 1/6 + 6/6 = 7/6. Adding 1/5 to 4/5 results in 5/5, which also equals 1. Adding 1/5 to 1 is 1/5 + 5/5 = 6/5.&lt;br /&gt;&lt;br /&gt;Multiplication is written with an x between the numbers. The x is called times. 3 times 5 is written 3 x 5 and means 3 multiplied by 5, amounting to 15.&lt;br /&gt;&lt;br /&gt;Multiplying a number by one results in the number itself. 5 x 1 is 5. 1 x 5 is 5.&lt;br /&gt;&lt;br /&gt;The inverse of a number multiplied with the number itself results in one. The inverse of a number is the fraction with number parts. Zero does not have an inverse. The inverse of +5 is +1/5. The inverse of -3 is -1/3. &lt;br /&gt;&lt;br /&gt;Three times the inverse of three is 3 x 1/3 = 1/3 + 1/3 + 1/3 = 3/3 = 1. &lt;br /&gt;&lt;br /&gt;Negative two times the inverse of negative two is -2 x 1/-2 = -(-1/2) + -(-1/2) = 1/2 + 1/2 = 2/2 = 1.&lt;br /&gt;&lt;br /&gt;Division is an operation where you multiply the first number with the inverse of the second number. 3 divided by 5 is 3 x 1/5 and amounts to 3/5. 8 divided by 2 is 8 x 1/2 and amounts to 8/2, which adds up to 4. &lt;br /&gt;&lt;br /&gt;Division is written with a / between the numbers. 5 divided by -3 is written 5 / -3 and means 5 x -1/3, and amounts to -5/3, or -1 + -2/3.&lt;br /&gt;&lt;br /&gt;The allowed moves when manipulating an equation with additions are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Apply associativity rule to two additions: a + (b + c) = (a + b) + c&lt;/li&gt;&lt;li&gt;Apply commutativity rule to an addition: a + b = b + a&lt;/li&gt;&lt;li&gt;Add the same value to both sides of the equality: a = a &amp;lt;=&gt; a + b = a + b&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;For an equation with multiplications, the above moves apply as well, but there is an additional move when dealing with a mix of additions and multiplications:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Apply distributivity rule: a x (b + c) = a x b  +  a x c&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;These are the only allowed structural moves for manipulating equations of additions and multiplications.&lt;br /&gt;&lt;br /&gt;Additions can use the following equalities for simplifying expressions: a + 0 = a and a + -a = 0. Multiplications can use a x 1 = a and a x 1/a = 1.&lt;br /&gt;&lt;br /&gt;We can build more moves from the above, for example:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;I'm playing fast and loose with the associativity in these, &lt;br /&gt;a + b + c = (a + b) + c = a + (b + c), &lt;br /&gt;I'm using whichever is the easiest at any given moment.&lt;br /&gt;&lt;br /&gt;Also, multiplication binds tighter than addition:&lt;br /&gt;a x b + c x d = (a x b) + (c x d)&lt;br /&gt;&lt;br /&gt;     x + b = c      | a = a &amp;lt;=&gt; a + b = a + b&lt;br /&gt;x + b + -b = c + -b | a + -a = 0&lt;br /&gt;     x + 0 = c + -b | a + 0 = a&lt;br /&gt;         x = c + -b | a - b = a + -b&lt;br /&gt;         x = c - b&lt;br /&gt;x + b = c &amp;lt;=&gt; x = c - b&lt;br /&gt;&lt;br /&gt;      a x b = c       | a = a &amp;lt;=&gt; a x b = a x b&lt;br /&gt;a x b x 1/b = c x 1/b | a x 1/a = 1 when a != 0&lt;br /&gt;      a x 1 = c x 1/b | a x 1 = a&lt;br /&gt;          a = c x 1/b | a / b = a x 1/b&lt;br /&gt;          a = c / b&lt;br /&gt;a x b = c &amp;lt;=&gt; a = c / b when a != 0&lt;br /&gt;&lt;br /&gt;(a + b) x c = (a + b) x c   | a x b = b x a&lt;br /&gt;(a + b) x c = c x (a + b)   | a x (b + c) = a x b + a x c&lt;br /&gt;(a + b) x c = c x a + c x b | a x b = b x a&lt;br /&gt;(a + b) x c = a x c + b x c&lt;br /&gt;&lt;br /&gt;a x 1 = a x 1 | a x b = b x a&lt;br /&gt;a x 1 = 1 x a&lt;br /&gt;&lt;br /&gt;a x b = a x b                  | (a + b) x c = c x a + c x b&lt;br /&gt;a x b = (a + -1) x b + (1 x b) | 1 x a = a&lt;br /&gt;a x b = (a + -1) x b + b       | a - b = a + -b&lt;br /&gt;a x b = (a - 1) x b + b&lt;br /&gt;&lt;br /&gt;         -(a) = -(a)     | a = b &amp;lt;=&gt; a + b = a + b&lt;br /&gt;     a + -(a) = a + -(a) | a + -(a) = 0&lt;br /&gt;     a + -(a) = 0        | a = b &amp;lt;=&gt; a + b = a + b&lt;br /&gt;-a + a + -(a) = -a + 0   | a + -a = 0&lt;br /&gt;     0 + -(a) = -a + 0   | a + b = b + a and a + 0 = a&lt;br /&gt;         -(a) = -a &lt;br /&gt;&lt;br /&gt;-1 x a = -1 x a     | 0 + a = a&lt;br /&gt;-1 x a = 0 + -1 x a | a - b = a + -b&lt;br /&gt;-1 x a = 0 - 1 x a  | 1 x a = a&lt;br /&gt;-1 x a = 0 - a      | a - b = a + -b&lt;br /&gt;-1 x a = 0 + -a     | 0 + a = a&lt;br /&gt;-1 x a = -a&lt;br /&gt;&lt;br /&gt;-1 x a = -a   | -a = -(a)&lt;br /&gt;-1 x a = -(a)&lt;br /&gt;&lt;br /&gt;-a x b = -a x b     | -1 x a = -a&lt;br /&gt;-a x b = -1 x a x b | a x b = b x a&lt;br /&gt;-a x b = a x -1 x b | -1 x a = -a&lt;br /&gt;-a x b = a x -b&lt;br /&gt;&lt;br /&gt;-a x b = -1 x a x b | -1 x a = -(a)&lt;br /&gt;-a x b = -(a x b)&lt;br /&gt;&lt;br /&gt;a x (b - c) = a x (b - c)        | a - b = a + -b&lt;br /&gt;a x (b - c) = a x (b + -c)       | a x (b + c) = a x b + a x c&lt;br /&gt;a x (b - c) = (a x b) + (a x -c) | a x b = b x a&lt;br /&gt;a x (b - c) = (a x b) + (-c x a) | -a x b = -(a x b)&lt;br /&gt;a x (b - c) = (a x b) + -(c x a) | a x b = b x a&lt;br /&gt;a x (b - c) = (a x b) + -(a x c) | a - b = a + -b&lt;br /&gt;a x (b - c) = (a x b) - (a x c)&lt;br /&gt;&lt;br /&gt;a x 0 = a x 0          | 0 = -1 + 1&lt;br /&gt;a x 0 = a x (-1 + 1)   | a x (b + c) = a x b + a x c&lt;br /&gt;a x 0 = a x -1 + a x 1 | a x 1 = a&lt;br /&gt;a x 0 = a x -1 + a     | -1 x a = -a and a x b = b x a &lt;br /&gt;a x 0 = -a + a         | -a + a = 0&lt;br /&gt;a x 0 = 0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now go forth and solve the problems of algebra!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7150087657626062760?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7150087657626062760/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7150087657626062760' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7150087657626062760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7150087657626062760'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/arithmetic.html' title='Arithmetic'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3443761559340044030</id><published>2010-09-08T00:33:00.003+03:00</published><updated>2010-09-08T01:31:41.727+03:00</updated><title type='text'>Links for Tuesday</title><content type='html'>A &lt;a href="http://github.com/rehno-lindeque/Blender-WebGL-exporter"&gt;Blender-WebGL exporter got forked&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://rrrola.wz.cz/downloads.html#effects"&gt;Rrrola's FX&lt;/a&gt;. Mandelboxes create a lot of cool geometry, riding that line between geometric and organic. I wonder if you could build game levels quickly by CSG cutting mandelboxes.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=RsAlYhF0F0A&amp;feature=related"&gt;Some&lt;/a&gt; &lt;a href="http://www.youtube.com/watch?v=QwUflYfCDq4&amp;feature=related"&gt;motion&lt;/a&gt; &lt;a href="http://www.youtube.com/watch?v=uG2tV-X5NiQ&amp;feature=related"&gt;graphics&lt;/a&gt; &lt;a href="http://www.youtube.com/watch?v=YS7tsvq6bas&amp;feature=related"&gt;videos&lt;/a&gt; on YouTube.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3443761559340044030?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3443761559340044030/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3443761559340044030' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3443761559340044030'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3443761559340044030'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/links-for-tuesday.html' title='Links for Tuesday'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1107991158525597283</id><published>2010-09-07T16:20:00.007+03:00</published><updated>2010-09-17T00:00:45.097+03:00</updated><title type='text'>WebGL color spaces</title><content type='html'>There's an &lt;a href="http://www.khronos.org/webgl/public-mailing-list/archives/1009/msg00000.html"&gt;interesting&lt;/a&gt; &lt;a href="http://www.khronos.org/webgl/public-mailing-list/archives/1009/msg00051.html"&gt;discussion&lt;/a&gt; happening on the public-webgl mailing list about the color spaces used by the textures and the canvas.&lt;br /&gt;&lt;br /&gt;As far as I can tell, the problem is:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Simple math works correctly only in a linear color space.&lt;/li&gt;&lt;li&gt;Images and the rest of web content is 8 bits per channel sRGB.&lt;/li&gt;&lt;li&gt;Converting between two 8-bit color spaces is lossy.&lt;/li&gt;&lt;li&gt;You can't tell what an image's color space is from JavaScript.&lt;/li&gt;&lt;li&gt;You can't easily convert between color spaces in JavaScript.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The proposed solutions are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Pass image data as-is to WebGL, treat WebGL canvas values as sRGB. (I.e. the do-nothing solution. If application writers want correct math, they should use linear textures and do a final linear-to-sRGB pass on the rendered image.)&lt;/li&gt;&lt;li&gt;Optionally convert textures to linear color space, treat WebGL canvas as linear. (Problematic as there is severe information loss at the conversions. Math works correctly though.)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Color spaces? But I thought space was black?&lt;/h3&gt;&lt;br /&gt;Well, actually, space has no color. It's transparent. It just looks black because there's very little light coming through it at most points.&lt;br /&gt;&lt;br /&gt;Color spaces, on the other hand, are definitions of what the numerical pixel values in an image mean. A linear RGB color space means something like "given an output device with three component emitters at wavelengths R=580 nm, G=490 nm and B=440 nm, the number of photons emitted by an emitter is Max_Output * V, where V = component_value / (2^component_bits-1)." For sRGB, the component values are transformed by the &lt;a href="http://en.wikipedia.org/wiki/SRGB_color_space"&gt;sRGB curve&lt;/a&gt; (roughly pow(V, 2.2)) to have more control over the important parts of the dynamic range. In particular, sRGB sacrifices precision in the light end to have more control in the darks. Converting linear-&gt;sRGB loses detail in the light end, whereas sRGB-&gt;linear loses detail in the dark end. &lt;br /&gt;&lt;br /&gt;To project an image onto your eyeball, the image data goes through the following wringer: Image data in image color space -&gt; Viewer app color space -&gt; Window system color space -&gt; Display device color space -&gt; Hardware blinkenlichts -&gt; Eyeball.&lt;br /&gt;&lt;br /&gt;Now, most software goes "huh?" at the mention of that and acts as if the image data is in the window system color space and uses the data directly. Further, the assumption when doing image editing operations is that the aforementioned color space is linear. (Why that might cause problems is that while 0.25 + 0.25 has the same luminance as 0.5 in a linear color space, to add the luminances in gamma 2.2 you have to do (0.25^2.2 + 0.25^2.2)^(1/2.2) = 0.34.)&lt;br /&gt;&lt;br /&gt;Software that requires accurate color handling usually converts the 8-bit values to 16 or 32-bit linear color space to get around the conversion errors and to get easy correct math internally. The resulting high-depth image is then converted down to window system color space for viewing.&lt;br /&gt;&lt;br /&gt;The real solution is to use 64 bits per channel and specify the channels to mean photon count / second / mm^2. With that, you can crank from 1 photon / second all the way up to 6500x noon sunlight. And linear math. Then use that as the interchange format between everything. Heyo, massive framebuffers~&lt;br /&gt;&lt;br /&gt;In the meantime, maybe the browser could have an iconv-like system for converting images from one color space to another. WebGL 1.0 doesn't have float textures, so upconverting to linear float textures has to wait for the float texture extension.&lt;br /&gt;&lt;br /&gt;With linear float textures, you could upconvert all images to float textures, then use a linear float FBO for rendering, and finally convert the FBO to sRGB to display on the canvas element.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1107991158525597283?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1107991158525597283/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1107991158525597283' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1107991158525597283'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1107991158525597283'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/webgl-color-spaces.html' title='WebGL color spaces'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-821648771992557059</id><published>2010-09-06T23:09:00.006+03:00</published><updated>2010-09-07T03:57:59.411+03:00</updated><title type='text'>Ramble on sorting</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Merge_sort"&gt;Merge sort&lt;/a&gt; merges already sorted regions together. It also has an operation to turn a small region into a sorted region. The merge sort splits its input into small regions, turns each of those into a sorted region, and finally merges them together.&lt;br /&gt;&lt;br /&gt;There are some sorted regions that have a trivial merge operation: if the largest value in region X is smaller or equal to the smallest value in region Y, the merge operation is the concatenation X ++ Y. We can pre-process the input of the merge sort so that the two merged regions have this property. The pre-processing pass uses a pivot value and moves all values smaller than the pivot to the start of the region, leaving the end of the region for values larger than the pivot. Now you don't have to do anything to merge the regions together once they're sorted. This is the idea behind the &lt;a href="http://en.wikipedia.org/wiki/Quicksort"&gt;quicksort&lt;/a&gt; algorithm.&lt;br /&gt;&lt;br /&gt;Because pivoting can cause the regions to have different sizes, there is a worst-case scenario where the pivot is the only element in the other region. Because of this scenario, quicksort has O(n^2) worst-case time complexity. If you pick the pivot to be the median of the region with an O(n) &lt;a href="http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm"&gt;median finding algorithm&lt;/a&gt; and further split the equal-to-pivot values equally between the subregions, you can get an O(n lg n) quicksort (but the O(n) median finding algorithm has a high overhead, so they say the resulting algorithm is slower than quicksort in the average case).&lt;br /&gt;&lt;br /&gt;You can do a O(n) pre-processing pass over the input data to find all already sorted regions in the input data. If the number of found regions is small, you're dealing with nearly sorted data. You can directly merge all sorted regions that are larger than the region size for the region sorting operation. The merging should be done so that the smallest regions are merged together first. Merging regions of unequal size causes the worst-time complexity to increase (merging a 2-region and a X-region takes X+2 time, consider N/4 2-regions merged into a N/2-region: it takes N/4 * (N/2 + (2+N) / 2) time, which is in O(N^2).)&lt;br /&gt;&lt;br /&gt;The pre-processing pass can also reverse regions that are sorted in the opposite direction. And it is probably possible to topologically sort the sorted region descriptors by their min-max-values to find regions that can be merged by concatenation. Another merge trick would be to use binary search on one region to find the span that the other region overlaps, then you could get away with &lt;code&gt;memcpy(dst,smaller,smaller_sz); merge(dst+smaller_sz,overlap,region2,overlap_sz,region2_sz); memcpy(dst+smaller_sz+overlap_sz+region2_sz, larger, larger_sz)&lt;/code&gt;. &lt;br /&gt;&lt;br /&gt;If you keep track of how equally the quicksort pivot pass splits a region, you can avoid the quicksort worst-case by handing pathological splits over to merge sort. That is a bit nasty though, as the resulting regions no longer have the concatenative property and must be merged the slow way. Another alternative is to do something like &lt;a href="http://en.wikipedia.org/wiki/Introsort"&gt;introsort&lt;/a&gt; and switch over to heapsort once recursion depth exceeds lg n.&lt;br /&gt;&lt;br /&gt;Once merge sort or quicksort reaches the maximum unsorted region size (say, 8 elements), implementations usually switch over to a low-overhead O(n^2) sorting algorithm like &lt;a href="http://en.wikipedia.org/wiki/Insertion_sort"&gt;insertion sort&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;The partitioning operation of merge sort is a no-op, but the partitioning operation in quicksort takes O(n) time. In the same fashion, the merge operation of quicksort is a no-op, but the merge operation of merge sort takes O(n) time. &lt;br /&gt;&lt;br /&gt;You can do the merge sort merge in parallel by picking a pivot and splitting the sorted regions into two regions: one with values smaller or equal to the pivot and the other with values larger or equal to the pivot. The splitting operation consists of finding the index of the pivot in each region using a binary search. You then merge the small values with the small values and the large values with the large values. The merge between the merged smalls and larges is concatenation, so you can just allocate an array with the beginning reserved for the small values and the end reserved for the large values, and run the small and large merges in parallel. Recurse to increase parallelism. The nice thing about this approach is that you get segregated in-place parallelism, each of the sub-partitions can write to its own part of the root target array. The problem it suffers from is the quicksort partitioning problem: the pivot should be the median of the data set. Because the sorted regions are sorted, we can get the medians of each region easily enough, the worst case with one of those should be a 25:75-split when the regions are of equal size.&lt;br /&gt;&lt;br /&gt;If your input data is 256 elements long and you split it into 8-element insertion-sorted regions, executing each in parallel (on a 32-core), what's the merge time? The first merge level you want to split into 2-way parallel merges (16 merges -&gt; 32 merges). The second level is split 4-way (8 merges -&gt; 32 merges), then 8-way and 16-way? The split bsearch overhead is 2 log&lt;sub&gt;2&lt;/sub&gt; mergeSize per level (sub-splits execute in parallel).&lt;br /&gt;&lt;br /&gt;To do the 16-way split (assuming we get a perfectly balanced split), we first do a 2 log&lt;sub&gt;2&lt;/sub&gt; 128 = 14 op split, then 2 log&lt;sub&gt;2&lt;/sub&gt; 64 = 12 op split, then 10 op and 8 op, for total split cost of 44 ops. The merge then takes 16 ops to run (8+8-merge), for total merge cost of 60 ops. If we stop the split one level short, it'd take 36 ops to split and 32 ops to merge, 68 ops in total. So I guess you do want to split it all the way down if you have negligible overhead.&lt;br /&gt;&lt;br /&gt;The total runtime for the zero-overhead parallel merge tree would be 44 + 30 + 18 + 8 + 4*16 = 164 ops. Total sort time with 64-op 8-elem sorts would be 228 ops. Hey, sub-O(n). Quadruple the input size and the core count and it's 8+18+30+44+60+78+6*16 + 64 = 398 ops. Hey, sub-linear parallel scaling.&lt;br /&gt;&lt;br /&gt;There is an another way to do parallel merging as well (cribbed from &lt;a href="http://www.nku.edu/~foxr/CSC464/ch14.ppt"&gt;some lecture slides&lt;/a&gt;): for regions A and B, write A[i] to i + B.indexOfLarger(A[i]) and write B[i] to i + A.indexOfLarger(B[i]). N-parallelizable, serial O(N log N) time, parallel O(log N) time. However, it relies on scattered writes, so it doesn't seem very memory-access friendly (ideally each CPU core should be working on its own cache line, otherwise you get cache contention and traffic between the CPUs).&lt;br /&gt;&lt;br /&gt;Fun reading: &lt;a href="http://users.aims.ac.za/~mackay/sorting/sorting.html"&gt;Heapsort, Quicksort, and Entropy&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-821648771992557059?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/821648771992557059/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=821648771992557059' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/821648771992557059'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/821648771992557059'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/ramble-on-sorting.html' title='Ramble on sorting'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-270537490377659417</id><published>2010-09-05T23:46:00.002+03:00</published><updated>2010-09-05T23:57:31.774+03:00</updated><title type='text'>Around the world on the ISS</title><content type='html'>&lt;object width="960" height="505"&gt;&lt;param name="movie" value="http://www.youtube.com/v/CyHiMORy9tU?fs=1&amp;amp;hl=en_US&amp;amp;hd=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/CyHiMORy9tU?fs=1&amp;amp;hl=en_US&amp;amp;hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="960" height="505"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-270537490377659417?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/270537490377659417/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=270537490377659417' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/270537490377659417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/270537490377659417'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/around-world-on-iss.html' title='Around the world on the ISS'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3012429405657280037</id><published>2010-09-04T05:50:00.007+03:00</published><updated>2010-09-04T07:05:41.025+03:00</updated><title type='text'>Two's complement integers</title><content type='html'>Oh hey, I finally get how two's complement works after reading the first couple paragraphs of &lt;a href="http://igoro.com/archive/why-computers-represent-signed-integers-using-twos-complement/"&gt;Why computers represent signed integers using two’s complement&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Simply put, adding a negative number to a positive number overflows if the result is positive, and doesn't overflow if the result is negative.&lt;br /&gt;&lt;br /&gt;With 32-bit integers&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;5 - 1 = 5 + (-1) = 5 + (2&lt;sup&gt;32&lt;/sup&gt;-1) = 2&lt;sup&gt;32&lt;/sup&gt;+4 = 4&lt;br /&gt;5 - 3 = 5 + (2&lt;sup&gt;32&lt;/sup&gt;-3) = 2&lt;sup&gt;32&lt;/sup&gt;+2 = 2&lt;br /&gt;5 - 7 = 5 + (2&lt;sup&gt;32&lt;/sup&gt;-7) = 2&lt;sup&gt;32&lt;/sup&gt;-2 = -2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is kinda interesting, we can put the separator between positive and negative numbers where we like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;void print_custom_int (unsigned int n, unsigned int separator)&lt;br /&gt;{&lt;br /&gt;  if (n &amp;lt; separator) {&lt;br /&gt;    printf("%u\n", n);&lt;br /&gt;  } else {&lt;br /&gt;    printf("-%u\n", ~n+1);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;print_custom_int(0, 200);&lt;br /&gt;// 0&lt;br /&gt;print_custom_int(1, 200);&lt;br /&gt;// 1&lt;br /&gt;print_custom_int(-1, 200);&lt;br /&gt;// -1&lt;br /&gt;print_custom_int(200, 200);&lt;br /&gt;// -4294967096&lt;br /&gt;print_custom_int(-4294967097, 200);&lt;br /&gt;// 199&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Or maybe you need extra positive range and only 256 negative values:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;print_custom_int(4000000000, 0xFFFFFF00);&lt;br /&gt;// 4000000000&lt;br /&gt;print_custom_int(2*2004001006, 0xFFFFFF00);&lt;br /&gt;// 4008002012&lt;br /&gt;print_custom_int(-32-80+15, 0xFFFFFF00);&lt;br /&gt;// -97&lt;br /&gt;&lt;br /&gt;// Watch out for overflows!&lt;br /&gt;print_custom_int(-16*17, 0xFFFFFF00);&lt;br /&gt;// 4294967024&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And yeah, division doesn't work out of the box.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;unsigned int div_custom_int (unsigned int a, unsigned int b, unsigned int separator)&lt;br /&gt;{&lt;br /&gt;  if (a &amp;lt; separator &amp;&amp; b &amp;lt; separator)&lt;br /&gt;    return a / b;&lt;br /&gt;  else if (a &amp;lt; separator)&lt;br /&gt;    return ~(a / (~b+1))+1;&lt;br /&gt;  else if (b &amp;lt; separator)&lt;br /&gt;    return ~((~a+1) / b)+1;&lt;br /&gt;  else&lt;br /&gt;    return (~a+1) / (~b+1);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;unsigned int n20 = -20, n3 = -3, p4 = 4, p2 = 2;&lt;br /&gt;&lt;br /&gt;print_custom_int(n20 / p4, 0xFFFFFF00);&lt;br /&gt;// 1073741819&lt;br /&gt;print_custom_int(p4 / n3, 0xFFFFFF00);&lt;br /&gt;// 0&lt;br /&gt;print_custom_int(p4 / p2, 0xFFFFFF00);&lt;br /&gt;// 2&lt;br /&gt;print_custom_int(n20 / n3, 0xFFFFFF00);&lt;br /&gt;// 0&lt;br /&gt;&lt;br /&gt;// vs.&lt;br /&gt;&lt;br /&gt;print_custom_int(div_custom_int(n20, p4, 0xFFFFFF00), 0xFFFFFF00);&lt;br /&gt;// -5&lt;br /&gt;print_custom_int(div_custom_int(p4, n3, 0xFFFFFF00), 0xFFFFFF00);&lt;br /&gt;// -1&lt;br /&gt;print_custom_int(div_custom_int(p4, p2, 0xFFFFFF00), 0xFFFFFF00);&lt;br /&gt;// 2&lt;br /&gt;print_custom_int(div_custom_int(n20, n3, 0xFFFFFF00), 0xFFFFFF00);&lt;br /&gt;// 6&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3012429405657280037?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3012429405657280037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3012429405657280037' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3012429405657280037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3012429405657280037'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/twos-complement-integers.html' title='Two&apos;s complement integers'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8084415974329151131</id><published>2010-09-02T01:51:00.002+03:00</published><updated>2010-09-02T01:56:19.682+03:00</updated><title type='text'>The autumn is on its way</title><content type='html'>&lt;a href="http://matt.might.net/articles/productivity-tips-hints-hacks-tricks-for-grad-students-academics/"&gt;Productivity tips, tricks and hacks for academics&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8084415974329151131?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8084415974329151131/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8084415974329151131' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8084415974329151131'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8084415974329151131'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/09/autumn-is-on-its-way.html' title='The autumn is on its way'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7324584261928327130</id><published>2010-08-31T23:31:00.003+03:00</published><updated>2010-09-01T00:18:04.116+03:00</updated><title type='text'>Branching factor generator</title><content type='html'>A function to generate a stream of tree branching factors to approximate a non-integer branching factor.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;branches' x i sum = &lt;br /&gt;  let v = (if sum/i &gt; x then floor x else ceiling x) :: Int in &lt;br /&gt;  v : branches' x (i+1) (sum+fromIntegral v)&lt;br /&gt;&lt;br /&gt;branches n = branches' n 0 0       &lt;br /&gt;&lt;br /&gt;e_branches = branches (exp 1)&lt;br /&gt;e_100000 = take 100000 $ e_branches&lt;br /&gt;average l = sum l / fromIntegral (length l)&lt;br /&gt;average $ map fromIntegral e_100000 == 2.71828&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You could also write the average function as&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;average2 l = snd $ foldl (\(i, avg) e -&gt; (i+1, (avg*(i-1) + e)/i)) (1, 0) l &lt;br /&gt;&lt;br /&gt;average2 $ map fromIntegral e_100000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;even though that is less accurate. You can use a similar trick for the branches' function to use a running average instead of a running sum.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;branches2' x i avg = &lt;br /&gt;  let v = (if avg &gt; x then floor x else ceiling x) :: Int in &lt;br /&gt;  v : branches2' x (i+1) ((avg*(i-1)+fromIntegral v)/i)&lt;br /&gt;&lt;br /&gt;branches2 n = branches2' n 1 0       &lt;br /&gt;average $ map fromIntegral $ take 100000 $ branches2 (exp 1)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To generate a tree of wanted size out of the branching factor list, you'd have a tree generator that eats values off the list and generates tree nodes breadth-first. It is kind of a pain to write.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7324584261928327130?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7324584261928327130/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7324584261928327130' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7324584261928327130'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7324584261928327130'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/branching-factor-generator.html' title='Branching factor generator'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6997317402011835963</id><published>2010-08-31T03:12:00.005+03:00</published><updated>2010-08-31T23:03:13.606+03:00</updated><title type='text'>And some Tuesday links</title><content type='html'>&lt;a href="http://groups.csail.mit.edu/medg/people/psz/Licklider.html"&gt;Man-Computer Symbiosis&lt;/a&gt; by J.C.R. Licklider. Continuing on the HCI vision golden oldies hitlist.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.nasa.gov/mission_pages/hurricanes/archives/2010/h2010_Earl.html"&gt;Twin hurricanes&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/File:House_Dust_Mite.jpg"&gt;A Dust Mite&lt;/A&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6997317402011835963?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6997317402011835963/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6997317402011835963' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6997317402011835963'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6997317402011835963'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/and-some-tuesday-links.html' title='And some Tuesday links'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7685101835452155491</id><published>2010-08-30T20:27:00.002+03:00</published><updated>2010-08-30T20:31:50.214+03:00</updated><title type='text'>Monday links</title><content type='html'>&lt;a href="http://hawaiianlavadaily.blogspot.com/2010/08/month-in-pictures-of-lava-flow.html"&gt;Hawaiian lava flows look amazing&lt;/a&gt; via &lt;a href="http://all-geo.org/highlyallochthonous/"&gt;Highly Allochthonous&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7685101835452155491?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7685101835452155491/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7685101835452155491' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7685101835452155491'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7685101835452155491'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/monday-links.html' title='Monday links'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-2669430921861587808</id><published>2010-08-30T00:36:00.003+03:00</published><updated>2010-08-30T01:14:17.522+03:00</updated><title type='text'>Sunday links</title><content type='html'>Even though it's technically Monday already.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://all-geo.org/highlyallochthonous/2010/08/yellowstone-what-lies-beneath/"&gt;Yellowstone: what lies beneath&lt;/a&gt; via &lt;a href="http://scienceblogs.com/eruptions/"&gt;Eruptions&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.theatlantic.com/magazine/archive/1969/12/as-we-may-think/3881/"&gt;As We May Think&lt;/a&gt; by Vannevar Bush.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://penelope.uchicago.edu/Thayer/E/Roman/Texts/Vitruvius/home.html"&gt;De architectura&lt;/a&gt; by Vitruvius.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.fordham.edu/halsall/ancient/periplus.html"&gt;The Periplus of the Erythraean Sea&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;"&lt;a href="http://www.reshafim.org.il/ad/egypt/a-burnaburiash3.htm"&gt;My work in the houses of the Gods is abundant, and now I have begun an undertaking: Send much gold!&lt;/a&gt;"&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.youtube.com/watch?v=S_d-gs0WoUw"&gt;Video of asteroid discovery&lt;/a&gt;, best viewed in high-def.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://daveslandslideblog.blogspot.com/2010/08/hunza-debris-flow-video.html"&gt;Hunza debris flow video&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-2669430921861587808?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/2669430921861587808/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=2669430921861587808' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2669430921861587808'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2669430921861587808'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/sunday-links.html' title='Sunday links'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-698852193565449765</id><published>2010-08-28T22:23:00.003+03:00</published><updated>2010-08-29T13:12:25.070+03:00</updated><title type='text'>Folding, part 3: parallel folds</title><content type='html'>So, you can do a parallel fold if your folding function is associative.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-- Here i should be the neutral element of f, that is f i x = x.&lt;br /&gt;-- E.g. 0 for + and 1 for *&lt;br /&gt;parFold f i [] = i&lt;br /&gt;parFold f i [x] = f i x&lt;br /&gt;parFold f i xs =&lt;br /&gt;  l `par` r `pseq` f l r&lt;br /&gt;  where l = parFold f i left&lt;br /&gt;        r = parFold f i right&lt;br /&gt;        left, right = splitAt (length xs `div` 2) xs&lt;br /&gt;&lt;br /&gt;-- See how associative functions work right with it&lt;br /&gt;parFold (+) 0 [1..10] == foldl (+) 0 [1..10]&lt;br /&gt;parFold (*) 1 [1..10] == foldl (*) 1 [1..10]&lt;br /&gt;&lt;br /&gt;-- But others have problems&lt;br /&gt;parFold (-) 0 [1..10] /= foldl (-) 0 [1..10]&lt;br /&gt;parFold (/) 1 [1..10] /= foldl (/) 0 [1..10]&lt;br /&gt;&lt;br /&gt;-- As - and / are defined as A + -B and A * 1/B,&lt;br /&gt;-- we can work around these instances&lt;br /&gt;parFold (+) 0 (map negate [1..10]) == foldl (-) 0 [1..10]&lt;br /&gt;parFold (*) 1 (map recip [1..10]) == foldl (/) 1 [1..10]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But for the general case, we need something stronger. Roll in the reduceFold! It splits the foldable list into sublists, folds each sublist, and reduces the subresults into a single value.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;reduceFold reduce f init [] = init&lt;br /&gt;reduceFold reduce f init [x] = f init x&lt;br /&gt;reduceFold reduce f init xs =&lt;br /&gt;  l `par` r `pseq` reduce l r&lt;br /&gt;  where l = reduceFold reduce f init left&lt;br /&gt;        r = reduceFold reduce f init right&lt;br /&gt;        (left, right) = splitAt (length xs `div` 2) xs&lt;br /&gt;&lt;br /&gt;-- Associative operations work as usual&lt;br /&gt;reduceFold (+) (+) 0 [1..10] == foldl (+) 0 [1..10]&lt;br /&gt;reduceFold (*) (*) 1 [1..10] == foldl (*) 1 [1..10]&lt;br /&gt;&lt;br /&gt;-- And we can now combine the subresults in a different fashion&lt;br /&gt;reduceFold (+) (-) 0 [1..10] == foldl (-) 0 [1..10]&lt;br /&gt;reduceFold (*) (/) 1 [1..10] == foldl (/) 1 [1..10]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Let's write some parallel list operations in terms of reduceFold.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;parMap f lst = reduceFold (++) (\l i -&gt; l ++ [f i]) [] lst&lt;br /&gt;parFilter f lst = reduceFold (++) (\l i -&gt; if f i then l++[i] else l) [] lst&lt;br /&gt;parConcat lsts = reduceFold (++) (++) [] lsts&lt;br /&gt;parConcatMap f lst = reduceFold (++) (\l i -&gt; l++(f i)) [] lst&lt;br /&gt;&lt;br /&gt;optLeft (Just x) _ = Just x&lt;br /&gt;optLeft Nothing (Just x) = Just x&lt;br /&gt;optLeft Nothing Nothing = Nothing&lt;br /&gt;&lt;br /&gt;optFilter f i = if f i then Just i else Nothing&lt;br /&gt;&lt;br /&gt;parFind f lst = reduceFold optLeft (\l i -&gt; &lt;br /&gt;  optLeft l (optFilter f i)) Nothing lst&lt;br /&gt;&lt;br /&gt;optRight x y = optLeft y x&lt;br /&gt;&lt;br /&gt;parFindLast f lst = reduceFold optRight (\l i -&gt; &lt;br /&gt;  optRight l (optFilter f i)) Nothing lst&lt;br /&gt;&lt;br /&gt;-- Yeah, they pretty much work&lt;br /&gt;divisibleBy n x = x `mod` n == 0&lt;br /&gt;&lt;br /&gt;parMap (+ 10) [1..10] == [11..20]&lt;br /&gt;parFilter (divisibleBy 3) [1..15] == [3,6,9,12,15]&lt;br /&gt;parConcat [[1], [2,3,4], [5,6,7], [8,9], [10..20]] == [1..20]&lt;br /&gt;parConcatMap (\x -&gt; [x,x,x]) [1..3] == [1,1,1,2,2,2,3,3,3]&lt;br /&gt;parFind (divisibleBy 127) [90,93..900] == Just 381&lt;br /&gt;parFind (&gt; 10) [1..10] == Nothing &lt;br /&gt;parFindLast (divisibleBy 127) [90,93..900] == Just 762&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You might've noticed that both parFold and reduceFold are creating a parallel evaluation spark for each element in the lists. This is not a very good idea as the sparking overhead is often going to dominate the execution time and kill performance. &lt;br /&gt;&lt;br /&gt;So we need some way to control the reduce tree branching factor and clump up the leaf-level folds into longer operations. I don't know if I can manage to come up with an automagic or even semi-automagic way to do this. I would like to use yesterday's theoretical math as guidance for the tree shape optimizer. Maybe we'll find out in the next installment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-698852193565449765?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/698852193565449765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=698852193565449765' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/698852193565449765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/698852193565449765'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/folding-part-3-parallel-folds.html' title='Folding, part 3: parallel folds'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-9067751130769155502</id><published>2010-08-28T16:12:00.006+03:00</published><updated>2010-08-28T16:22:22.899+03:00</updated><title type='text'>A small hill-climb optimizer</title><content type='html'>&lt;pre&gt;&lt;br /&gt;hillClimb f current delta error 0 = current&lt;br /&gt;hillClimb f current delta error steps | delta &amp;lt; error = current&lt;br /&gt;hillClimb f current delta error steps =&lt;br /&gt;  if curVal &gt; prevVal &amp;&amp; curVal &gt; nextVal&lt;br /&gt;    then hillClimb f current (delta / 2) error (steps-1)&lt;br /&gt;    else if prevVal &gt; nextVal&lt;br /&gt;           then hillClimb f (current-delta) delta error (steps-1)&lt;br /&gt;           else hillClimb f (current+delta) delta error (steps-1)&lt;br /&gt;  where curVal = f current&lt;br /&gt;        prevVal = f (current-delta)&lt;br /&gt;        nextVal = f (current+delta)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;May have bugs in it, only tested on (\x -&gt; -(x**2)).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-9067751130769155502?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/9067751130769155502/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=9067751130769155502' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/9067751130769155502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/9067751130769155502'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/small-hill-climb-optimizer.html' title='A small hill-climb optimizer'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8594914760971671137</id><published>2010-08-27T21:09:00.006+03:00</published><updated>2010-08-28T05:35:47.697+03:00</updated><title type='text'>Folding, part 2: parallel reduction math</title><content type='html'>Parallel folding is a bit of an oxymoron. The fold operation sequentially builds up an accumulator value from the elements of a data structure. So if it's inherently sequential, how can it be parallelized?&lt;br /&gt;&lt;br /&gt;If the reduce function given to fold is associative, the fold can be parallelized. If it's not associative, the fold is sequential.&lt;br /&gt;&lt;br /&gt;Associativity means that you can turn &lt;code&gt;(a + b) + c&lt;/code&gt; into &lt;code&gt;a + (b + c)&lt;/code&gt;, or, in function call terms, &lt;code&gt;(f (f a b) c) == (f a (f b c))&lt;/code&gt;. When you have a long call chain like &lt;code&gt;a + b + c + d + e + f + g + h&lt;/code&gt;, it means that you can split it into &lt;code&gt;(a + b) + (c + d) + (e + f) + (g + h)&lt;/code&gt;, evaluate the parenthesized bits in parallel, and then reduce the results to the final value. The reduction can also be parallelized, as it is the fold &lt;code&gt;(a_b + c_d) + (e_f + g_h)&lt;/code&gt;. This sort of parallel tree reduction is what map-reduce is all about btw: compute a bunch of values in parallel and reduce them to the result value.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Where it all goes by the wayside&lt;/h3&gt;&lt;br /&gt;Let's do the math to figure out the theoretical runtime for a reduction network. One parallel reduction step takes &lt;code&gt;computeTime&lt;/code&gt; time. Moving the result to the next reduce node takes &lt;code&gt;totalResultSize / bandwidth&lt;/code&gt; time. The variable &lt;code&gt;totalResultSize&lt;/code&gt; is defined as &lt;code&gt;resultSize * branchingFactor&lt;/code&gt;, as the branching factor is the amount of nodes sending their reduce results to the reduce node. We can also split it into a more useful form &lt;code&gt;branchingFactor * (resultSize / bandwidth)&lt;/code&gt; and write &lt;code&gt;transferTime = resultSize / bandwidth&lt;/code&gt; Finally, the amount of parallel reduction steps required to reduce a tree of size &lt;code&gt;N&lt;/code&gt; is &lt;code&gt;log_branchingFactor N&lt;/code&gt; (and &lt;code&gt;1 &amp;lt; branchingFactor &amp;lt;= N&lt;/code&gt;).&lt;br /&gt;&lt;br /&gt;Putting it all together, we get something like&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;treeReduceTime n branchingFactor computeTime transferTime =&lt;br /&gt;  stepCount * stepTime&lt;br /&gt;  where stepTime = computeTime + (branchingFactor * transferTime)&lt;br /&gt;        stepCount = if branchingFactor == 1.0&lt;br /&gt;                      then n&lt;br /&gt;                      else log n / log branchingFactor&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From the above we can come up with a couple of generalizations:&lt;br /&gt;&lt;br /&gt;If computeTime is O(1) with regard to branchingFactor and you have infinite bandwidth or zero result size (both mean that transferTime is zero), you want a branching factor of &lt;code&gt;N&lt;/code&gt;. That's easy enough to see:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;With infinite bandwidth, the treeReduceTime is&lt;br /&gt;  (computeTime + (branchingFactor * 0)) + stepCount =&lt;br /&gt;  computeTime * stepCount&lt;br /&gt;&lt;br /&gt;To minimize the above, we need to minimize stepCount.&lt;br /&gt;The smallest stepCount we can have is 1.&lt;br /&gt;The stepCount is 1 when branchingFactor is N.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you have infinitely fast compute nodes (computeTime = 0), you want a branching factor of &lt;code&gt;e&lt;/code&gt;. That's a bit tougher to prove&lt;a href="#fold-inf-comp"&gt;[1]&lt;/a&gt; (or maybe I'm just lacking some essential bit of knowledge that would make the proof easier.)&lt;br /&gt;&lt;br /&gt;Note that it's highly likely that &lt;code&gt;computeTime&lt;/code&gt; depends on &lt;code&gt;branchingFactor&lt;/code&gt;. If the reduce algorithm is O(n), &lt;code&gt;computeTime = branchingFactor * elementComputeTime&lt;/code&gt;, and in that case the optimum branching factor is &lt;code&gt;e&lt;/code&gt; (as elementComputeTime and transferTime have the same factor, they can be rolled together and the infinitely fast compute nodes proof applies). For O(n^2) algorithms, the optimal branching factor seems to be &lt;code&gt;sqrt(e)&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;For O(1) computeTime, finite computational power and non-zero transfer time, the optimal branching factor is somewhere between &lt;code&gt;e&lt;/code&gt; and &lt;code&gt;N&lt;/code&gt;. I used a simple hill-climb optimizer to find optimal branching factors for different computation/transfer ratios, and they do seem to be independent of N (well, apart from the maximum branching factor). For 1:1 compute time : transfer time, the branching factor is around 3.6. For 1:10 (low bandwidth), the branching factor is a bit over 2.8. For 10:1 (slow compute), the branching factor is about 8.6. Here's some approximate ratios for integer branching factors: 3 -&gt; 1:3.5, 4 -&gt; 1.5:1, 5 -&gt; 3:1, 6 -&gt; 5:1, and for 1000:1 the factor is about 226.&lt;br /&gt;&lt;br /&gt;The above approximations can be used for branching factor -dependent computation times too. You do that by replacing the computation time with per-reduce-step overhead and combining computeTime in transferTime. The new ratio is &lt;code&gt;overhead : (computeTime+transferTime)&lt;/code&gt;. For intuition on this: If the overhead dominates, you want more branching as that will decrease the contribution of the overhead. If you have very little overhead, you want a branching factor close to &lt;code&gt;e&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;For an example, consider an email tournament that aims to pick the best email out of ten thousand. You receive some fixed amount of emails and your task is to pick the best one, then forward it to your assigned receiver (who does the same, etc., and the last reducer sends the mail to the award ceremony). Let's estimate that the transfer time is 5 seconds, the computing time is a minute per email, and the fixed overhead is 8 hours. This gives us an overhead:processing-ratio of 443. If each receiver processes 10 emails per session, running the tournament would take 33 hours. But if each receiver does 118 emails a go, the tournament would be done in 19.5 hours. And if they do 200 emails, it'd take ... 20.2 hours. (Yeah, 118 is the best branching factor here.)&lt;br /&gt;&lt;br /&gt;Suppose you do the same tournament but with people glued to their mail apps. Now the overhead might be just one minute. This time, doing the tournament with 118 mails per session would take a snappy 4.1 hours. But as the role of overhead has fallen, we should reduce the branching factor as well. With 4 mails per session, the tournament would take only 35 minutes. (With 2 mails per session, 42 minutes. 4 is optimal.)&lt;br /&gt;&lt;br /&gt;&lt;a name="fold-inf-comp"&gt;&lt;/a&gt;&lt;pre&gt;&lt;br /&gt;With infinitely fast compute nodes, the treeReduceTime is&lt;br /&gt;  (branchingFactor * transferTime) * stepCount&lt;br /&gt;&lt;br /&gt;For a branching factor of 1, that equals&lt;br /&gt;  transferTime * n&lt;br /&gt;&lt;br /&gt;For other branching factors, the equation is&lt;br /&gt;  b * t * log_b(n), where b = branchingFactor and t = transferTime&lt;br /&gt;&lt;br /&gt;From which we see that t is a fixed factor,&lt;br /&gt;so the minimum of the above equation is the minimum of the equation&lt;br /&gt;  b * log_b(n)&lt;br /&gt;&lt;br /&gt;Which can be written as&lt;br /&gt;  log_b n = log n / log b,  b &gt; 1&lt;br /&gt;  b * log_b(n) =&lt;br /&gt;  b * (log n / log b) =&lt;br /&gt;  (b / log b) * log n&lt;br /&gt;&lt;br /&gt;From which we see that log n is a fixed factor,&lt;br /&gt;so the minimum of b * t * log_b(n) is the minimum of&lt;br /&gt;  b / log b&lt;br /&gt;&lt;br /&gt;To find the minimum, find the derivative&lt;br /&gt;  D(b/log b) = (1*log b - b*1/b) / (log b)^2&lt;br /&gt;             = (log b - 1) / (log b)^2&lt;br /&gt;&lt;br /&gt;And its inflection point at b = e,&lt;br /&gt;  (log e - 1) / (log e)^2 = (1 - 1) / 1^2 = 0 / 1 = 0,&lt;br /&gt;we check the values of b / log b on both sides of the&lt;br /&gt;inflection point to be larger than the value at the inflection point,&lt;br /&gt;  e / log e = e =~ 2.718&lt;br /&gt;  2 / log 2 =~ 2.885&lt;br /&gt;  3 / log 3 =~ 2.731,&lt;br /&gt;thus confirming it as a local minimum.&lt;br /&gt;&lt;br /&gt;Checking the discontinuity of b / log b at b = 1,&lt;br /&gt;  b / 0 = infinity,&lt;br /&gt;and discovering it to be larger than e / log e,&lt;br /&gt;we find the global minimum of b / log b to lie at b = e.&lt;br /&gt;&lt;br /&gt;Plugging that back into the original equation:&lt;br /&gt;  minimum of branchingFactor * transferTime * stepCount is&lt;br /&gt;    transferTime * min(n, e*ln(n))&lt;br /&gt;  and because n &gt;= e*ln(n), the minimum is&lt;br /&gt;    transferTime * e * ln(n).&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8594914760971671137?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8594914760971671137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8594914760971671137' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8594914760971671137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8594914760971671137'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/folding-part-2-parallel-reduction-math.html' title='Folding, part 2: parallel reduction math'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1255418484691272958</id><published>2010-08-26T16:03:00.003+03:00</published><updated>2010-08-26T17:23:32.356+03:00</updated><title type='text'>Folding, part 1</title><content type='html'>The fold function is a data structure accumulator: it goes through every element in the data structure and builds up an accumulator value from them. An example for folding lists:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;foldl f acc [] = acc&lt;br /&gt;foldl f acc (x:xs) = foldl f (f acc x) xs&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With a fold you can do maps and filters of different kinds. Here's a few examples:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-- id x = x&lt;br /&gt;reverseMap f lst = foldl (\l i -&gt; (f i):l) [] lst&lt;br /&gt;reverse = reverseMap id&lt;br /&gt;map f lst = reverse (reverseMap f lst)&lt;br /&gt;&lt;br /&gt;filter f lst = &lt;br /&gt;  reverse (foldl (\l i -&gt; &lt;br /&gt;    if f i &lt;br /&gt;      then i:l &lt;br /&gt;      else l&lt;br /&gt;  ) [] lst)&lt;br /&gt;&lt;br /&gt;concat a b = foldl (\l i -&gt; i:l) b (reverse a)&lt;br /&gt;&lt;br /&gt;concatMap f lst = foldl (\l i -&gt; concat l (f i)) [] lst&lt;br /&gt;&lt;br /&gt;triplicate lst = concatMap (\i -&gt; [i,i,i]) lst&lt;br /&gt;&lt;br /&gt;drop n lst = &lt;br /&gt;  reverse $ snd $ foldl (\(c,l) i -&gt;&lt;br /&gt;    if c &gt;= n &lt;br /&gt;      then (c, i:l) &lt;br /&gt;      else (c+1, l)) (0,[]) lst&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You can also do find operations with fold, but they're not as efficient as you'd like, because the fold goes through all the elements in the list. To make find efficient, we need a fold with break.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;findInefficient f lst = &lt;br /&gt;  foldl (\e i -&gt; &lt;br /&gt;    case e of &lt;br /&gt;      Nothing -&gt; (if f i then Just i else e)&lt;br /&gt;      Just x -&gt; e) Nothing lst&lt;br /&gt;&lt;br /&gt;data Break a = Break a | Continue a&lt;br /&gt;&lt;br /&gt;foldlWithBreak f acc [] = acc&lt;br /&gt;foldlWithBreak f acc (x:xs) = &lt;br /&gt;  case f acc x of&lt;br /&gt;    Break a -&gt; a&lt;br /&gt;    Continue a -&gt; foldlWithBreak f a xs&lt;br /&gt;&lt;br /&gt;find f lst = &lt;br /&gt;  foldlWithBreak (\e i -&gt; &lt;br /&gt;    if f i &lt;br /&gt;      then Break (Just i) &lt;br /&gt;      else Continue Nothing) Nothing lst&lt;br /&gt;&lt;br /&gt;take n lst = &lt;br /&gt;  reverse $ snd $ foldlWithBreak (\(c, l) i -&gt; &lt;br /&gt;    if c &gt;= n &lt;br /&gt;      then Break (c,l)&lt;br /&gt;      else Continue (c+1, i:l)) (0, []) lst&lt;br /&gt;&lt;br /&gt;any f lst = &lt;br /&gt;  -- maybe False (const True) (find f lst)&lt;br /&gt;  case find f lst of&lt;br /&gt;    Just x -&gt; True&lt;br /&gt;    Nothing -&gt; False&lt;br /&gt;&lt;br /&gt;all f lst = &lt;br /&gt;  -- not $ any (not.f) lst&lt;br /&gt;  not (any (\x -&gt; not (f x)) lst)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And that's it for today. Tomorrow, parallel folding.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1255418484691272958?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1255418484691272958/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1255418484691272958' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1255418484691272958'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1255418484691272958'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/folding-part-1.html' title='Folding, part 1'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8386863990524971507</id><published>2010-08-26T01:14:00.005+03:00</published><updated>2010-08-26T01:23:44.797+03:00</updated><title type='text'>FormData file uploads</title><content type='html'>&lt;a href="http://dev.w3.org/2006/webapi/XMLHttpRequest-2/Overview.html"&gt;XMLHttpRequest Level 2&lt;/a&gt; &lt;a href="http://hacks.mozilla.org/2010/05/formdata-interface-coming-to-firefox/"&gt;FormData&lt;/a&gt; is super. Loving it.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;var fd = new FormData();&lt;br /&gt;fd.append("key", "value");&lt;br /&gt;var files = dropEvent.dataTransfer.files;&lt;br /&gt;for (var i=0; i&amp;lt;files.length; i++)&lt;br /&gt;  fd.append("file", files[i]);&lt;br /&gt;&lt;br /&gt;var xhr = new XMLHttpRequest();&lt;br /&gt;xhr.open("POST", "/upload");&lt;br /&gt;xhr.send(fd);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8386863990524971507?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8386863990524971507/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8386863990524971507' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8386863990524971507'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8386863990524971507'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/formdata-file-uploads.html' title='FormData file uploads'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-247847018047286722</id><published>2010-08-25T02:31:00.002+03:00</published><updated>2010-08-25T03:45:05.174+03:00</updated><title type='text'>sse.h update, HTML5 fiddling, more links</title><content type='html'>I updated &lt;a href="http://github.com/kig/correlate_opencl/blob/master/sse.h"&gt;sse.h&lt;/a&gt; with license info (MIT) and added software implementations of recip() and rsqrt() to double2 and double4. This &lt;a href="http://compeng.uni-frankfurt.de/index.php?id=vc"&gt;Vector Classes&lt;/a&gt; library might be more useful for Real Serious Use, though. &lt;br /&gt;&lt;br /&gt;Currently fiddling with HTML5 media tags (cursed be audio and video codecs. If it were possible to implement media codecs in JS, that would be a solution.) Also doing drag-and-drop file uploads. The event API is ... interesting. To receive a drop event, you need to preventDefault other drag-related events.&lt;br /&gt;&lt;br /&gt;Failures are strengths.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Have some links to go:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://scienceblogs.com/eruptions/2010/08/a_request_from_me.php#comments"&gt; Eruptions blog readership&lt;/a&gt;. &lt;br /&gt;&lt;a href="http://earthobservatory.nasa.gov/NaturalHazards/view.php?id=42730"&gt;Activity at Sakurajima Volcano Intensifies&lt;/a&gt;.&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Region-based_memory_management"&gt;Region-based memory management&lt;/a&gt;, &lt;a href="http://www.itu.dk/research/mlkit/index.php/Main_Page"&gt;ML Kit&lt;/a&gt;, &lt;a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.26.912"&gt;A Simplified Account of Region Inference&lt;/a&gt;, &lt;a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.18.6623"&gt;Combining Region Inference and Garbage Collection&lt;/a&gt;, &lt;a href="http://www.cs.rit.edu/~mtf/research/safe-runtime/index.html"&gt;A Safe Runtime System in Cyclone&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-247847018047286722?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/247847018047286722/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=247847018047286722' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/247847018047286722'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/247847018047286722'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/sseh-update-html5-fiddling-more-links.html' title='sse.h update, HTML5 fiddling, more links'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6331272461743237714</id><published>2010-08-23T23:43:00.004+03:00</published><updated>2010-08-24T00:06:11.957+03:00</updated><title type='text'>A new blog post!</title><content type='html'>No, wait, sorry, just more link copypasta. My bad.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Join,_or_Die"&gt;Join, or Die&lt;/a&gt;. Benjamin Franklin, truly a master of Perl. Via &lt;a href="http://www.harkavagrant.com/index.php?id=277"&gt;Hark, a vagrant&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://earthobservatory.nasa.gov/NaturalHazards/event.php?id=45116"&gt;Ice Island Calves off Petermann Glacier&lt;/a&gt;, via &lt;a href="http://blogs.discovermagazine.com/badastronomy/2010/08/23/enormous-glacier-calves-in-largest-arctic-event-seen-in-48-years/"&gt;Bad Astronomy&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6331272461743237714?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6331272461743237714/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6331272461743237714' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6331272461743237714'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6331272461743237714'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/new-blog-post.html' title='A new blog post!'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-613020111095058521</id><published>2010-08-22T23:25:00.004+03:00</published><updated>2010-08-22T23:50:42.654+03:00</updated><title type='text'>Yet more random links</title><content type='html'>&lt;a href="http://www.esa.int/esaEO/SEM09F5OJCG_index_0.html"&gt;Electric blue planktom blooms off Ireland&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://arxiv.org/pdf/1008.2437v1"&gt;Employer Expectations, Peer Effects and Productivity: Evidence from a Series of Field Experiments [PDF]&lt;/a&gt;. A study using Amazon Mechanical Turk, one finding was that peers reward effort rather than output quality. Quote from the implications section: "For example, it may be difficult to get workers to substitute easy, correct procedures for difficult, inefficient procedures. Ironically, the difficulty itself might make an outdated procedure harder to replace, as workers who adopt the easier method might be perceived to be shirking. The finding that exposure to low-output work lowers output, combined with the finding that low-productivity reduces willingness to punish, suggests the possibility of an organizational vicious cycle: after observing idiosyncratically bad work, workers may lower their own output and punish less in response, in turn reducing other workers’ incentives to be highly productive."&lt;br /&gt;&lt;br /&gt;&lt;a href="http://nordicbirdstrike.com/App8Helsinki.pdf"&gt;Forecasting bird migration for the Finnish Air Force&lt;/a&gt;. "Why? Bird is a threat."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-613020111095058521?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/613020111095058521/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=613020111095058521' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/613020111095058521'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/613020111095058521'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/yet-more-random-links.html' title='Yet more random links'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8464676887379730385</id><published>2010-08-22T01:47:00.004+03:00</published><updated>2010-08-22T02:56:16.534+03:00</updated><title type='text'>Random links for another day</title><content type='html'>&lt;a href="http://webcache.googleusercontent.com/search?q=cache:aZUO6TBjMdkJ:www.ndsl.kaist.edu/papers/packetshader.pdf&amp;cd=2&amp;hl=en&amp;ct=clnk&amp;gl=uk"&gt;PacketShader: a GPU-Accelerated Software Router&lt;/a&gt; - eeh? &lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.planetary.org/blog/article/00002624/"&gt;Enceladus plumes&lt;/a&gt; with anigifs. Cassini just keeps taking these awesome shots of the Saturn system.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://commons.wikimedia.org/wiki/File:Kirkenes_Hafen.jpg"&gt;Kirkenes harbour&lt;/a&gt; (and googling with the name of the ship got me &lt;a href="http://upload.wikimedia.org/wikipedia/commons/2/2b/Coat_of_Arms_of_Birsk_(Bashkortostan).png"&gt;this&lt;/a&gt;. Take &lt;a href="http://en.wikipedia.org/wiki/File:Tysfjord_komm.png"&gt;that&lt;/a&gt; for good measure as well.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.skyscrapercity.com/showthread.php?t=195171"&gt;Fantoft stave church&lt;/a&gt;, &lt;a href="http://www.skyscrapercity.com/showthread.php?t=1178331"&gt;Pre-Colombian Mexican architecture&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.soest.hawaii.edu/oceanography/faculty/drazen/fishes.htm"&gt;... the deep-sea food web is fueled by a rain of dead plants and animals from surface waters&lt;/a&gt;. Sediment for the sediment god, let the red clay sink back into the mantle. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://arctic.atmos.uiuc.edu/cryosphere/"&gt;Cryosphere Today&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;That reminds me: We inhabit a few meter thick layer at the solid-gas interface of a 6500 km radius sphere of rock and iron. Go up or down a few meters and you'll die. Go to the solid-liquid or gas-liquid interface and you'll die. Go to the solid, go to the liquid or go to the gas and you'll die. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/File:Bi-crystal.jpg"&gt;Bismuth crystal&lt;/a&gt;. I quite like the &lt;a href="http://www.minrec.org"&gt;Mineralogical Record&lt;/a&gt; print mag. Lots of good pictures.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8464676887379730385?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8464676887379730385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8464676887379730385' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8464676887379730385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8464676887379730385'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/random-links-for-another-day.html' title='Random links for another day'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-142957596104316853</id><published>2010-08-21T01:02:00.007+03:00</published><updated>2010-08-21T02:22:37.571+03:00</updated><title type='text'>Random links of the day</title><content type='html'>&lt;a href="http://events.math.unipd.it/ae2008/material/Wednesday/Use_of_Ada_in_a_Student_CubeSat_Project.pdf"&gt;Use of Ada in a Student CubeSat Project&lt;/a&gt; (via &lt;a href="http://events.math.unipd.it/ae2008/pro.html"&gt;Ada-Europe 2008&lt;/a&gt; via &lt;a href="http://www.altran-praxis.com/sparkConferencePapers.aspx"&gt;SPARK Conference Papers&lt;/a&gt;). First they built some arctic sea ice buoys and then used the same HW/SW-platform to do a CubeSat, in a high-integrity dialect of Ada. (The conference proceedings have some other wacky stuff too, like a presentation about porting a naval command system used in Royal Navy destroyers to Ada 2005. And yes, the slides are basically a long list of stuff that's changed between language versions, so it's not that exciting.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://scienceblogs.com/startswithabang/2010/08/an_18_billion_mile_journey.php"&gt;Neptune about to complete first orbit since discovery&lt;/a&gt;. Discovered in 1846, orbital period 165 years, orbit complete next summer. Celebration? (Via &lt;a href="http://scienceblogs.com/principles/"&gt;Uncertain Principles&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gurneyjourney.blogspot.com/2010/08/gamestoppers.html"&gt;The dangers of plein air&lt;/a&gt;. That reminds me, I've fallen off the drawing routine. Perhaps I should unfall. Also &lt;a href="http://gurneyjourney.blogspot.com/2010/07/totentanz.html"&gt;this&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;"&lt;a href="http://www.gamvik.kommune.no/welcome-to-the-nordkyn-peninsula-and-the-municipallty-of-gamvik.255702-17949.html"&gt;According to the meteorologists, it's never summer in Gamvik&lt;/a&gt;". The coast of the Arctic Sea is a jolly good place. Full of fun and excitement and days that last for months.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-142957596104316853?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/142957596104316853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=142957596104316853' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/142957596104316853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/142957596104316853'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/random-links-of-day.html' title='Random links of the day'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-9010452777029202756</id><published>2010-08-19T13:13:00.006+03:00</published><updated>2010-09-22T18:15:14.872+03:00</updated><title type='text'>Random programming ideas</title><content type='html'>&lt;ul&gt;&lt;li&gt;Programming is about gathering knowledge about a piece of code. What does it do, what are its limitations, how does it perform, where can it be used, what is it doing right now, how do I extend it, how much memory does it use, how do I know that it works right, etc.&lt;/li&gt;&lt;li&gt;Testing, logging, documentation and profiling as an integral part of a language. Parse each function and module into a {body, tests, docs, logstream, profilestream}-struct that can be fiddled with at runtime.&lt;/li&gt;&lt;li&gt;Automatically test a function based on its type. Call it with different args, log the results and summarize detected patterns to give programmer a quick description of what the function does and what its time-space-complexity looks like. Add a way to flag found properties as want/do not want and check that subsequent versions of the function fulfill those (basically autogenerating QuickCheck rules).&lt;/li&gt;&lt;li&gt;Typed UNIX pipes. Use HTTP headers to tell the recipient what's coming through.&lt;/li&gt;&lt;li&gt;Auto-generate type-&gt;type-pipelines by putting conversion functions in a graph, run graph search on it with edge weight given by amount of information lost in each conversion (and secondarily performance).&lt;/li&gt;&lt;li&gt;Semantic type information to encode the effects of a function. Two functions that purport to do the same thing may well have semantic differences: suppose two image scaling functions where one assumes linear color space and the other does gamma correction. Both do ((RGB w h), new_width, new_height) -&gt; (RGB new_width new_height), but the results differ.&lt;/li&gt;&lt;li&gt;Concurrency based on HW-level message passing. Processors read and write in cache lines, cache lines can't be shared between two processors =&gt; use cache lines as messages.&lt;/li&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Array_programming"&gt;Array programming&lt;/a&gt;-like parallel array computation lib. Element-wise operations on N-wide vectors are easy to parallelize and vectorize: divide N by the amount of processors and the SIMD width. Needs to compile array-level pipes (&lt;code&gt;(f . g . h) A&lt;/code&gt;) into element-level pipes (&lt;code&gt;map (f' . g' . h') A&lt;/code&gt;) to maximize arithmetic per memory access. Maybe &lt;a href="http://www.haskell.org/haskellwiki/GHC/Data_Parallel_Haskell"&gt;Data Parallel Haskell&lt;/a&gt; does this stuff already.&lt;/li&gt;&lt;li&gt;Uniform batteries-included interface for different data structures. Yeah, doable with typeclasses. Yeah, something like &lt;a href="http://thelema.github.com/batteries-included/hdoc/BatEnum.html#TYPEt"&gt;OCaml Batteries Included Enums&lt;/a&gt;. You can &lt;a href="http://fhtr.blogspot.com/2008/09/building-ocaml-array-library-from-basic.html"&gt;reduce&lt;/a&gt; data structures to either empty, fold and unfold (for streaming data structures) or alloc, length, get and set (for random access structures).&lt;/li&gt;&lt;li&gt;Data structure -generic regular expressions (i.e. not tied to just strings), are they useful? See &lt;a href="http://gist.github.com/58018"&gt;proof of concept&lt;/a&gt;. What you need is a fold over the data structure (and I guess that most data structures are foldable, how else would you free the memory their elements use?)&lt;/li&gt;&lt;li&gt;Instruction set with bounded &amp; bordered LOADs and STOREs: LOAD reg addr minAddr maxAddr [borderValue]. Throw hardware exception if a bounded LOAD goes out of bounds, load borderValue if a bordered LOAD goes out of bounds. Bordered STORE writes to the address in borderValue instead. Faster than two compares and JMP? More compiler-friendly? Eliminates buffer overflows? (Random literature search: &lt;a href="http://www.emeraldinsight.com/journals.htm?articleid=1630710&amp;show=pdf"&gt;Security improvement in embedded systems via an efficient hardware bound checking architecture&lt;/a&gt;, &lt;a href="http://portal.acm.org/citation.cfm?id=1078297"&gt;Checking Array Bound Violation Using Segmentation Hardware&lt;/a&gt;, &lt;a href="http://www.springerlink.com/content/5ywvhgq6g4qum3em/"&gt;A Comprehensive Approach to Array Bounds Check Elimination for Java&lt;/a&gt;, x86 segmented access mode, some mainframe architectures are said to have something like that, mysteries of life.)&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-9010452777029202756?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/9010452777029202756/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=9010452777029202756' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/9010452777029202756'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/9010452777029202756'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/random-programming-ideas.html' title='Random programming ideas'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5527563220403925578</id><published>2010-08-16T06:08:00.007+03:00</published><updated>2010-08-16T15:39:07.289+03:00</updated><title type='text'>China at #2</title><content type='html'>Hey, PRC thundered past Japan in nominal GDP this year. At this pace, China'll be passing USA and EU in a decade and maybe reach the same GDP per capita by 2040. At which point its annual growth rate will stabilize at around 4% and all the funds expecting 10% will cause a major economic crash, absorbed in part by an ascendant India? I guess the question is how China will transform from a fast-growing implementation economy into a slow-growing research one (IN THE FUTURE!!!1!one).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5527563220403925578?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5527563220403925578/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5527563220403925578' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5527563220403925578'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5527563220403925578'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/china-at-2.html' title='China at #2'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1479860956371400381</id><published>2010-08-10T01:33:00.003+03:00</published><updated>2010-08-10T02:39:46.106+03:00</updated><title type='text'>A slow cache algorithm and a faster one</title><content type='html'>It took around 30 ms to draw some text titles. The weird thing was that not drawing the titles didn't really affect the draw time. So clearly the problem was not in the drawing speed. Investigating, my text bitmap LRU cache was hitting its max size, destroying all possible benefits of caching. But making the cache large enough didn't fix the performance problem. A mystery!&lt;br /&gt;&lt;br /&gt;On closer look, my caching algorithm was retarded:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function makeCache(maxSize) {&lt;br /&gt;  return { array: [], hash: {}, maxSize: maxSize };&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function cacheItem(cache, item) {&lt;br /&gt;  // shift old items off the array&lt;br /&gt;  while (cache.array.length &gt;= cache.maxSize) {&lt;br /&gt;    var it = cache.array.shift();&lt;br /&gt;    delete cache.hash[it.key];&lt;br /&gt;  }&lt;br /&gt;  cache.array.push(item);&lt;br /&gt;  cache.hash[item.key] = item;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// move item with key to the end of cache array&lt;br /&gt;function refresh(cache, key) {&lt;br /&gt;  var it = cache.hash[key];&lt;br /&gt;  var idx = cache.array.indexOf(it);&lt;br /&gt;  cache.array.splice(idx,1);&lt;br /&gt;  cache.array.push(it);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function get(cache, key) {&lt;br /&gt;  refresh(cache, key);&lt;br /&gt;  return cache.hash[key];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Why that is bad: O(n) get, O(n) cacheItem after hitting maxSize. If you're going through the whole cache every frame, that's O(n^2). If you're also at maxSize, double that! Aargh!&lt;br /&gt;&lt;br /&gt;So I rewrote it to be less insane and now the drawing is nice and fast:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function makeCache(maxSize) {&lt;br /&gt;  return { array: [], hash: {}, maxSize: maxSize, lastUsedIndex: 0 };&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function cacheItem(cache, item) {&lt;br /&gt;  // chop off the oldest items after reaching overflow limit&lt;br /&gt;  if (cache.array.length &gt;= cache.maxSize * 2) {&lt;br /&gt;    cache.array.sort(function(a,b){ return a.lastUsed - b.lastUsed; });&lt;br /&gt;    var deleted = cache.array.splice(cache.maxSize);&lt;br /&gt;    for (var i=0; i&amp;lt;deleted.length; i++) {&lt;br /&gt;      var it = deleted[i];&lt;br /&gt;      delete cache.hash[it.key];&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;  cache.array.push(item);&lt;br /&gt;  cache.hash[item.key] = item;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// update the cached item's lastUsed&lt;br /&gt;function refresh(cache, key) {&lt;br /&gt;  cache.hash[key].lastUsed = cache.lastUsedIndex++;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function get(cache, key) {&lt;br /&gt;  refresh(cache, key);&lt;br /&gt;  return cache.hash[key];&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The gets are now O(1) and cacheItem with maxSize cache runs in amortized O((2n log 2n) / n) time. Or something. You could further optimize it by using a split instead of sort. Basically run an O(n) selection algorithm to find the maxSizeth element of the cache array, then use that as a pivot and do a single quicksort partition pass. That'd give you amortized O(2n / n), which is close enough to O(1) :P&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1479860956371400381?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1479860956371400381/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1479860956371400381' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1479860956371400381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1479860956371400381'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/08/slow-cache-algorithm-and-faster-one.html' title='A slow cache algorithm and a faster one'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-260196727523097393</id><published>2010-07-27T00:15:00.006+03:00</published><updated>2010-07-27T01:26:32.236+03:00</updated><title type='text'>Berlin!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_yZA36-WQFmM/TE4GzCLvvnI/AAAAAAAABuI/K-nDpPCccs4/s1600/IMG_5386.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://4.bp.blogspot.com/_yZA36-WQFmM/TE4GzCLvvnI/AAAAAAAABuI/K-nDpPCccs4/s320/IMG_5386.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498339668895186546" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hanging out in Berlin this week, with a shiny new sketchbook to boot.&lt;br /&gt;&lt;br /&gt;Went to the &lt;a href="http://en.wikipedia.org/wiki/Pergamon_Museum"&gt;Pergamonmuseum&lt;/a&gt; on Sunday to be all Ozymandias. The things they have there are huge (indoor display of 15m high pillars, etc.) and pretty old (2-3 thousand years) and beautiful. And messed up in that ancient style of messed-upness. The objects in the Islamic museum side were nicee, superior metalcraft and calligraphy (also a millennium or two younger than the rest of the stuff but who's counting). The Assyrian reliefs had writing going across them, like a textured strip of cuneiform. They had a special display on the coloring of the Greek statues, with brightly colored reproductions of much RGB might.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4GzaNROpI/AAAAAAAABuQ/plsAn8a-4bY/s1600/IMG_5413.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4GzaNROpI/AAAAAAAABuQ/plsAn8a-4bY/s320/IMG_5413.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498339675344026258" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_yZA36-WQFmM/TE4G0If_NSI/AAAAAAAABuY/Ej47EKagX0M/s1600/IMG_5481.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://4.bp.blogspot.com/_yZA36-WQFmM/TE4G0If_NSI/AAAAAAAABuY/Ej47EKagX0M/s320/IMG_5481.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498339687770567970" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4GyoFE64I/AAAAAAAABuA/8Z0GrzuyFJ0/s1600/IMG_5438.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4GyoFE64I/AAAAAAAABuA/8Z0GrzuyFJ0/s320/IMG_5438.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498339661887892354" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_yZA36-WQFmM/TE4HdveTAQI/AAAAAAAABu4/Yw6KHbIPhXo/s1600/IMG_5423.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/TE4HdveTAQI/AAAAAAAABu4/Yw6KHbIPhXo/s320/IMG_5423.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498340402607096066" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_yZA36-WQFmM/TE4HdX3AvkI/AAAAAAAABuw/IKGpMgw8ldQ/s1600/IMG_5493.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/TE4HdX3AvkI/AAAAAAAABuw/IKGpMgw8ldQ/s320/IMG_5493.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498340396268305986" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_yZA36-WQFmM/TE4HdKVoysI/AAAAAAAABuo/yvwTQo7idLw/s1600/IMG_5449.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://1.bp.blogspot.com/_yZA36-WQFmM/TE4HdKVoysI/AAAAAAAABuo/yvwTQo7idLw/s320/IMG_5449.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498340392638663362" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4Hc-hzBcI/AAAAAAAABug/pel5b3JG3-8/s1600/IMG_5402.JPG"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4Hc-hzBcI/AAAAAAAABug/pel5b3JG3-8/s320/IMG_5402.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5498340389468440002" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4JAotwdsI/AAAAAAAABvA/6lpkj7DH5BE/s1600/profi.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TE4JAotwdsI/AAAAAAAABvA/6lpkj7DH5BE/s320/profi.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5498342101599942338" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-260196727523097393?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/260196727523097393/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=260196727523097393' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/260196727523097393'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/260196727523097393'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/07/berlin.html' title='Berlin!'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_yZA36-WQFmM/TE4GzCLvvnI/AAAAAAAABuI/K-nDpPCccs4/s72-c/IMG_5386.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8701941820806781815</id><published>2010-07-20T22:27:00.007+03:00</published><updated>2010-07-20T22:59:07.219+03:00</updated><title type='text'>Silly C hacks</title><content type='html'>Macros! To do safe mallocs and fopens! Brittle like the glass you tread upon!&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;/* A small cat that prints out the files given to it */&lt;br /&gt;&lt;br /&gt;#include &amp;lt;stdlib.h&gt;&lt;br /&gt;#include &amp;lt;stdio.h&gt;&lt;br /&gt;#include &amp;lt;string.h&gt;&lt;br /&gt;#include &amp;lt;malloc.h&gt;&lt;br /&gt;#include &amp;lt;sys/stat.h&gt;&lt;br /&gt;&lt;br /&gt;#define BLOCK(pre, post, ok, error, block) \&lt;br /&gt;  { \&lt;br /&gt;    pre; \&lt;br /&gt;    if (ok) { \&lt;br /&gt;      block; \&lt;br /&gt;      post; \&lt;br /&gt;    } else { \&lt;br /&gt;      error; \&lt;br /&gt;    } \&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;#define with_malloc(t, var, sz, block) \&lt;br /&gt;  BLOCK(t var = (t)malloc(sz), free(var), var, \&lt;br /&gt;        fprintf(stderr, "Malloc failed, out of memory\n"); exit(1), block)&lt;br /&gt;&lt;br /&gt;#define with_file(var, fn, mode, error, block) \&lt;br /&gt;  BLOCK(FILE* var = fopen(fn, mode), fclose(var), var, error, block)&lt;br /&gt;&lt;br /&gt;#define with_file_contents(var, len, fn, error, block) \&lt;br /&gt;  with_file(var##__f, fn, "r", error, \&lt;br /&gt;    off_t len = file_size(fn); \&lt;br /&gt;    with_malloc(char*, var, len+1, \&lt;br /&gt;      len = fread(var, 1, len, var##__f); \&lt;br /&gt;      var[len] = 0; \&lt;br /&gt;      block))&lt;br /&gt;&lt;br /&gt;off_t file_size(char *path)&lt;br /&gt;{&lt;br /&gt;  struct stat st;&lt;br /&gt;  stat(path, &amp;st);&lt;br /&gt;  return st.st_size;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main(int argc, char *argv[]) {&lt;br /&gt;  int i;&lt;br /&gt;  for (i=1; i&amp;lt;argc; i++) {&lt;br /&gt;    with_file_contents(s, slen, argv[i], fprintf(stderr, "Couldn't open %s.\n", argv[i]),&lt;br /&gt;      fwrite(s, 1, slen, stdout);&lt;br /&gt;    );&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It would be kinda fun to port the whole functional array-munging part of prelude.ml to C. Fun in a very painful fashion.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8701941820806781815?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8701941820806781815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8701941820806781815' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8701941820806781815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8701941820806781815'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/07/silly-c-hacks.html' title='Silly C hacks'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-2225252287865313184</id><published>2010-07-20T18:21:00.005+03:00</published><updated>2010-07-20T19:23:41.863+03:00</updated><title type='text'>Small C++ SSE3 vector lib</title><content type='html'>If you want to do some simple and somewhat fast vector math in C++, &lt;a href="http://github.com/kig/correlate_opencl/blob/master/sse.h"&gt;sse.h&lt;/a&gt; might be fun.&lt;br /&gt;&lt;br /&gt;It has float4, double2 and double4 structs, constructors of different kinds, operator overloading and methods for horizontal add, dot product and swizzling. &lt;br /&gt;&lt;br /&gt;Code examples:&lt;br /&gt;&lt;br /&gt;4x4 matrix multiply with floats and doubles:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;// dst = a x b, ~44 cycles with -O3 -funroll-loops&lt;br /&gt;void mmul4x4 (const float *a, const float *b, float *dst)&lt;br /&gt;{&lt;br /&gt;  for (int i=0; i&amp;lt;16; i+=4) {&lt;br /&gt;    float4 row = float4(a) * float4(b[i]); // float4(a) is {a[0], a[1], a[2], a[3]}&lt;br /&gt;                                           // float4(b[i]) is {b[i], b[i], b[i], b[i]}&lt;br /&gt;    for (int j=1; j&amp;lt;4; j++)&lt;br /&gt;      row += float4(a+j*4) * float4(b[i+j]);&lt;br /&gt;    *(float4*)(&amp;dst[i]) = row;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void mmul4x4d (const double *a, const double *b, double *dst)&lt;br /&gt;{&lt;br /&gt;  for (int i=0; i&amp;lt;16; i+=4) {&lt;br /&gt;    double4 row = double4(a) * double4(b[i]);&lt;br /&gt;    for (int j=1; j&amp;lt;4; j++)&lt;br /&gt;      row += double4(a+j*4) * double4(b[i+j]);&lt;br /&gt;    *(double4*)(&amp;dst[i]) = row;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Dot product of two arrays of vectors:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;double dotArrays (const double2 *a, const double2 *b, int len)&lt;br /&gt;{&lt;br /&gt;  double2 sum;&lt;br /&gt;  for (int i=0; i&amp;lt;len; i++) {&lt;br /&gt;    sum += a[i] * b[i];&lt;br /&gt;  }&lt;br /&gt;  return sum.sum();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-2225252287865313184?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/2225252287865313184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=2225252287865313184' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2225252287865313184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2225252287865313184'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/07/gpu-wishlist.html' title='Small C++ SSE3 vector lib'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-807829786818976626</id><published>2010-07-12T16:46:00.004+03:00</published><updated>2010-07-21T02:03:05.735+03:00</updated><title type='text'>AC the planet</title><content type='html'>&lt;a href="http://rabett.blogspot.com/2010/06/death-doom-and-disaster-coming-soon-to.html"&gt;+7C equals ow&lt;/a&gt;. (Check out the map in that: yellow areas would be deadly.)&lt;br /&gt;&lt;br /&gt;Guess humans wouldn't have survived too well in the Cretaceous period (when the avg. temp was 15-20C higher than today. Tropical sea surface temperatures at around +37C. &lt;a href="http://www.agu.org/meetings/fm09/lectures/lecture_videos/A23A.shtml"&gt;Here a fun lecture on that&lt;/a&gt;.) If you'd time-travel back to ride on brontosaurs or whatever, you'd probably end up with a heat stroke.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-807829786818976626?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/807829786818976626/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=807829786818976626' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/807829786818976626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/807829786818976626'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/07/ac-planet.html' title='AC the planet'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3099808263865269228</id><published>2010-07-09T23:13:00.008+03:00</published><updated>2010-07-28T14:30:23.792+03:00</updated><title type='text'>Future display tech</title><content type='html'>Graphics, where is the limit? The display resolution of the day is 1920x1080, or Full HD, as it is called, with high-end displays operating at 2560x1600. Modern graphics cards are quite capable of running games at those resolutions. Adding stereo-3D on top of that doubles the amount of pixels, but that isn't much of a problem either. Pushing up the resolution from 100 ppi to 300 ppi would need an extra 9x the resolution, which would probably start pushing today's limits. &lt;br /&gt;&lt;br /&gt;To do no-glasses viewing-angle independent 3D requires a pixel for every direction (or something like a pixel, e.g. plop a lens in front of a flat pixel array). That would easily multiply the resolution by 16 or 256 or such. Not to mention requiring new display tech and the cameras to produce content for them. &lt;br /&gt;&lt;br /&gt;You probably want to push the dynamic range of the display as well, in order to reproduce bright noonday sun and a pitch-black night. According to &lt;a href="http://www.visualexpert.com/Resources/nightvision.html"&gt;this page&lt;/a&gt; the contrast from the sun to the limits of human night vision is around 300 trillion to one. To do that range with fixed step would take 48 bits. The display would have to be able to emit from a couple photons at a time all the way up to a billion candela per square meter, which is a good million times brighter than today's bright LCD displays.&lt;br /&gt;&lt;br /&gt;Using 16 bits per RGB channel for encoding the hue and saturation, and then 48 bits divided among them to do the luminosity would result in 96 bits per pixel. With 32-bit alpha channel, 128 bits per pixel, or four times today's display bit depth.&lt;br /&gt;&lt;br /&gt;The display would be nice if it covered your field of vision from a comfortable viewing distance. A wall-sized display might be the ticket to that. Let's say 5 meters by 3 meters. &lt;br /&gt;&lt;br /&gt;Summing up all the above, a future wall-sized 256x integral hologram display with 300 ppi resolution and natural dynamic range would have 550 gigapixels at 128-bit color, and would need a 8.8 TB frame buffer from the graphics processor. A more modest 30" display at those specs would have only 9.4 gigapixels and a 150 GB frame buffer. If we assume that 1 GFLOPS is enough to run a 32-bit two megapixel display, a 128-bit 550 gigapixel display would require around 1,100,000 GFLOPS of processing power. Today's graphics cards are at around 2 GFLOPS, so if they keep doubling in performance every year it'd take 20 years for them to get to the required 1.1 PFLOPS.&lt;br /&gt;&lt;br /&gt;What would such a display look like in person? I suppose the experience would be like looking out of a window. A window to a place that doesn't exist. Where the searing hot twin suns rise above the desert, windswept dialog boxes of long-forgotten programs casting long shadows into your living room. Or the view out into a starlit forest: a ghastly green shape here, another there, the darkness registering as a noisy shimmer as the rods in your eyes struggle to resolve the scant errant photons reflected from your surroundings.&lt;br /&gt;&lt;br /&gt;I don't really know if 256x holography would be enough, though. Maybe you want to boost that to 4096x or more. And make the display wrap around you for that VR cave experience. And add one extra color channel for tetrachromats. Or heck, just use photon energy histograms as your colors instead of the limited RGB. The people with those new-fangled omni-EM eyes will thank you for that. And a monitor just isn't a monitor unless you can do astronomical spectrography off it, take your X-ray with it, microwave an egg, make your radio play tunes and do a realistic documentary of the Chernobyl disaster, complete with the viewer receiving a half dozen gray of radiation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3099808263865269228?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3099808263865269228/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3099808263865269228' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3099808263865269228'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3099808263865269228'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/07/1920x1080-today-what-tomorrow.html' title='Future display tech'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8935844325272252973</id><published>2010-06-27T06:51:00.093+03:00</published><updated>2010-08-19T15:11:02.043+03:00</updated><title type='text'>Optimization madness</title><content type='html'>This paper, &lt;a href="http://domino.watson.ibm.com/library/CyberDig.nsf/1e4115aea78b6e7c85256b360066f0d4/9192e6536facfcef85257720005a0265!OpenDocument&amp;Highlight=0,Bordawekar"&gt;Believe it or Not! Multicore CPUs can Match GPUs for FLOP-intensive Applications!&lt;/a&gt;, from IBM Research is kinda fun. They did a benchmark with an Nvidia GTX 285 pitted against a dual quad-core Nehalem Xeon and a Power7. The benchmark is doing O(n&lt;sup&gt;2&lt;/sup&gt;) dot products from two 4 MB (not MiB, mind) source images to a 250 kB destination image. Maybe you can see where this is going?&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;TL;DR&lt;/h3&gt;&lt;br /&gt;The aggregate cache bandwidth of two high-end &lt;a href="http://ark.intel.com/Product.aspx?id=37111"&gt;$1386&lt;/a&gt; quad-core Xeons almost reaches the main memory bandwidth of a $300 graphics card. The cache bandwidth of an eight-core Power7 blade (&lt;a href="http://www-03.ibm.com/systems/bladecenter/hardware/servers/ps700series/browse_linux.html"&gt;cheapest system&lt;/a&gt; I could find started at $9788.00) actually exceeds the main memory bandwidth of the graphics card by 40%. A cache-conscious version of the GPU implementation might make it 5x faster (as according to &lt;a href="http://blog.cudachess.org/2009/07/cpu-vs-cuda-gpu-memory-bandwidth/"&gt;this blog post&lt;/a&gt;, the GTX 285 has 1.5 TB/s cache bandwidth). Currently trying to write one. Testing on my 2GHz Core 2 Duo and a Radeon HD 4850 that has 40% of GTX 285's bandwidth.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;My results as of now&lt;/h3&gt;&lt;br /&gt;&lt;h4&gt;Hardware tested&lt;/h4&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;ATI Radeon HD 4850, 67.9 GBps memory bandwidth, 384 GBps L2, 480 GBps L1, 1000 mul-add SP GFLOPS, hobbled by lack of local mem and the lack of image support in AMD's OpenCL drivers, resulting in most reads coming from main memory&lt;/li&gt;&lt;li&gt;Core 2 Duo E6400, 6.4 GBps memory bandwidth, 18 GBps L2, 90.5 GBps L1, 37.5 mul-add SP GFLOPS, hobbled by lack of cache bandwidth and computing performance (but hey, maybe there's some way to unlock the last 16 GFLOPS)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Table of current results (8 FLOPS per 32 bytes memory read)&lt;/h4&gt;&lt;pre&gt;&lt;br /&gt;  The results from the paper are on 500x500 input images. The paper reported&lt;br /&gt;  runtimes in seconds, I calculated the bandwidth numbers by dividing 281 GB&lt;br /&gt;  by the reported runtimes.&lt;br /&gt;&lt;br /&gt;  The OpenCL results are measured without the context and kernel creation&lt;br /&gt;  and release times, as those are likely only relevant for a single-run&lt;br /&gt;  workload (and a 0.7 sec one-time overhead to do a 1 sec operation would&lt;br /&gt;  be a bit skewy). The OpenCL results do include the data write and read times.&lt;br /&gt;  GLSL times are similarly measured without global setup overhead.&lt;br /&gt;&lt;br /&gt;  359 GBps (90 GFLOPS) 2x2-blocked GLSL on Radeon HD 4850 (1024x1024 input)&lt;br /&gt;  337 GBps (84 GFLOPS) 2x2-blocked GLSL on Radeon HD 4850 (500x500 input)&lt;br /&gt;  278 GBps (70 GFLOPS) optimized OpenCL GPU on Radeon HD 4850 (640x640 input)&lt;br /&gt; [276 GBps (69 GFLOPS) the paper's optimized impl on 8-core 3 GHz Power7]&lt;br /&gt;  259 GBps (65 GFLOPS) optimized OpenCL GPU on Radeon HD 4850 (512x512 input)&lt;br /&gt;  243 GBps (61 GFLOPS) optimized OpenCL GPU on Radeon HD 4850 (500x500 input)&lt;br /&gt;  240 GBps (60 GFLOPS) 2x2-blocked GLSL on Radeon HD 4850 (250x250 input)&lt;br /&gt; [230 GBps (58 GFLOPS) the paper's GTX 285 implementation using local mem]&lt;br /&gt;  204 GBps (51 GFLOPS) naive GLSL implementation on Radeon HD 4850&lt;br /&gt; [159 GBps (40 GFLOPS) the paper's GTX 285 implementation]&lt;br /&gt; [150 GBps (38 GFLOPS) the paper's optimized impl on 2x4-core 2.93 GHz Nehalem]&lt;br /&gt;   86 GBps (22 GFLOPS) naive OpenCL GPU implementation (1D workgroup)&lt;br /&gt;   84 GBps (21 GFLOPS) optimized OpenCL CPU on Core 2 Duo E6400&lt;br /&gt;   45 GBps (11 GFLOPS) manually optimized OpenMP SSE implementation&lt;br /&gt;   25 GBps (6 GFLOPS) naive OpenCL GPU implementation (2D workgroup)&lt;br /&gt;    5 GBps (1 GFLOPS) naive OpenMP SSE implementation&lt;br /&gt;    5 GBps (1 GFLOPS) naive scalar C implementation&lt;br /&gt;    1.1 GBps (0.3 GFLOPS) naive OCaml implementation&lt;br /&gt;    0.03 GBps (0.008 GFLOPS) naive pure-Python implementation&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Notes&lt;/h3&gt;&lt;br /&gt;The GPU data transfer overhead scales linearly with problem size, but the computational complexity of the algorithm grows quadratically. So on small problem sizes the effect of the overhead is more visible, as you can see above. The CPU does not suffer from the data transfer and setup overheads, so there is a point where the CPU is faster for problems smaller than that. With the current results for the GLSL implementation and the server CPUs, that point is at somewhere around 200x200 input image size. For my Core 2, it's around 150x150. For single image runs (with all the OpenGL overhead factored in the runtime), the GLSL-Power7-cutoff runs at around 600x600. &lt;br /&gt;&lt;br /&gt;CPU performance went up from a 5.5 GBps naive parallelized SSE implementation to a 84 GBps OpenCL optimized version (93% of L1 bandwidth, 56% peak GFLOPS). The fastest manual SSE code I managed topped out at 43 GBps. GPU performance went from a 86 GBps naive implementation to a 337 GBps optimized version (70% of L1 bandwidth, 8% peak GFLOPS). Note that the naive CPU implementation is written in C, parallelized and vectorized. If you're writing it in pure Python instead, the performance is around 0.03 GBps, or 180x slower than the C baseline. For another baseline, a naive OCaml implementation manages 1.1 GBps. And the OCaml and Python implementations don't get faster from reordering the memory accesses because they're bottlenecked by computation, not memory access.&lt;br /&gt;&lt;br /&gt;But in vectorized C, this algorithm is all about optimizing your memory accesses. The CPU got a 6x boost from changing the loop nesting (4x from the order optimization, rest from it enabling each core use its L1 caches and thus making parallelization more effective) and another 2x boost from reusing already loaded values (again, reducing average read latency). The GPU seems to be mostly bound by the memory access pattern (and the lack of local memory and global mem caching on the Radeon 4xxx series). On the GPU I got a 2.2x performance increase from updating 8 values at a time and reading the current block of used values to a local array. Heck, a more optimal OpenCL access pattern with _more_ reads and computation (but, sadly, _wrong_ reads) nearly doubles the GPU performance. A mostly lock-step-iterating version that does more reads got me an extra 20 GBps on OpenCL (kinda freaky, that.) GLSL does use the caches, so it's a good bit faster.&lt;br /&gt;&lt;br /&gt;The AMD OpenCL implementation in Stream SDK 2.1 doesn't support images on HD 4850, and global memory accesses aren't cached (if I understood correctly. The generated assembly has texture fetches with the UNCACHED keyword, so probably!), which might be the cause for the low bandwidth. Guess I should buy an HD 5xxx / Nvidia and test the performance with images (I hear they have real local memory as well :P). Testing with a faster CPU would be nice as well.. Not that I have money for any of those so pfft.&lt;br /&gt;&lt;br /&gt;Some interesting things from my POV: Slow CPU code is slow. It's possibly to reach at least 93% L1 bandwidth on the CPU, though even that doesn't max out the ALU (56% of the peak GFLOPS). And it's still kinda slow even when you do reach it. OpenCL is pretty nice for writing high-performance CPU code, it takes the pain out of SSE and parallelization. The setup is a pain though, and there's a 0.1 s binary program loading overhead. GPUs are kinda fast, even when hobbled with incomplete driver support. I'm stuck at 8% the peak GFLOPS because I can't read from the memory fast enough (and the GLSL implementation doesn't reuse loaded values to compute several output values in parallel, so it's doing cache reads instead of register reads. Which might well help.)&lt;br /&gt;&lt;br /&gt;The algorithm used in the benchmark is massively amenable to optimization as it has an embarrassingly parallel map pass, a completely associative reduce pass, completely vectorizable, each output value is basically a single N-wide mul with a horizontal add, has a large amount of shared reads between adjacent output values, benefits a lot from cache, benefits a lot from prefetch, etc. Any numbers you see about a benchmark like that should be taken with a huge grain of salt, it's just too easy to get 2x performance differences with small optimizations. The difference between my scalar implementation and parallelized SSE implementation was 15%. The difference between the parallelized SSE implementation and the optimized implementation was 1700%. The GPU performance delta was from a pessimized version that ran at 20 GBps to a regular version that ran at 85 GBps to an optimized version that ran at 240 GBps to a GLSL version that ran at 337 GBps. And the OpenCL GPU number is very sensitive to data access pattern, problem size and the size of the OpenCL workgroup: I'm getting 240 GBps with 64 workers on 512x512 images, 190 GBps with 32 workers, 180 GBps with 128 workers, 30 GBps on 500x500 images (that rises to 150 GBps by changing stride size), 150 GBps on 256x256 images, etc.  The GLSL version is much stabler with regard to input size, though it too suffers from lower performance at smaller input sizes.&lt;br /&gt;&lt;br /&gt;The CPU is less prone to wild swings in throughput from small changes, which is nice. The only way to get decent performance on this algorithm seems to be memory access optimization, SSE, parallelization and collecting several values at a time. So you pretty much need to write it in C / C++ with OpenMP and SSE intrinsics, or OpenCL. And if you're writing it in OpenCL, why not get the GPU in the mix as well...&lt;br /&gt;&lt;br /&gt;GPUs would be so much better if they didn't hard-lock when encountering an infinite loop. Over the past few days I've done Alt-SysRq-REISUB more times than I can remember. The OpenCL GPU implementation on the HD 48xx is ... finicky. Small changes in code can lead to either the GPU hard-locking, a 10x performance drop, or just plain incorrect behaviour. I'd like to see either GPUs with CPU levels of usability or CPUs with GPU levels of performance. Or both. I'm not picky.&lt;br /&gt;&lt;br /&gt;Based on my initial experience, I'd say that if your algorithm is amenable to GPUs (i.e. it does lots of single-precision FP math or needs high memory bandwidth), it's not too difficult to have a naive GPU implementation that performs better than a naive CPU implementation, especially if your CPU implementation is in a slow language. In my case, optimizing the CPU and GPU implementations happened in parallel and the optimizations were quite often portable between the different implementations. The CPU was easier to optimize for and had a much better development experience (i.e. no lockups), but my cheapo CPU didn't have the performance to match the GPU at any point. The CPU-GPU performance delta went from 15x between naive implementations (40x with GLSL) to 4x between the optimized implementations.&lt;br /&gt;&lt;br /&gt;In terms of price-performance, a $100 GPU and a $100 quad-core CPU (hmm, should you include the memory price in the CPU prices?) should perform about the same on this algorithm when using OpenCL. With GLSL, the GPU would be around 50% faster. A 2.9 GHz Athlon II X4 should get something like 220 GBps (247 GBps L1 bandwidth) and a 3 GHz quad-core Core i7 should get around 175 GBps (192 GBps L1 bandwidth). At the $200 price point, the gap between the GPU and CPU will likely increase: you get around 2x the GPU L1 bandwidth, but only a 50% extra CPU L1 bandwidth (with 6-core Phenom. Opting for a higher-clocked quad-core CPU would likely show very little performance benefit, you'd only get +20% out of going from 2.9GHz to 3.5GHz, for example.) But lacking the hardware to test, this is mere speculation. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Development log&lt;/h3&gt;&lt;br /&gt;&lt;b&gt;Update #0&lt;/b&gt;: &lt;a href="http://github.com/kig/correlate_opencl"&gt;Source code&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #1&lt;/b&gt;: GPU at 86 GBps. Managed to drop the CPU runtime from 49 s (5.5 GBps) to 30 s (9 GBps) by making the inner loop update the values in blocks. The intuition there being that you're going to have better cache locality if you're updating a bunch of values that use data from the same area. It also returns bad values for some input sizes, so it probably doesn't work right. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #2&lt;/b&gt;: The OpenCL GPU kernel is kinda hard to optimize, most of my naive tries ended up hurting performance. But I did manage to get the performance up by 40% using a similar block-based approach as on the CPU (by using a local cache array instead of relying implicitly on L1.) Currently at 118 GBps with roughly the same optimization effort as with the CPU implementation. I don't really know much anything about optimizing OpenCL though, so I think the number could go considerably higher. On the CPU, there's maybe another 10 GBps squeezable from there, I'll have to see about that too.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #3&lt;/b&gt;: GPU at 165 GBps (note that this is on a HD 4850 that has 40% of the memory bandwidth of the GTX 285. If the results scale linearly, GTX 285 would get 391 GBps.) CPU at 27 GBps, but the CPU results are wrong so I need to fix them to see if that's a real score. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #4&lt;/b&gt;: Fixed the optimized CPU version. It's at 22 GBps, but could probably go to 27 GBps with little work. The upper limit for the CPU version is 42 GBps where all reads come from L1. The L2 limit is less than half the L1 limit, so the optimized version is already beating pure L2 bandwidth.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #5&lt;/b&gt;: According to &lt;a href="http://ixbtlabs.com/articles3/video/rv770-part1-p3.html"&gt;this article on iXBT Labs&lt;/a&gt;, the L2 bandwidth of the HD 4850 is 384 GBps and the L1 bandwidth is 480 GBps, so those would be the limits for the GPU score. If the CPU scores are any indication, 400 GBps lies within the realms of possibility. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #6&lt;/b&gt;: Wow, a less math-heavy version of the OpenCL kernel absolutely flies on the CPU. 33 GBps. That's equivalent to 8 cycles per cache line. If the Nehalem was doing 10 cycles per cache line, it might go 20% faster with this version and reach 187 GBps. Anyone with an 8-core 2.93 GHz Nehalem want to give it a go (:?&lt;br /&gt;&lt;br /&gt;The difference between the GPU version and the CPU version is that the CPU version is doing the dot product with an accumulator vector and doing the horizontal add at the very end of the loop. This is because a float4 dot product is mul + horizontal add + float add on the CPU, and that's going to take 6 cycles or something. On the other hand &lt;code&gt;acc += v0 * v1&lt;/code&gt; executes in one cycle. The GPU does the dot product in a single cycle and the add in another, so it's just 2 cycles. I tried running the CPU version on the GPU but it strokes it the wrong way and results in the GPU hanging (or just running at 0.1 GBps through a benchmark designed for thousand times that). And the ATI Linux drivers don't do the Windows 7 thing of resetting the GPU if it goes unresponsive for several seconds.&lt;br /&gt;&lt;br /&gt;There's also a weird data alignment issue (or something) on the GPU: 496x496 images run at 152 GBps, 500x500 images run at 145 GBps, 512x512 runs at 165 GBps. The CPU doesn't care all that much.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #7&lt;/b&gt;: On trying to optimize the GPU kernel, managed to get the CPU kernel up to 42 GBps by refactoring a bit. That's nuts. That's a Core 2 Duo 2.12 GHz from four years ago beating the paper's two-thread Nehalem 2.93 GHz version by 13%. Maybe I should write a paper titled "Believe it or Not! 2 GHz Core 2 CPUs can Match 3 GHz Nehalems and POWER7s for FLOP-intensive Applications!"&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #8&lt;/b&gt;: Did a few bogus GPU kernels to figure out where the realistic limits are. If the 160 GBps kernel did all its reads from L1, it could achieve 270 GBps.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #9&lt;/b&gt;: GPU kernel at 182 GBps, achieved by breaking each row into its own extra work unit. Fixed the algorithm as well.. The paper was offsetting both images on Y but the idea is to keep one image static and move the other image over it. Anyhow, that had no effect on performance. It's funny how an extra 15 GBps on the GPU is kinda meh, but a 7 GBps increase on the CPU is WHOAH AWESOMESAUCE. Hurr durr, still lacking the L1 breakthrough. If the new kernel did all its reads from L1, 350 GBps.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #10&lt;/b&gt;: Oh right, += isn't atomic. Now I get to rewrite things.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #11&lt;/b&gt;: Actually, += not being atomic wasn't the problem. The problem was that the HD 4850 OpenCL implementation only gives you 16k of local mem. And that's across all the workgroups. And I can't get it to work. Oh well. Anyhow, optimized work group size and got 190 GBps from the GPU (including data transfer to-from main mem), and the kernel bandwidth broke 200 GBps! Woop!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #12&lt;/b&gt;: CPU kernel at *drumroll* 78 GBps! Insane! That's equivalent to a single quad-core Nehalem socket. From a previous-gen chip with 66% of the clock-speed, half as many cores and quarter of the cache. Applying the same optimizations to the GPU kernel also got a nice 30 GBps performance boost, bringing it up to 235 GBps.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #13&lt;/b&gt;: Tuned the GPU kernel a bit and got 250 GBPs at 640x640 problem size. Kernel is doing 260 GBps internally. Still need to eke out 16 GBps and it'd match the paper's Power7 numbers. Man, that thing is a beast. Or maybe they were using a version that was more optimized for it than for the Nehalem. My CPU kernel should do, hmm, maybe 500 GBps on the Nehalem, if it scales in a linear fashion. Who knows. [Update: 92% of an 8-core Nehalem's 370 GBps L1 bandwidth would be 340 GBps.]&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #14&lt;/b&gt;: Fixed the kernels, as I had optimized correctness away. 6 GBps drop for the CPU, GPU speed dropped 10-20 GBps.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #15&lt;/b&gt;: CPU kernel at 84 GBps, GPU kernel at 220 GBps. The CPU kernel is now about as fast as the naive GPU kernel, but still 3x slower than the optimized one. And the CPU kernel produces different results from the GPU kernel and the reference implementation because it does the additions in a different order.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #16&lt;/b&gt;: GPU kernel at 207 GBps (500x500), 245 GBps (512x512), 274 GBps (640x640), with internal kernel bandwidth reaching 285 GBps on 640x640. The 640x640 bandwidth is about the same as the Power7 number in the paper, yay!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #17&lt;/b&gt;: 500x500 kernel at 243 GBps. Also, before I was removing the context creation times and kernel build times from the OpenCL timings, but not the context and kernel release times. Which meant that the results weren't really descriptive of real multi-image run performance as both the creation and release are done one time only regardless of the amount of images you process. The creation overhead is 0.4 s and the release overhead is around 0.3 s, which will skew numbers for a test that takes ~1 s to run. To make the times more accurate for multi-image runs, I'm now deducting both the creation and release overheads from the OpenCL runtimes. The runtimes do include the data read and write overheads, as those are image-specific. (Hey, now all my OpenCL implementations have 0.3 s shorter times, yay?)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #18&lt;/b&gt;: Wrote a GLSL version of the GPU kernel to see if it might be able to use the texture caches better. Short answer: Yes. 204 GBps on a naive kernel.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update #19&lt;/b&gt;: GLSL version at 337 GBps for 500x500 images (350 GBps on 640x640). 70% of L1 bandwidth. Cached memory reads sure are awesome. The performance improvement came from reading in 2x2 blocks. The reason why it's faster is that a texture memory read also loads the adjacent values to cache. It might also be possible to use a similar load-reusing algorithm as I have on the CPU and the OpenCL implementations and make the GLSL version faster. But GLSL is painful for general computation, so maybe not.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The paper&lt;/h3&gt;&lt;br /&gt;By my calculations&lt;sup&gt;[&lt;a name="back-mem1" href="#mem1"&gt;1&lt;/a&gt;]&lt;/sup&gt; they achieved 160 GBps read bandwidth from the GTX 285 (advertised memory bandwidth 159 GBps), 150 GBps from the Nehalem (advertised peak 2x22 GBps) and 276 GBps from the eight-core Power7 (advertised peak 100 GBps). The algorithm is doing 1 cycle of computation (a SSE mul-add) per 32 bytes of memory read, so it's pretty much a pure bandwidth benchmark. As the total problem size is 8.25 MB, it snugly fits inside the 8.38 MB L3 caches of the CPUs, so maybe the CPU L3 caches are made out of infinite bandwidth magic? &lt;br /&gt;&lt;br /&gt;But, uh, the L3 latency on the Nehalem is 39 cycles, which'd give it 37.5 GBps bandwidth at 8 cores. 150 GBps is 10 cycles per cache line. The L2 latency on the Nehalem is 11 cycles. Plus you actually have to do computation for at least four cycles per cache line, though that's going to be masked by prefetch. How can it get 150 GBps?&lt;br /&gt;&lt;br /&gt;The paper had a table that compared the different stages of optimization. A single-threaded scalar version ran in 30 s, compared to a single-threaded SSE version that ran in 12 s. A 16-threaded SSE version took 1.8 seconds, which is 6.7x faster than the single-threaded one. Pretty nice with 8 cores. But this is a memory benchmark. How does a memory benchmark get 7x faster by increasing the thread count? That's only going to happen if each core has its own copy of the memory block it's working on. The same thing happened on the Power7 implementation: SIMD + OpenMP resulted in a 14x performance boost(!) So maybe their algorithm is really good at caching, who knows. There's no compilable source code in the paper so...&lt;br /&gt;&lt;br /&gt;I did a quick reimplementation of the algorithm in C++ and got 69 s for the scalar version and 58 s for the SSE version on my Core 2 Duo 2.12 GHz. Parallelizing it by tossing a '&lt;code&gt;#pragma omp parallel for&lt;/code&gt;' above the top-level loop brought the SSE execution time down to 49 s. Why so slow? The 8 MB input images don't fit in the CPU cache, turning the program from a cache bandwidth benchmark to a memory bandwidth one. By quick calculation, it's plowing through the benchmark at 5.6 GBps.&lt;br /&gt;&lt;br /&gt;[Update: A more cache-optimal memory access pattern got the CPU up to 84 GBps, so it's definitely possible to do most reads from L1.]&lt;br /&gt;&lt;br /&gt;Now, I also tested the algorithm on uninitialized allocations. And the results were very strange. On uninitialized data, the SSE version completed in 13 seconds. The parallel version ran in 7.5 seconds, for a bandwidth of 37.5 GBps. That's a 7 cycle latency. L2 latency is 14 cycles. It's actually reading from L1.&lt;br /&gt;&lt;br /&gt;So how can two 4 MB images fit into 32 kB of L1 cache? The easy answer? They don't. I think what we're seeing here is the Linux VM subsystem doing optimistic allocation. That is, when you allocate a block of memory, all 4 kiB pages in that block are going to map to The Empty Page at first. The pages are only actually created when you write on them. So as all the pages of a 4 MB image actually map to the same empty page of RAM, the benchmark is going to act as if the whole image is in L1.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TCbXglV0vgI/AAAAAAAABt4/2sKbFA6c3yA/s1600/Screenshot-1.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 399px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TCbXglV0vgI/AAAAAAAABt4/2sKbFA6c3yA/s400/Screenshot-1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5487310150776962562" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I next wanted to check out the GPU numbers and wrote an OpenCL version of the algorithm. Which also gave me an excuse to learn OpenCL. I downloaded the ATI Stream SDK and a couple hours later had a working OpenCL version. There's no Linux installer in the package, so you get to do a strange dance setting LD_LIBRARY_PATH and ATISTREAMSDKROOT environment variables.&lt;br /&gt;&lt;br /&gt;Writing the kernel was simple. I pretty much copy-pasted the C++ version of the algorithm to do the OpenCL kernel (there are two things that differ between the versions: vec4 turned into float4 and offset loop counters are now job ids). The state setup one has to do to execute the kernel was quite tedious though, and weighed in at 30 lines of code.&lt;br /&gt;&lt;br /&gt;The nice thing about OpenCL is that you can also run the kernels on the CPU. And, well, the CPU build of the OpenCL kernel actually ran faster than my parallelized SSE version, which is pretty awesome. The bad thing about OpenCL is that it's like C and OpenGL when it comes to error reporting: it fails silently and you have to milk the errors out. And when you manage to do an infinite loop, the GPU hangs and you have to cold-boot the computer. Additionally, when you're running something time-consuming on the GPU, it freezes compiz for the duration of the computation and you're left twiddling your thumbs. It would be really REALLY nice if the GPU could do pre-emptive multitasking. I've had to Magic-SysRq-boot my computer 4 times today. It's so Windows 95.&lt;br /&gt;&lt;br /&gt;But how about the runtimes for the GPU? My first version took 3.5 seconds on a Radeon HD 4850. 14 times as fast as the CPU implementation, twice as fast as the bogus uninitialized version, not to forget having to account for a 0.6 sec kernel compilation and setup overhead. Caching a copy of the built kernel binary to disk and reusing it brought the GPU overhead down to 0.2 s. The CPU OpenCL overhead was 0.2 s with compilation, 0.1 s with binary kernel.&lt;br /&gt;&lt;br /&gt;The HD 4850 has a memory bandwidth of 67 GBps, but the benchmark achieves 91 GBps. Did I make an error in my bandwidth calculations? Or might it cache more of the input image than the CPU? Time to write a small memory benchmark... Yeah. 10.3 seconds to do 69 billion 16-byte reads from a 4 MB buffer, 106 GBps. Guess it caches it better than the CPU (which managed 7.6 GBps.)&lt;br /&gt;&lt;br /&gt;By spending the same time optimizing the CPU implementation and the GPU implementation, I managed to further drop the realistic case CPU runtime to 12 s (23 GBps) by making the inner loop update the values in blocks. The intuition there being that you're going to have better cache locality if you're updating a bunch of values that use data from the same area. This kind of a thing could make the Nehalem keep the reads mostly in L2, which might well get it close to 150 GBps. [Update: As noted above, you can keep the reads mostly in L1, which might get the dual Nehalem to ~340 GBps, if the Core 2 results scale.]&lt;br /&gt;&lt;br /&gt;Applying the same optimization to the GPU version brought its bandwidth up to 165 GBps. That's 10% faster than the $2600 Nehalem duo. On a $100 graphics card. And that's not even close to the 384 GBps L2 bandwidth limit for the HD 4850, so there is probably quite a bit of headroom there. [Update: Well, if you could use the cache with the HD 4xxx OpenCL implementation...]&lt;br /&gt;&lt;br /&gt;To wrap up: Benchmarking a cache-optimized algorithm against an algorithm that reads from the main memory is kinda pointless. Or was the goal of the paper to say "Hey, we've got an awesome optimizing compiler that can reorder loops to be very cache-optimal"? Also, I should remember to initialize buffers when benchmarking. And a $100 graphics card trounces $2600 worth of CPU power in this benchmark. Finally, that $100 graphics card sure could use some damn &lt;a href="http://forums.amd.com/devforum/textthread.cfm?catid=390&amp;threadid=135036&amp;filtmsgid=1142559"&gt;hardware support for pre-emptive multitasking&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I put the sources on GitHub, go &lt;a href="http://github.com/kig/correlate_opencl"&gt;check 'em out&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;[&lt;a name="mem1" href="#back-mem1"&gt;1&lt;/a&gt;] Estimated bandwidth use for the benchmark:&lt;pre&gt;&lt;br /&gt;float_sz = 4&lt;br /&gt;out_size = 250 * 250 = 62500&lt;br /&gt;&lt;br /&gt;reads_per_out = 2 * (500-0.5*250) * (500-0.5*250) * 4*float_sz&lt;br /&gt;              = 2 * 375*375 * 4*float_sz&lt;br /&gt;              = 4500000&lt;br /&gt;writes_per_out = float_sz&lt;br /&gt;&lt;br /&gt;total_reads = out_size * reads_per_out&lt;br /&gt;total_writes = out_size * writes_per_out&lt;br /&gt;&lt;br /&gt;total_bw = out_size * (reads_per_out + writes_per_out)&lt;br /&gt;         = 62500 * (4500000 + 4) &lt;br /&gt;         = 281 GB&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8935844325272252973?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8935844325272252973/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8935844325272252973' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8935844325272252973'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8935844325272252973'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/06/memory-madness.html' title='Optimization madness'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_yZA36-WQFmM/TCbXglV0vgI/AAAAAAAABt4/2sKbFA6c3yA/s72-c/Screenshot-1.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8825097272572482073</id><published>2010-06-25T15:51:00.005+03:00</published><updated>2010-06-26T03:05:00.200+03:00</updated><title type='text'>Democracy</title><content type='html'>You can only call an economy a market economy if at least 60% of the population can fully participate in it on a daily basis and own property.&lt;br /&gt;&lt;br /&gt;By this measure, there are several functioning market economies in the world.&lt;br /&gt;&lt;br /&gt;You can only call a country democratic if at least 60% of the population can fully participate and vote in a parliamentary meeting about the matters of the state at least once every two weeks.&lt;br /&gt;&lt;br /&gt;By this measure, there are zero democracies in the world.&lt;br /&gt;&lt;br /&gt;Suppose that you had an economic system similar to a representational republic. Only the members of the parliament are allowed to own property and participate in the economy. Every four years, the people vote to select a couple hundred people who are allowed to participate in the economy. Does that sound sane? A good system of economy?&lt;br /&gt;&lt;br /&gt;I blame Socrates and Plato for the travesty that is known as the Republic. They were children of a democracy&lt;sup&gt;[1]&lt;/sup&gt; and wanted to overthrow the system to install a philosopher dictatorship in its place. The only reason their writings have survived is that they were written in a democracy (and hence not summarily burned) and that they have provided a nice argument for rulers ever since.&lt;br /&gt;&lt;br /&gt;"We actually have the best system of governance. Only us well-born/popular/rich/divine/ideologically correct should be the ones to have any political power. See, even the Ancient Greeks who lived in an oh-so-terrible democracy agree: the perfect political system is one governed by a perfect philosopher-king who is omniscient and makes perfect decisions. Not one that's governed by flawed people who know nothing and make imperfect decisions. QED."&lt;br /&gt;&lt;br /&gt;Compare:&lt;br /&gt;&lt;br /&gt;"We actually have the best system of economy. Only us well-born/popular/rich/divine/ideologically correct should be the ones to own any property. See, even the Ancient Greeks who lived in an oh-so-terrible free market agree: the perfect economic system is one governed by a perfect economist-king who is omniscient and makes perfect trades. Not one where property can be owned by flawed people who know nothing and make imperfect trades. QED."&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Athens had a population of around 200 000, of which only 30 000 were members of parliament, so it fails to reach the 60% standard. However, that was 2500 years ago, in the times of the Persian and Egyptian God-Kings, the Spartan military dictatorship, the Warring States Period, etc. and should perhaps be judged in that context. In a modern society, where you have equality of sexes and no slavery, they should have had 120 000 members of parliament. Then again, where are the modern countries with even a paltry 30 000 members of parliament.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8825097272572482073?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8825097272572482073/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8825097272572482073' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8825097272572482073'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8825097272572482073'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/06/democracy.html' title='Democracy'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8699168340563755552</id><published>2010-06-24T02:11:00.005+03:00</published><updated>2010-06-24T08:48:40.518+03:00</updated><title type='text'>Funny multiplication tricks</title><content type='html'>This kinda struck me yesterday: multiplication is additive pattern splatting (I just came up with that term.) I've been seeing binary tricks that have used multiplication as copy operation but never really understood it before now. &lt;br /&gt;&lt;br /&gt;With sparse binary patterns it's quite apparent. For example, let's multiply 00100010001001001 by 111. Sounds hard? It's actually easy, you replace each 001 in the long pattern with the short pattern and end up with 11101110111111111. In the same way you can copy a byte to different places in a word. This may sometimes be faster and/or less bother than bit-shifting:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;int64 fillInt64WithByte_shift(int8 b) {&lt;br /&gt;  return b &lt;&lt; 56 | b &lt;&lt; 48 | b &lt;&lt; 40 | b &lt;&lt; 32 | b &lt;&lt; 24 | b &lt;&lt; 16 | b &lt;&lt; 8 | b;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;vs.&lt;br /&gt;&lt;br /&gt;int64 fillInt64WithByte_mul(int8 b) {&lt;br /&gt;  return 0x0101010101010101 * b;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It also works with decimal numbers: 10101 x 99 is 999999. If you have a number that's not a 1 in the target pattern, you need to multiply your source pattern with that: 001 002 003 0005 x 203 = 203 406 609 1015.&lt;br /&gt;&lt;br /&gt;However, the simple copy splatting only works when you don't have overlaps. With overlaps you need to add the new splat to the target pattern. Let's consider non-carry addition first using 1001301001 x 211 as the example. First you fill in the non-overlapping parts (the 1001001001 pattern) and end up with 211 211 211 211. To integrate the 3 into the result you multiply it by 211 and add the result to the target pattern.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;   1001301001 x 211 =&lt;br /&gt; 211211211211&lt;br /&gt;+    633&lt;br /&gt; ------------&lt;br /&gt; 211274511211&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When you have carries, it gets a good deal more tedious. Consider binary 1111 x 1111. First we splat 1111 on the first 1 in the target pattern: 01111000, then on the second 1: 00111100, etc. and finally get 01111000 + 00111100 + 00011110 + 00001111 = 11100001. Drudgery.&lt;br /&gt;&lt;br /&gt;We could move it to base-15 and get 10 x 10 = 100, then convert that back to base-2... for no benefit whatsoever. 1111^2 x 1 = 1111 x 1111. Or do a trick with 10000 x 1111 - 1111 = 11110000 - 1111 = 11100001.&lt;br /&gt;&lt;br /&gt;Or convert to base-16 for F x F = E1, then convert that to base-2 for 1110 0001. Note the similarity between F x F = E1, 9 x 9 = 81, o7 x o7 = o61, b1 x b1 = b01. This can be explained by&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;For base N, N = 10 - 1:&lt;br /&gt;N x N =  N x (10 - 1)&lt;br /&gt;      =  N x 10 - N&lt;br /&gt;      =  N x 10 - (10 - 1)&lt;br /&gt;      = (N x 10 - 10) + 1&lt;br /&gt;      = (N-1) x 10 + 1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Back to splat-additive multiplication, it seems to work for positive integers. Does it work for negative ones? Well, yes, 1001001 * -101 = -101000000 + -101000 + -101 = -101101101. And there's that sign-trick to turn multiplication of negative integers to multiplication of positive integers: -A x B = -(A x B), -A x -B = -(-(AxB)) = AxB.&lt;br /&gt;&lt;br /&gt;Does it work for matrices... not that I can tell. Matrix multiplication is not splatting anyhow. What would a splat-additive matrix multiplication operation look like? I don't know, let's start exploring by trying to extend the scalar splat-additive multiplication to vectors. If we treat vectors as bucket-based integers (that is, no carries, base-&amp;#8734;), integer-like splat-additive vector multiplication would be quite easy. For example, &lt;code&gt;[1 0 0 2] [x] [3 6] = [3 6 0 6 12]&lt;/code&gt;, but what can that be used for? Is there some sensible conversion to scalar from it? Can it be extended to matrices?&lt;br /&gt;&lt;br /&gt;As an aside, scalar vector multiplication can be used to make integer multiplication a bit simpler. Using 32 x 1935 as an example: &lt;code&gt;32 x [1 9 3 5] = [32 288 96 160]&lt;/code&gt;. We can convert that to an integer value by doing a base conversion for each element and adding them together: 32*1000 + 288*100 + 96*10 + 160*1 = 61920 = 32 x 1935.&lt;br /&gt;&lt;br /&gt;Another way to do splat multiplication with vectors would be to generate a matrix from vector multiplication, an approach not entirely without appeal. &lt;code&gt;[A B] x [C D]&lt;/code&gt; would result in &lt;code&gt;[ [AC AD] [BC BD] ]&lt;/code&gt;. It can express number multiplication as a special case: AxB would be |A| x |B| = AxB (I'm using |A| to say that |A| is a single-element vector of zero dimension). If you have dimensional numbers, it preserves the dimensionality of the result, &lt;code&gt;[Length] x [Width] = [[Area]]&lt;/code&gt;. &lt;br /&gt;&lt;br /&gt;Can the vector-splatting multiplication be extended to vectors of vectors? Seems so:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[ [1 2] [3] ] x [ [5 5] [6 2] ] would be, uh, ... &lt;br /&gt;[  [   [1 2]x[5 5]     [1 2]x[6 2]   ]  [   [3]x[5 5]  [3]x[6 2] ]  ] =&lt;br /&gt;[  [  [[5 5] [10 10]] [[6 2] [12 4]] ]  [ [[15] [15]] [[18] [6]] ]  ]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And to continue with the dimensional numbers,&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[Height] x [[Area]] = [[[ Height x Area ]]]&lt;br /&gt;[[Area]] x [[Area]] = [[[[ Hypercube! ]]]]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What properties would this vector-splat multiplication have?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;[ A B ] x [ C D ] = [ [AC AD] [BC BD] ]&lt;br /&gt;[ C D ] x [ A B ] = [ [CA CB] [DA DB] ]&lt;br /&gt;Not commutative (unless the equality is sum-equality: A = B if sum(A) = sum(B).)&lt;br /&gt;&lt;br /&gt;([ A B ] x [ C D ]) x [ E F ] = &lt;br /&gt;[ [AC AD] [BC BD] ] x [ E F ] =&lt;br /&gt;[ [[ACE ACF] [ADE ADF]] [[BCE BCF] [BDE BDF]] ]&lt;br /&gt;&lt;br /&gt;[ A B ] x ([ C D ] x [ E F ]) =&lt;br /&gt;[ A B ] x [ [CE CF] [DE DF] ] =&lt;br /&gt;[ [[ACE ACF] [ADE ADF]] [[BCE BCF] [BDE BDF]] ]&lt;br /&gt;Seems to be associative.&lt;br /&gt;&lt;br /&gt;The identity element would be a dimensionless 1:&lt;br /&gt;1 x [ A B ] = [ 1xA 1xB ] = [ A B ]&lt;br /&gt;&lt;br /&gt;Note that a dimensional one won't do:&lt;br /&gt;[1] x [ A B ] = [ [A] [B] ]&lt;br /&gt;&lt;br /&gt;To do the inverse element, we need vectors with negative dimensions:&lt;br /&gt;I x [ A B ] = 1 requires I to reduce the dimension of [ A B ]&lt;br /&gt;&lt;br /&gt;Additionally, the inverse element requires sum-equality (i.e. [A B] = [B A])&lt;br /&gt;[A B] = [C] iff A + B = C&lt;br /&gt;|0.5 0.5| = 0.5 + 0.5 = 1, hello confusing notation for dimensionless vectors&lt;br /&gt;[[A [B] C]] = |[[[B]]] [[A C]]|, hello polynomial notation&lt;br /&gt;&lt;br /&gt;I x [ A B ] = 1&lt;br /&gt;[1/2A 1/2B]&lt;sup&gt;-1&lt;/sup&gt; x [ A B ] = |A/2A B/2B| = |1/2 1/2| = 1&lt;br /&gt;&lt;br /&gt;So the inverse function would be&lt;br /&gt;-- get inverse vector for which V x inv(V) = 1&lt;br /&gt;inv(V) = (map (\i -&gt; 1/(V_len*i)) V)&lt;sup&gt;-V_dim&lt;/sup&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;...ghhh. So, uh, I guess with sum-equality it could maybe form a field with some sort of vector addition. The real question is whether there is anything where this multidimensional splat-multiplication works better than existing methods. It looks a lot like polynomials. Maybe the vector addition could be..&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;|A B| = (A + B)&lt;sub&gt;0&lt;/sub&gt;&lt;br /&gt;[A B] = (A + B)&lt;sub&gt;1&lt;/sub&gt;&lt;br /&gt;|[[A B] C] D| = |[[A B]] [C] D| = (A+B)&lt;sub&gt;2&lt;/sub&gt; + C&lt;sub&gt;1&lt;/sub&gt; + D&lt;sub&gt;0&lt;/sub&gt;&lt;br /&gt;= [[A B]] + [C] + |D|&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's kinda interesting, the (A+B)&lt;sub&gt;2&lt;/sub&gt; isn't (A+B)&lt;sup&gt;2&lt;/sup&gt; but rather means that (A+B) is a two-dimensional number. &lt;br /&gt;&lt;br /&gt;Oh well, back to the grind.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8699168340563755552?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8699168340563755552/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8699168340563755552' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8699168340563755552'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8699168340563755552'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/06/funny-multiplication-tricks.html' title='Funny multiplication tricks'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4522252204945929461</id><published>2010-06-23T01:19:00.003+03:00</published><updated>2010-06-23T02:41:42.530+03:00</updated><title type='text'>Status update</title><content type='html'>Taking two days off, a mid-week weekend.&lt;br /&gt;&lt;br /&gt;Estimated that doing an editable zoomable tilemap would take a week. 15 days of consecutive commits say that I was wrong by at least a factor of three. Zoomable tilemap, check. Editable, sort of. Lacking in genericity as rendering in browsers is either slow or requires more ugly performance hacks than you can shake a stick at. And 60 fps is the law of the land, so screw genericity. &lt;br /&gt;&lt;br /&gt;OCaml is horrible on 32-bit. 16MB maximum string size. And strings are the intended way to handle binary data. And if you use BigArrays, they're not GC'd properly. Then again, the worst-case allocation in my use would be 2.5 gigs, so perhaps it would be reasonable to work very hard to do things in smaller increments. Loading 16k x 16k images, that is my nemesis (I had a Ruby + C version of this but...)&lt;br /&gt;&lt;br /&gt;Next need to either make an interactive character animation system in JS or find one. Hrm.. Maybe look at the Big Buck Bunny Blender files, see how to do the whole model-rig-animate-thing, export to JS, write some simple engine to run it. Or just draw the animation by hand (On paper! With a reed! A broken reed!)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4522252204945929461?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4522252204945929461/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4522252204945929461' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4522252204945929461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4522252204945929461'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/06/status-update.html' title='Status update'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8585469466305861109</id><published>2010-06-06T17:33:00.002+03:00</published><updated>2010-06-06T17:49:54.916+03:00</updated><title type='text'>WebGL stereo rendering demo</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_yZA36-WQFmM/TAux3mn8AyI/AAAAAAAABtQ/ZY7FInddme0/s1600/stereo.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 148px;" src="http://4.bp.blogspot.com/_yZA36-WQFmM/TAux3mn8AyI/AAAAAAAABtQ/ZY7FInddme0/s400/stereo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5479668940445319970" /&gt;&lt;/a&gt;&lt;br /&gt;Cross your eyes and prepare for eyestrain, here comes the &lt;a href="http://cs.helsinki.fi/u/ilmarihe/jscene/demos/stereo.html"&gt;WebGL stereogram&lt;/a&gt;. Drag with LMB to rotate, drag with MMB to pan, wheel zooms, shift-wheel scales model.&lt;br /&gt;&lt;br /&gt;The demo uses glScissor and glViewport to restrict drawing to one side of the viewport, then draws the scene to the left and right sides with the camera offset slightly. The camera offset is along the right-vector of the camera and you can get it by finding the normal of the camera up and look vectors. Basically &lt;code&gt;right = cross((lookAt - position), up)&lt;/code&gt;, then &lt;code&gt;leftEye = position - (sep/2 * right); rightEye = position + (sep/2 * right)&lt;/code&gt;. Then you render to left viewport from &lt;code&gt;rightEye&lt;/code&gt; and to the right viewport from &lt;code&gt;leftEye&lt;/code&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8585469466305861109?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8585469466305861109/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8585469466305861109' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8585469466305861109'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8585469466305861109'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/06/webgl-stereo-rendering-demo.html' title='WebGL stereo rendering demo'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_yZA36-WQFmM/TAux3mn8AyI/AAAAAAAABtQ/ZY7FInddme0/s72-c/stereo.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8345748160124607479</id><published>2010-06-02T20:38:00.005+03:00</published><updated>2010-06-06T23:33:10.716+03:00</updated><title type='text'>Loading WebGL models from tarballs</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/TAukHEDp0lI/AAAAAAAABtI/gh-e_Vr2p5o/s1600/bin_loader_whale.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 296px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/TAukHEDp0lI/AAAAAAAABtI/gh-e_Vr2p5o/s400/bin_loader_whale.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5479653812881445458" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Plugged the TarGZ loader together with &lt;a href="http://fhtr.blogspot.com/2009/12/3d-models-and-parsing-binary-data-with.html"&gt;my old OBJ and binary model loaders&lt;/a&gt; and emerged with a &lt;a href="http://cs.helsinki.fi/u/ilmarihe/jscene/demos/tgz_loader.html"&gt;couple&lt;/a&gt; &lt;a href="http://cs.helsinki.fi/u/ilmarihe/jscene/demos/tgz_bin_loader.html"&gt;demos&lt;/a&gt; that load a model and a texture from a gzipped tarball and render it using WebGL.&lt;br /&gt;&lt;br /&gt;Verdict: use uncompressed tarballs and HTTP gzip compression whenever possible. Doing the gzip with JS doesn't cache the uncompressed data transparently (you'd have to do an offline storage hack), and it's kinda slow. Parsing tar vs. plain files is a bit different tradeoff. With tar you only have to manage a single file. With plain files, you need to manage the whole directory hierarchy they live in. Which may not be something that you can or want to do with your file serving host.&lt;br /&gt;&lt;br /&gt;One solution is uncompressed tarballs and a .htaccess with deflate filter set for them, though it seems to screw up caching somehow (thanks for the &lt;a href="http://www.khronos.org/message_boards/viewtopic.php?f=35&amp;t=2904"&gt;.htaccess tip&lt;/a&gt; go to &lt;a href="http://www.ibiblio.org/e-notes/webgl/webgl.htm"&gt;Evgeny Demidov&lt;/a&gt;)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;AddOutputFilterByType DEFLATE text/html text/plain application/x-tar application/javascript&lt;br /&gt;&lt;br /&gt;&amp;lt;Files *.bin&gt;&lt;br /&gt;SetOutputFilter DEFLATE&lt;br /&gt;&amp;lt;/Files&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;which also helps for JSON models and OBJs and other textual things. The main problem with the tarballs is that loading the images takes a bit longer (read: hundredths / tenths of a second) due to having to dataURLize them. Well. If you pre-dataURL the images, that won't be a problem.&lt;br /&gt;&lt;br /&gt;The main benefit of the tarballs is that everything's in a single file and you don't need a chain loader to initialize models in a single step. Instead of first showing an untextured model, then sometime later the model with a diffuse texture, then later the model with a diffuse texture and a normal map, etc., with single-step initialization you get the full textured model once all the data is loaded. Plus, if you have a standard model format of some sort with manifests and whatnot, you can swap models by changing a single URL. You can do that with a plain model.xml or somesuch as well, but then you need to manage multiple files. It's harder to reuse resources inside tarballs than resources as separate files, though.&lt;br /&gt;&lt;br /&gt;Also updated the &lt;a href="http://cs.helsinki.fi/u/ilmarihe/jscene/demos/bin_loader.html"&gt;big whale model viewer&lt;/a&gt; to be mouse-controllable (drag with LMB to rotate, drag with MMB to pan, scroll wheel to zoom, shift-scroll wheel to scale).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8345748160124607479?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8345748160124607479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8345748160124607479' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8345748160124607479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8345748160124607479'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/06/loading-webgl-models-from-tarballs.html' title='Loading WebGL models from tarballs'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_yZA36-WQFmM/TAukHEDp0lI/AAAAAAAABtI/gh-e_Vr2p5o/s72-c/bin_loader_whale.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-6928680833211921157</id><published>2010-05-31T18:45:00.007+03:00</published><updated>2010-05-31T20:50:35.973+03:00</updated><title type='text'>Holographic video</title><content type='html'>&lt;a href="http://nextbigfuture.com/2010/05/nhk-japan-is-promising-functional.html"&gt;NHK is developing a holographic TV that doesn't require viewing glasses&lt;/a&gt; (via Reddit.)&lt;br /&gt;&lt;br /&gt;The premise is simple: put a transparent surface between the viewer and the object. All the viewer sees is the photons coming from the transparent surface. Now sustain the photon emission from the transparent surface as-is and remove the object. To the viewer it looks like nothing has changed. The object seems to be still visible through the surface and you can walk around and look at it from different angles. A hologram! (When I understood the idea, I got a stupid goofy grin and walked around the house going all "WOOHOO! HOLOGRAMS! YEAH!")&lt;br /&gt;&lt;br /&gt;Implementation then. The camera lens could be a large sheet of viewing-angle-optimized hemispherical pixels with an RGB light detector for each direction. The viewing device would swap the detectors for emitters. It'd require a huge lens (the size of the display, really) and require multiple times the megapixels of the usual one-directional image (as now each pixel is in fact several pixels, each facing a different direction.)&lt;br /&gt;&lt;br /&gt;Except that they're not doing it quite like that. I don't fully understand it, but I think they're using an 2D array of lenses to get a flat projection and then record the resulting 2D image in the usual manner (project to a small CCD/CMOS/whatever). The display is a regular ultra-high-resolution flat panel with a similar array of lenses in front of it. Here's a good presentation slide deck: &lt;a href="http://docs.google.com/viewer?url=http%3A%2F%2Fwww.ieee.org%2Forganizations%2Fsociety%2Fbt%2F3Dtv4.pdf"&gt;3D-TV System Based on Spatial Imaging Method for Future&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-6928680833211921157?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/6928680833211921157/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=6928680833211921157' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6928680833211921157'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/6928680833211921157'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/holographic-video.html' title='Holographic video'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7647054505536221334</id><published>2010-05-28T20:52:00.002+03:00</published><updated>2010-05-28T21:03:01.115+03:00</updated><title type='text'>Loading .tar.gz with JavaScript</title><content type='html'>With the power of copy-pasting code from the INTERNET, I have now created a JavaScript library that streams gzip files and specifically .tar.gz files. &lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/dankogai/js-deflate"&gt;DEFLATE&lt;/a&gt; and &lt;a href="http://noteslog.com/post/crc32-for-javascript/"&gt;CRC32&lt;/a&gt; implementations found with Google and tweaked and bolted onto the &lt;a href="http://fhtr.blogspot.com/2010/05/parsing-tarballs-with-javascript.html"&gt;tar parser&lt;/a&gt;. The performance is something between 1-5 MB/s on Chrome, third of that on Minefield.&lt;br /&gt;&lt;br /&gt;In action: &lt;a href="http://cs.helsinki.fi/u/ilmarihe/js_gzip/test_gzip.html"&gt;Demo page&lt;/a&gt; &lt;br /&gt;Just the library: &lt;a href="http://gist.github.com/417483"&gt;gzip.js&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7647054505536221334?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7647054505536221334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7647054505536221334' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7647054505536221334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7647054505536221334'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/loading-targz-with-javascript.html' title='Loading .tar.gz with JavaScript'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8271141331914981786</id><published>2010-05-25T15:57:00.004+03:00</published><updated>2010-05-25T16:40:33.944+03:00</updated><title type='text'>Numbers, numbers</title><content type='html'>&lt;a href="http://yle.fi/uutiset/viihde/2010/05/nettipiratismi_aiheuttaa_suomessa_jattitappiot_1708634.html"&gt;A Finnish copyright survey&lt;/a&gt; tells us that copyright infringement costs the Finnish recording industry more than 355 million euros every year. The yearly sales of the Finnish recording industry are around 100 million euros. If 30% of that is profit &amp;mdash; 30% being the profit margin for software companies &amp;mdash; they spend 70 million to make the records, get 100 million back in sales and pocket a cool 30 million in profit.&lt;br /&gt;&lt;br /&gt;Now, as a result of the copyright infringement costs, the costs of the Finnish recording industry are actually 70 + 355 = 425 million euros. They spend 425 million euros, get 100 million back in sales and end up 325 million in the red. I will expect the whole industry to go bankrupt in three months, if not faster. Every euro they spend is costing them five.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8271141331914981786?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8271141331914981786/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8271141331914981786' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8271141331914981786'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8271141331914981786'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/numbers-numbers.html' title='Numbers, numbers'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-1988207558840508673</id><published>2010-05-24T19:12:00.006+03:00</published><updated>2010-05-24T19:40:45.866+03:00</updated><title type='text'>Slideshow-qgl, now with new features</title><content type='html'>&lt;a href="http://github.com/kig/slideshow-qgl"&gt;Slideshow-qgl&lt;/a&gt; — my tiny slideshow app — now has recursive dir traversal, a fullscreen mode and a slideshow mode. Which makes it more useful than.. most slideshow apps out there. GQview and EOG, I'm looking at you. With a stern eye. And a hint of rebuke.&lt;br /&gt;&lt;br /&gt;As you might guess from the name, slideshow-qgl is based on Qt4 and OpenGL. Qt4 makes it relatively easy to write and OpenGL makes it fast to pan &amp; zoom &amp; fade between images.&lt;br /&gt;&lt;br /&gt;It follows the mplayer approach to GUI design, specifically "widgets are ugly, get rid of them." This is what it looks like with an image loaded and zoomed in a bit:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_yZA36-WQFmM/S_qpxSwfjlI/AAAAAAAABrQ/KDIIZZSaBC8/s1600/slideshow-qgl.jpg"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 303px;" src="http://1.bp.blogspot.com/_yZA36-WQFmM/S_qpxSwfjlI/AAAAAAAABrQ/KDIIZZSaBC8/s400/slideshow-qgl.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5474874961336176210" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Right.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-1988207558840508673?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/1988207558840508673/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=1988207558840508673' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1988207558840508673'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/1988207558840508673'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/slideshow-qgl-new-features.html' title='Slideshow-qgl, now with new features'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_yZA36-WQFmM/S_qpxSwfjlI/AAAAAAAABrQ/KDIIZZSaBC8/s72-c/slideshow-qgl.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-54266195588650227</id><published>2010-05-23T02:22:00.000+03:00</published><updated>2010-05-23T02:23:39.710+03:00</updated><title type='text'>Ugly Tachikomaling</title><content type='html'>&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/LyC8PpA3MWs&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/LyC8PpA3MWs&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-54266195588650227?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/54266195588650227/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=54266195588650227' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/54266195588650227'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/54266195588650227'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/ugly-tachikomaling.html' title='Ugly Tachikomaling'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5084834199813935720</id><published>2010-05-21T09:09:00.024+03:00</published><updated>2010-05-29T11:54:30.582+03:00</updated><title type='text'>Parsing tarballs with JavaScript</title><content type='html'>Update: Check out this &lt;a href="http://fhtr.blogspot.com/2010/05/loading-targz-with-javascript.html"&gt;augmented version&lt;/a&gt; that streams gzipped tarballs.&lt;br /&gt;&lt;br /&gt;Here's a small piece of &lt;a href="http://gist.github.com/408333"&gt;JavaScript to parse tarballs&lt;/a&gt; and my &lt;a href="http://gist.github.com/407595"&gt;custom JSON packfiles&lt;/a&gt;. There be four demos as well: &lt;a href="http://cs.helsinki.fi/u/ilmarihe/js_untar.html"&gt;loading files from a tar&lt;/a&gt;, &lt;a href="http://cs.helsinki.fi/u/ilmarihe/js_untar2.html"&gt;streaming images from a tar&lt;/a&gt;, &lt;a href="http://cs.helsinki.fi/u/ilmarihe/json_unpack.html"&gt;loading files from a JSON packfile&lt;/a&gt; and &lt;a href="http://cs.helsinki.fi/u/ilmarihe/json_unpack2.html"&gt;streaming files from a JSON packfile&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The part that converts images to date URLs is a bit slower than it could be, as it has to strip high bytes off the characters. The upcoming JS File and Blob APIs for binary data handling should help there. Though if you have less than a hundred kB of images, I don't think you'll even notice the delay. Even half a meg of stuff unpacks in a fraction of second on my slow laptop (Pentium M 1.7GHz). If you do need speed, you can &lt;a href="http://gist.github.com/408601"&gt;convert&lt;/a&gt; the images to data URIs beforehand. &lt;br /&gt;&lt;br /&gt;Quickly estimating, it'd take something like fifteen seconds to load up a hundred megs of models and textures on my laptop, maybe around 5 s on a decent computer. Doing the initial archive parsing pass would take maybe a second for a hundred meg archive. If that's too slow for you, I want your internet connection. If the hundred meg tarball is split 1:4 geometry:textures, where the geometry takes 20 bytes per tri and the textures are 10x compressed JPEGs, it'd have 1 Mtri geometry and 240 Mpx textures.&lt;br /&gt;&lt;br /&gt;The script doesn't handle gzip or any other compression, use gzip-encoding on the server for that. The &lt;a href="http://en.wikipedia.org/wiki/Tar_(file_format)"&gt;tar file format&lt;/a&gt; is pretty simple: it's based on 512-byte blocks and each file begins with a 512-byte header, followed by the file data padded up to a multiple of 512 bytes. The numbers are represented as octal ASCII (though there is a GNU tar extension that uses binary ints for handling files bigger than 8 GB, which my script doesn't support).&lt;br /&gt;&lt;br /&gt;My JSON packfile format consists of a one-line JSON header array of {filename : string, offset : bytes, length : bytes} followed by a newline and the concatenated file contents. Easy to create and parse.&lt;br /&gt;&lt;br /&gt;Edit: Added streaming using xhr.readyState == 3 checks. It might cause some stuttering on the page when dataURLing the images, though it should be quite efficient otherwise. Optimizations welcome :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5084834199813935720?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5084834199813935720/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5084834199813935720' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5084834199813935720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5084834199813935720'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/parsing-tarballs-with-javascript.html' title='Parsing tarballs with JavaScript'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8426532984109312837</id><published>2010-05-20T23:58:00.006+03:00</published><updated>2010-05-21T09:08:21.323+03:00</updated><title type='text'>Vanishing hardware</title><content type='html'>... Apparently the ethernet chip on my desktop computer motherboard completely disintegrated and now it doesn't even show up on lspci. All I know is that it disappeared after waking up from Windows 7 sleep mode.&lt;br /&gt;&lt;br /&gt;Oh well, another excuse to upgrade.&lt;br /&gt;&lt;br /&gt;Edit: after putting Windows to sleep again and waking it up, it seems that it momentarily lost and then regained the SMBus controller as well (not the ethernet controller however), in the process deactivating Windows. How very Windows of it. A pox on it! A pox on it and its descendants to the seventh generation!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8426532984109312837?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8426532984109312837/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8426532984109312837' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8426532984109312837'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8426532984109312837'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/vanishing-hardware.html' title='Vanishing hardware'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-7103251152849794401</id><published>2010-05-20T01:35:00.003+03:00</published><updated>2010-05-20T01:53:13.147+03:00</updated><title type='text'>Energy use by economic output</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_yZA36-WQFmM/S_RnxE48i1I/AAAAAAAABrI/8ZCnii3ClUY/s1600/kwh_per_gdp.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 400px;" src="http://3.bp.blogspot.com/_yZA36-WQFmM/S_RnxE48i1I/AAAAAAAABrI/8ZCnii3ClUY/s400/kwh_per_gdp.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5473113539985836882" /&gt;&lt;/a&gt;&lt;br /&gt;Energy use in kWh per GDP per capita. Country data from &lt;a href="http://nationmaster.com"&gt;NationMaster&lt;/a&gt;, plotted with R. The green line is the LOESS regression curve. The blue lines are averages for top and bottom 100 countries (after removing the best and worst 10 countries), the pink lines are averages for countries ranked 11-20 from top and bottom.&lt;br /&gt;&lt;br /&gt;Roughly speaking, the general trend is five bucks GDP for every kWh of energy use. The least efficient countries get 2 $/kWh and the most efficient 25 $/kWh.&lt;br /&gt;&lt;br /&gt;Assuming that energy use follows economic growth and that the average annual global economic growth is 3%, we can extrapolate (hah) that the global energy use would equal the total output of the Sun at around the year 3125. Put that in your pipe and smoke it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-7103251152849794401?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/7103251152849794401/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=7103251152849794401' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7103251152849794401'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/7103251152849794401'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/energy-use-by-economic-output.html' title='Energy use by economic output'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_yZA36-WQFmM/S_RnxE48i1I/AAAAAAAABrI/8ZCnii3ClUY/s72-c/kwh_per_gdp.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4003620925019953886</id><published>2010-05-17T18:48:00.010+03:00</published><updated>2010-05-18T03:20:06.537+03:00</updated><title type='text'>A chain of trivia</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Chemical_makeup_of_the_human_body"&gt;Chemical makeup of the human body&lt;/a&gt; - Elements by mass 65% O, 18% C, 10% H, 3% N, 4% others. You're three times as oxygen-rich as the air you breathe. 98.7% of the molecules in your cells are water molecules (but only 65% of mass, water molecules are light). There are around 174 teramolecules of water per cell. Your DNA chromosomes are huge giant molecules about 5 billion times more massive than water molecules.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_yZA36-WQFmM/S_FzWDAypBI/AAAAAAAABq4/_dNrBDarMS8/s1600/human_elements.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 400px;" src="http://2.bp.blogspot.com/_yZA36-WQFmM/S_FzWDAypBI/AAAAAAAABq4/_dNrBDarMS8/s400/human_elements.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5472281844834804754" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_yZA36-WQFmM/S_GQkdYziKI/AAAAAAAABrA/C3HzgtgTu_4/s1600/human_elements_atoms.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 400px;" src="http://4.bp.blogspot.com/_yZA36-WQFmM/S_GQkdYziKI/AAAAAAAABrA/C3HzgtgTu_4/s400/human_elements_atoms.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5472313978270222498" /&gt;&lt;/a&gt;&lt;br /&gt;R-generated graphs of the first fifteen elements in the human body by mass and count. Hey, power law distributions!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.csua.berkeley.edu/~wuhsi/elements.html"&gt;Elements in Animals and Humans&lt;/a&gt; - what are the other elements used for? Deficiency in Magnesium causes tetany, muscle spasms.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Clostridium_tetani"&gt;Clostridium tetani&lt;/a&gt; - the  Tetanus is an anaerobic soil bacteria that seems to produce one of the most deadly neurotoxins for no good reason whatsoever. The &lt;a href="http://en.wikipedia.org/wiki/Tetanospasmin"&gt;Tetanus toxin&lt;/a&gt; binds to muscle nerve synapses and blocks the release of relaxation signals, causing death by bone-shattering spasms.&lt;br /&gt;&lt;br /&gt;C. tetani is closely related to &lt;a href="http://en.wikipedia.org/wiki/Clostridium_botulinum"&gt;Clostridium botulinum&lt;/a&gt;, another lethal anaerobic soil bacteria. The &lt;a href="http://en.wikipedia.org/wiki/Botox"&gt;Botulinum toxin&lt;/a&gt; (a.k.a. Botox) binds to muscle nerve synapses and blocks the release of activation signals, causing death by flaccid paralysis.&lt;br /&gt;&lt;br /&gt;The two above bacteria are &lt;a href="http://en.wikipedia.org/wiki/Gram-positive"&gt;Gram-positive&lt;/a&gt;, as opposed to &lt;a href="http://en.wikipedia.org/wiki/Gram-negative"&gt;Gram-negative&lt;/a&gt;, the difference between the two types being that Gram-positive bacteria retain a violet crystal stain whereas the Gram-negative ones can be re-stained with a different color.  The cause of this is apparently that Gram-negative bacteria have an outer layer around their cell walls that doesn't absorb the violet crystal stain as readily.&lt;br /&gt;&lt;br /&gt;Animal cells and protozoans don't have &lt;a href="http://en.wikipedia.org/wiki/Cell_wall"&gt;cell walls&lt;/a&gt;. Plant cell walls are made out of cellulose, bacteria cell walls consist of peptidoglycan. The antibiotic enzymes, &lt;a href="http://en.wikipedia.org/wiki/Lysozyme"&gt;Lysozymes&lt;/a&gt;, react with peptidoglycan, breaking a molecular bond in it to tear apart the bacterial cell wall (by contorting a sugar in it to an uncomfortable position that breaks easily). Fungi cell walls are chitin, as are the exoskeletons of insects. &lt;a href="http://www.ucl.ac.uk/GeolSci/micropal/diatom.html"&gt;Diatoms&lt;/a&gt;, those strange silicon phytoplankton of the seas, have cell walls made out of silicic acid. Diatom shells along with &lt;a href="http://www.ucl.ac.uk/GeolSci/micropal/radiolaria.html"&gt;Radiolarian&lt;/a&gt; shells form the siliceous ooze at the bottom of the sea.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Pelagic_sediments"&gt;Pelagic sediments&lt;/a&gt; are the result of the constant rain of sediment over the abyssal plains. The rain consists of dead plankton shells, remains of marine animals, micrometeorites and dust carried by rivers and winds. If you ever wondered where marble comes from, the chain starts here.  Calcareous plankton shells fall to the ocean floor, forming &lt;a href="http://www.enotes.com/earth-science/calcareous-ooze"&gt;calcareous ooze&lt;/a&gt;, the precursor of limestone, a type of sedimentary rock that consists of calcite (CaCO&lt;sub&gt;3&lt;/sub&gt;). The limestone then gets hammered in the forge of crustal plate collisions  and recrystallizes to marble, some of which was dug up in Attica at around 440 BCE and used to build the &lt;a href="http://en.wikipedia.org/wiki/Parthenon"&gt;Parthenon&lt;/a&gt;, the CaCO&lt;sub&gt;3&lt;/sub&gt; of which is now being dissolved to aqueous Ca&lt;sup&gt;2+&lt;/sup&gt;, SO&lt;sub&gt;4&lt;/sub&gt;&lt;sup&gt;2-&lt;/sup&gt;, H&lt;sub&gt;2&lt;/sub&gt;O and CO&lt;sub&gt;2&lt;/sub&gt; in a reaction with sulfuric acid (H&lt;sub&gt;2&lt;/sub&gt;SO&lt;sub&gt;4&lt;/sub&gt;) from &lt;a href="http://www.chemistry.wustl.edu/~edudev/LabTutorials/Water/FreshWater/acidrain.html"&gt;acid rain&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The clouds covering &lt;a href="http://en.wikipedia.org/wiki/Venus"&gt;Venus&lt;/a&gt; are composed of sulfuric acid and it rains sulfuric acid in the upper atmosphere, but the rain evaporates around 25 km above the surface where the temperature hits 300 °C. The surface temperature of Venus is around 470 °C, which is close to &lt;a href="http://en.wikipedia.org/wiki/Draper_point"&gt;Draper point&lt;/a&gt; (525 °C) where solid materials emit clearly visible blackbody radiation. Maybe dark places on Venus could glow a faint dull red.&lt;br /&gt;&lt;br /&gt;The deep-sea hydrothermal vents &lt;a href="http://www.whoi.edu/page.do?pid=12458&amp;tid=282&amp;cid=2402"&gt;glow&lt;/a&gt; as well, but with a spectrum too blue to explain with just blackbody radiation from the hot 350 °C water. The higher frequencies might be from perturbation caused by the dynamic environment, namely sonoluminescence from tiny bubbles bursting and crystallo- and triboluminescence from crystals forming and breaking.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4003620925019953886?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4003620925019953886/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4003620925019953886' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4003620925019953886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4003620925019953886'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/surfing-chain-of-trivia.html' title='A chain of trivia'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_yZA36-WQFmM/S_FzWDAypBI/AAAAAAAABq4/_dNrBDarMS8/s72-c/human_elements.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5063687924708219222</id><published>2010-05-16T04:41:00.008+03:00</published><updated>2010-05-16T05:59:53.137+03:00</updated><title type='text'>Athenian Democracy</title><content type='html'>Listening to this &lt;a href="http://academicearth.org/lectures/athenian-democracy"&gt;Athenian Democracy lecture by Donald Kagan&lt;/a&gt;. The &lt;a href="http://en.wikipedia.org/wiki/Athenian_democracy"&gt;Wikipedia article&lt;/a&gt; is  detailed too.&lt;br /&gt;&lt;br /&gt;Ancient Athens of the time had 100,000 citizens, of whom maybe 30,000 were politically eligible (male citizens of 20+ years of age). They had political meetings every ten days to discuss and set policy about everything with majority votes. These political meetings had maybe 5,000 people present each time, but there were some votes that required 6,000 or more present to be binding.&lt;br /&gt;&lt;br /&gt;Compare that to a modern representational democracy such as Finland. 6 million citizens, around 4.2 million eligible (citizens of 18+ years of age). They vote once every four years to choose which parties should get parliamentary seats. The parliament numbers 200 and meets four times a week to set policy about everything with majority votes.&lt;br /&gt;&lt;br /&gt;If the Athenians had used a similar system (scaled by population), the number of members in their parliament would have been one.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5063687924708219222?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5063687924708219222/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5063687924708219222' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5063687924708219222'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5063687924708219222'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/athenian-democracy.html' title='Athenian Democracy'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-575282236975089407</id><published>2010-05-15T03:58:00.006+03:00</published><updated>2010-05-19T03:21:33.831+03:00</updated><title type='text'>Quick game reviews</title><content type='html'>The night is dark and long and I have nothing to do.&lt;br /&gt;&lt;br /&gt;Puzzle Quest, a character-building story battle game with movement done on a map and battles resolved in Bejeweled. 65 hours played. Addictive, not all that fun to play, the match-three matches take too long, the item &amp; spell combos are fun to come up with but haven't gotten powerful enough to shorten the matches. The art is nice but the gameplay GUI art is not so nice. Story is whatever but has a non-stereotypical stereotypical fantasy world with the player helping everyone regardless of what they want to do. And what they want to do always seems to involve playing Bejeweled against megafauna / Bob Warlord. Playable in 15-min increments in the same way that you can smoke just one cig a day. Story: A bad guy is resuscitating people and no-one wants to help and minotaurs are turning into robots to kidnap dragons and orcs and ogres are being belligerent and you must put some other minotaur bad guy together and resuscitate it so that it can fight the other bad guy and my head is hurting.&lt;br /&gt;&lt;br /&gt;Company of Heroes, a WW2 RTS about the U.S. experience on the Western Front to take out of the remaining 20% of the Nazi army. 19 hours played. Nice graphics, jaggedy shadow maps, paintings shown between missions lack finish. Battles take too long, three guys building a tank factory to build an endless supply of Shermans makes it very RTS gamey. Not casual, takes hours per campaign map. Cover system good. UI less good than in SupCom. Not all that fun to play, but not addictive either. Story: terrorists have taken over Europe and you must kill them all. I guess?&lt;br /&gt;&lt;br /&gt;The Misadventures of P.B. Winterbottom, a cloning-based puzzle platformer about pies. One hour played. Good presentation. Strong style. Good music loops. Figuring out the tricks to the levels is fun but means frustration, which is less fun. Not quite casual, requires ~half hour per session. Story very wtf.&lt;br /&gt;&lt;br /&gt;Penny Arcade Adventures Episode 2, a character-building story battle game with movement done by walking characters around sets and battles resolved in turn-based combat. Half an hour played (plus Episode 1). Much like Episode 1. Maybe too much. Gabe's special attack minigame is no longer the most frustrating thing ever, which is good. Good presentation, strong style. Has battle-helping items with a time penalty to utilize, making one less likely to bother. Not quite casual, half hour sessions. Story: there are more robots and you should break them all.&lt;br /&gt;&lt;br /&gt;Star Wars: Knights of the Old Republic, a character-building story battle game with movement done by walking characters around sets and battles resolved in pausable real-time combat. 1.5 hours played. Good presentation. Oh god do I have to talk to everyone here and beat up military people and this racist city is very boring why do I have a sword and a glow-bolt pistol where should I go now this is a lot like Baldur's Gate I'm assigning skill points to some skills I don't know anything about, hey I'm dying in these combat sequences, how quaint. I'll play more at a later date (said five months ago.) I guess it has a story to it?&lt;br /&gt;&lt;br /&gt;Torchlight, a character-building story battle game with movement done by walking characters around sets and battles resolved in real-time combat. 41 hours played. Nice presentation, good ambient music, randomly generated maps, way too many drops, decent feel of POWER in attacks (I would still take it up a notch but I'm weird like that), fiddly targeting. Addictive. If Diablo 2 is Zangband (well, noooot really but), this is Moria. Go down down a dungeon, down down down. Down. When done with the story dungeon, there's a new random dungeon that never ends. The random dungeon generation system is nifty. Summoning golems is nifty. Lightnings and laser beams are nifty. Can be played in short stints but takes a while to load and is kinda iffy windowed. Story: There's a McGuffin in the earth and it turns you into a monster. Except not really. Only the bad guy can do that. In the meantime you just genocide everything living down there because they're hindering your walk.&lt;br /&gt;&lt;br /&gt;Warhammer 40k: Dawn of War 2, a character-building story battle game with movement done by clicking on a map and battles resolved in tactical RTS-ish real-time combat. Played 20 hours. Randomly generated maps (I.. think.) Boss fight at the end of each map. Playing it feels like using Photoshop, very much a keyboard shortcut game. Move your four squads of bald robot men from cover to cover in a very micro fashion. Starts off challenging, gets difficult, mocks you for doing less than perfectly. Game gets uglier the more you screw up (invading monster army turns the air green with red flakes). Not casual, maps take hours to play and you're punished for failing. Presentation much like Company of Heroes (same engine, same studio) but with more shiny metal bits and glowy glow guns. Story: an invasive space species carried by the space winds is taking over space planets and the indigenous surface dwellers try to chase it off before it turns their C-H-O into its C-H-O.&lt;br /&gt;&lt;br /&gt;CoH vs. DoW2: In WW2 you try to look like a tree or a grass because you are a hunter and don't want to be spotted. In the grim future where there is only war you wear colorful shiny clothes because war is waged as an amusement and proving ground for the aristocrats, so you must have bright clothes to show off the colors of your team.&lt;br /&gt;&lt;br /&gt;Trees and grass are a form of water and air that absorb solar radiation to turn more water and air into tree and grass. Hence they try very hard to not be shiny reflective, hence clothes that try to look like they're trees and grass are also not shiny, hence a WW2 infantry game is not shiny. Also, you're more likely capable of digesting plastic than metal. Because plastic is made of C-H-O, same as you. Metal is not what you're made out of, so you can't turn it into your own mass, so you don't have a way to digest it. And you're mostly made out of air and water too. With some small gunks of dissolved crust elements such as Ca, P, K, S, Cl, Na, Mg, Fe.&lt;br /&gt;&lt;br /&gt;Beyond Good &amp; Evil, a story game with photography and occasional real-time combat, movement by walking characters on sets. Two hours played. Good presentation. Strong style. Controls on PC quite horrible. Performance problems. Tells a story, creates a world. Should finish it.&lt;br /&gt;&lt;br /&gt;Trine, a character-building physics puzzle platformer with real-time combat. 1.5 hours played. Very nice level graphics and main character models. Paintings less nice. Somewhat frustrating. Story whatever at least up to now. Puzzle Quest ate all my time so I haven't played this much. Story: A McGuffin spares game designer from having to come up with a way to have three separate characters working together in a single-player action game.&lt;br /&gt;&lt;br /&gt;Prince of Persia (2008), a running and jumping game with real-time combat. Played it through. Production values through the roof. Fun to play, pretty scenery, good sounds, strong style, non-obtrusive storytelling. You're constantly being saved. Story: bad gods help people, good gods do nothing, felling trees is bad but sometimes you gotta do it.&lt;br /&gt;&lt;br /&gt;Hammerfight, a skill-based real-time combat game. Played the demo through. Super difficult, but good impact when your attacks connect. GUI graphics rather awesome, game graphics good too with glowing colors and banners waving in the wind. Slow renderer, most lacking in fillrate for smoke. Talking heads in story not so good. Story: Neighbouring emperor wants to conquer you! And he succeeds! Man.&lt;br /&gt; &lt;br /&gt;Why do I buy these games if I don't play them? Probably because it's easy and they're cheap. And grocery shopping desensitizes you to paying 10e for something.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-575282236975089407?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/575282236975089407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=575282236975089407' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/575282236975089407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/575282236975089407'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/quick-game-reviews.html' title='Quick game reviews'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-5502954304158038413</id><published>2010-05-13T23:48:00.008+03:00</published><updated>2010-05-14T10:51:05.563+03:00</updated><title type='text'>Git commit stats</title><content type='html'>Tonight I wrote a little tool in Ruby to print out stats based on git log output. See &lt;a href="http://github.com/kig/gitnuts"&gt;the project page on GitHub&lt;/a&gt;. (Hey, apparently there's a &lt;a href="http://gitstats.sourceforge.net"&gt;ready-made tool&lt;/a&gt; for this. And it has the same name too! Better change the name of my script to gitnuts.)&lt;br /&gt; &lt;br /&gt;Here are the weird things it tells about my commits on a project:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;                  Commits: 1365&lt;br /&gt;            Changed files: 2483&lt;br /&gt; Changed files per commit: 1.8&lt;br /&gt;        Total added lines: 86277&lt;br /&gt;      Total deleted lines: 22628&lt;br /&gt;      Total changed lines: 108905&lt;br /&gt;         Added per commit: 63.2&lt;br /&gt;       Deleted per commit: 16.6&lt;br /&gt;       Changed per commit: 79.8&lt;br /&gt;&lt;br /&gt;Changed lines per commit&lt;br /&gt;Bucket size: 10&lt;br /&gt;Bar step size: 9&lt;br /&gt;      0 *********************************************************** 538&lt;br /&gt;     10 ******************** 187&lt;br /&gt;     20 ***************** 161&lt;br /&gt;     30 ********** 98&lt;br /&gt;     40 ******* 68&lt;br /&gt;     50 ****** 62&lt;br /&gt;     60 **** 43&lt;br /&gt;     70 *** 33&lt;br /&gt;     80 * 14&lt;br /&gt;     90 ** 18&lt;br /&gt;    100 * 17&lt;br /&gt;    110 * 15&lt;br /&gt;    120 ** 21&lt;br /&gt;&lt;br /&gt;Commits per month&lt;br /&gt;Bar step size: 7&lt;br /&gt;2008-01 ***** 35&lt;br /&gt;2008-04 **************** 117&lt;br /&gt;2008-05 **************************************************** 367&lt;br /&gt;2008-06 ********************************************************** 411&lt;br /&gt;2008-07 ******************************************** 313&lt;br /&gt;2008-08 **************************************** 286&lt;br /&gt;2008-09 ********************* 148&lt;br /&gt;2008-10 ** 15&lt;br /&gt;2009-01 *** 22&lt;br /&gt;2009-02 **** 32&lt;br /&gt;2009-10 ************* 92&lt;br /&gt;2009-11 *********** 77&lt;br /&gt;2009-12 * 9&lt;br /&gt;2010-01 ****************************************** 295&lt;br /&gt;2010-02 ********** 71&lt;br /&gt;2010-03 ************ 89&lt;br /&gt;2010-04 * 13&lt;br /&gt;2010-05 ********** 76&lt;br /&gt;&lt;br /&gt;Commits per weekday&lt;br /&gt;Bar step size: 7&lt;br /&gt;  0-Sun ***************************************** 291&lt;br /&gt;  1-Mon ****************************************** 298&lt;br /&gt;  2-Tue ********************************************* 317&lt;br /&gt;  3-Wed ********************************************************** 406&lt;br /&gt;  4-Thu ********************************************************** 408&lt;br /&gt;  5-Fri ********************************************************* 404&lt;br /&gt;  6-Sat *************************************************** 359&lt;br /&gt;&lt;br /&gt;Commits per hour of day&lt;br /&gt;Bar step size: 3&lt;br /&gt;      0 ************************************************ 145&lt;br /&gt;      1 ******************************************************* 165&lt;br /&gt;      2 ************************************ 108&lt;br /&gt;      3 ********************************** 103&lt;br /&gt;      4 ***************************************** 124&lt;br /&gt;      5 **************************************** 121&lt;br /&gt;      6 ************** 42&lt;br /&gt;      7 ****************** 54&lt;br /&gt;      8 ******************* 57&lt;br /&gt;      9 ************ 37&lt;br /&gt;     10 *************** 46&lt;br /&gt;     11 ******************** 62&lt;br /&gt;     12 *************************** 82&lt;br /&gt;     13 ********************** 68&lt;br /&gt;     14 ********************************** 104&lt;br /&gt;     15 *********************************** 107&lt;br /&gt;     16 ************************************ 109&lt;br /&gt;     17 ********************************** 103&lt;br /&gt;     18 *************************************** 118&lt;br /&gt;     19 ********************************************* 135&lt;br /&gt;     20 **************************************** 122&lt;br /&gt;     21 ****************************************** 127&lt;br /&gt;     22 ****************************************************** 164&lt;br /&gt;     23 ************************************************************ 180&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;How about some chartjunk courtesy of R? Check color identifies commit author, y-axis is log10 of change size + 1, x-axis is time in seconds from epoch. The lines are &lt;a href="http://stat.ethz.ch/R-manual/R-patched/library/stats/html/lowess.html"&gt;lowess&lt;/a&gt; smoothings of the raw data.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_yZA36-WQFmM/S-yHEv0cjAI/AAAAAAAABqw/s12rCRfxUeM/s1600/plot.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 260px;" src="http://1.bp.blogspot.com/_yZA36-WQFmM/S-yHEv0cjAI/AAAAAAAABqw/s12rCRfxUeM/s400/plot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5470896162973977602" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-5502954304158038413?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/5502954304158038413/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=5502954304158038413' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5502954304158038413'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/5502954304158038413'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/git-commit-stats.html' title='Git commit stats'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_yZA36-WQFmM/S-yHEv0cjAI/AAAAAAAABqw/s12rCRfxUeM/s72-c/plot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8030111867214131622</id><published>2010-05-08T03:44:00.001+03:00</published><updated>2010-05-08T03:48:55.966+03:00</updated><title type='text'>Effort estimation game first round results</title><content type='html'>&lt;h3&gt;First round results&lt;/h3&gt;&lt;br /&gt;Task 1 estimated: 50 lines, 30 min implementation, +15 min making it work right. &lt;br /&gt;Task 1 measured: 55 lines, 60 min implementation, +15 min making it work right.&lt;br /&gt;Task 1 adjustment factors: 1.1x lines, 2.0x implementation time, 1.0x fix time.&lt;br /&gt;&lt;br /&gt;Task 2 estimated: 10 lines, 10 min implementation, +15 min fix time.&lt;br /&gt;Task 2 measured: 39 lines, 15 min implementation, +12 min fix time.&lt;br /&gt;Task 2 factors: 3.9x lines, 1.5x impl, 0.8x fix time.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Second round&lt;/h3&gt;&lt;br /&gt;Task 3 estimated: 150 lines, 8 hours implementation, 17 hours fix time.&lt;br /&gt;&lt;br /&gt;Task 3 estimates adjusted with factors from tasks 1 &amp; 2:&lt;br /&gt;375 lines, 14 hours implementation, 15 hours fix time.&lt;br /&gt;Let's see how it goes...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8030111867214131622?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8030111867214131622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8030111867214131622' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8030111867214131622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8030111867214131622'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/error-estimation-game-first-round.html' title='Effort estimation game first round results'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-3316166410798584614</id><published>2010-05-05T23:59:00.004+03:00</published><updated>2010-05-08T03:47:41.580+03:00</updated><title type='text'>Bayesian effort estimation game</title><content type='html'>Update: &lt;a href="http://fhtr.blogspot.com/2010/05/error-estimation-game-first-round.html"&gt;First round results&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;How accurately can you estimate how much effort it takes you to write a new piece of code? Fear not, I have a solution! An untested solution! Very academic, you might say!&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Compile a list of features of different sizes that you're going to implement.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Pick a small feature for starters.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Estimate the effort required for it: lines of code, time to "it compiles!", time spent fixing bugs, time spent testing, time spent documenting.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Document the effort: Write in your notebook when you start and stop working on the feature, and the lines of code (look at the commit data). If you don't work on the feature, write why.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;When you're done with an estimated task, compare the estimate and the effort by computing the effort/estimate -ratio for the task. This is your new a priori estimate error for that task.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Pick a new feature, estimate, multiply the estimate by the a priori estimate error.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;When completing the new feature, calculate its effort/estimate -ratio. Add the ratio to the list of ratios, use their average as the new a priori estimate error. When you have a bunch of errors, plug them into R and start keeping track of their mode, standard deviation and whatever useful statistical variables you can think of. Plot a curve for estimate error per task size.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The goal of the game is to keep the average estimate error of the previous 10 tasks as close to 1 as possible as long as possible.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Take photographs of the people playing this game and make trading cards of them with seasonal averages and all the interesting statistics you collected earlier.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Sell the trading cards to kids.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Profit!&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-3316166410798584614?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/3316166410798584614/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=3316166410798584614' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3316166410798584614'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/3316166410798584614'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/05/bayesian-effort-estimation-game.html' title='Bayesian effort estimation game'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-2875554238026540257</id><published>2010-04-30T20:04:00.009+03:00</published><updated>2010-04-30T21:55:25.735+03:00</updated><title type='text'>Drawing with writing</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_yZA36-WQFmM/S9sNq3RLsQI/AAAAAAAABms/1ym6FX2gkCc/s1600/drawing_with_writing.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 308px;" src="http://4.bp.blogspot.com/_yZA36-WQFmM/S9sNq3RLsQI/AAAAAAAABms/1ym6FX2gkCc/s400/drawing_with_writing.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5465977602785915138" /&gt;&lt;/a&gt;&lt;br /&gt;The four faces on the left are made up of Hanzi/Kanji radicals, the middle face is hiragana, the left-side faces are Hangeul, Latin, and Greek. The face left of the bunny and the bunny itself are Cyrillic.&lt;br /&gt;&lt;br /&gt;One thing that's been interesting me during the writing system study is how writing systems affect the drawings of the people who use them. I mean, if you're going to draw something, you're going to use the strokes and patterns you already know. And the patterns you know best are the ones you use every day, so the writing system should have a impact on the drawings. There seems to be a feedback effect at work here: the things you draw turn into writing and the writing guides your drawing.&lt;br /&gt;&lt;br /&gt;To explain, drawing and writing are both ways to communicate information visually. The old writing systems were in fact formalized alphabets of simplified drawings (or not so simplified drawings in the case of Egyptian hieroglyphs). And these drawings were based on the surrounding nature of the time the writing system was made, hence you have cobras, pintail ducks, flamingos, jackals, crocodiles and hippos in the hieroglyphs. Had it been a Northern European writing system, it would've had wolves, elks and bears instead of the Nile fauna.&lt;br /&gt;&lt;br /&gt;One example of writing systems affecting drawing that's easy to reason about are &lt;a href="http://en.wikipedia.org/wiki/Emoticon"&gt;emoticons&lt;/a&gt;. You mostly do emoticons that are easy to type and don't get mangled in the transmission. With a Latin keyboard layout you get emoticons with Latin characters and punctuation. With a Korean keyboard, you get Korean emoticons. There's a feedback effect here as well: emoticons create an alphabet of emotions, that you then use in your drawings as a shorthand for the emotions in question. And when you draw an emotion without an emoticon, someone might figure out a way to simplify that drawing into an emoticon, which is then added to the alphabet and used by people drawing emotions...&lt;br /&gt;&lt;br /&gt;And then you have &lt;a href="http://en.wikipedia.org/wiki/Technical_drawing"&gt;writing systems designed for drawing&lt;/a&gt;. Perhaps I could segue from drawing writing systems to learning circuit analysis.&lt;br /&gt;&lt;br /&gt;To take this drawing with writing thing further, Islamic calligraphy is one famous example:&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Islamic_calligraphy"&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/6/6a/Caligrafia_arabe_pajaro.jpg"&gt;&lt;/a&gt;&lt;br /&gt;&lt;small&gt;Image courtesy of &lt;a href="http://en.wikipedia.org/wiki/Islamic_calligraphy"&gt;Wikipedia&lt;/a&gt;.&lt;/small&gt;&lt;br /&gt;&lt;br /&gt;And the Egyptian Book of the Dead... those big guys there are more like 180pt calligraphy than drawings. Even their sculptures veer towards being big 3D letters carved from stone.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Book_of_the_Dead"&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/BD_Hunefer.jpg/800px-BD_Hunefer.jpg"&gt;&lt;/a&gt;&lt;a href="http://en.wikipedia.org/wiki/File:Tutanhkamun_jackal.jpg"&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/1/1f/Tutanhkamun_jackal.jpg"&gt;&lt;/a&gt;&lt;br /&gt;&lt;small&gt;Images courtesy of Wikipedia. Click to see the pages.&lt;/small&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-2875554238026540257?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/2875554238026540257/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=2875554238026540257' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2875554238026540257'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/2875554238026540257'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/04/drawing-with-writing.html' title='Drawing with writing'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_yZA36-WQFmM/S9sNq3RLsQI/AAAAAAAABms/1ym6FX2gkCc/s72-c/drawing_with_writing.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-8694822379792097018</id><published>2010-04-30T02:00:00.003+03:00</published><updated>2010-04-30T02:41:35.063+03:00</updated><title type='text'>That hand-wavy movie GUI</title><content type='html'>&lt;object width="601" height="338"&gt;&lt;param name="allowfullscreen" value="true" /&gt;&lt;param name="allowscriptaccess" value="always" /&gt;&lt;param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=2229299&amp;amp;server=vimeo.com&amp;amp;show_title=0&amp;amp;show_byline=0&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" /&gt;&lt;embed src="http://vimeo.com/moogaloop.swf?clip_id=2229299&amp;amp;server=vimeo.com&amp;amp;show_title=0&amp;amp;show_byline=0&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="601" height="338"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://oblong.com"&gt;Oblong's&lt;/a&gt; G-speak prototype (G stands for Gesture, as far as I can tell). They consulted on the Minority Report movie, which then inspired them to build an actual prototype. (Via &lt;a href="http://twistedphysics.typepad.com/cocktail_party_physics/2010/04/beautiful-tools.html"&gt;Cocktail Party Physics&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;Who do you sell that to? Museums? Universities? Put it in lecture halls, have the professors wave through their slide decks? Automotive design houses might buy a couple to augment their VR caves. Air traffic controllers... kind of, except that it needs to be either secondary or a proven stable system.&lt;br /&gt;&lt;br /&gt;It is the new technology dilemma: no current applications, new hardware, no exact target market. (Or very entrenched "target markets" that want proven products which have jumped through all the regulatory hoops and are backwards compatible and have an excellent sales force to wine and dine the people holding the strings of the corporate purse.)&lt;br /&gt;&lt;br /&gt;I suppose the largest market would be semi-wealthy individuals. Strap it onto a 50" HDTV, have them wow visitors with awesome gesture-driven slideshows of holiday pics and the way you can change channels and surf the web from your couch. But without a remote or a keyboard. Worked for the iPad.&lt;br /&gt;&lt;br /&gt;You can't replace the computer desktop with it, because the desktop has infinite inertia. And you need to rewrite all the apps to fit that thing. It does sound like they do need to use a carbon copy of the iPad strategy: have an App Store and integrate media stores and the web, require creation of a Store account to use it, have it be a not-computer (to not set high expectations and to avoid having to compete with the strong competitors), make it look cool and different (again to avoid comparisons), do the cool things well and basic things passably, sell an upgrade a year later ("It fixes all your problems!")&lt;br /&gt;&lt;br /&gt;Extract money from people entering the platform, extract money from people on the platform, extract money from people upgrading the platform, extract money from people who want to sell things to people on the platform. Fight very very hard to maintain your grip on the money pipes.&lt;br /&gt;&lt;br /&gt;Oh, also, I drew a &lt;a href="http://globaloid.blogspot.com/2010/04/reflector-vests.html"&gt;new picture&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-8694822379792097018?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/8694822379792097018/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=8694822379792097018' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8694822379792097018'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/8694822379792097018'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/04/that-hand-wavy-movie-gui.html' title='That hand-wavy movie GUI'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5103406499726689782.post-4441621920597677279</id><published>2010-04-28T14:43:00.007+03:00</published><updated>2010-04-28T17:54:34.818+03:00</updated><title type='text'>Early (and recent) computer art</title><content type='html'>&lt;a href="http://translab.burundi.sk/code/vzx/index.htm"&gt;VISUAL AESTHETICS IN EARLY COMPUTING (1950-80)&lt;/a&gt; (via &lt;a href="http://chneukirchen.org/trivium/"&gt;Trivium&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;It's the demoscene, 50s-style!&lt;br /&gt;&lt;br /&gt;Or maybe something akin to &lt;a href="http://www.youtube.com/watch?v=R5mQI8159Ho&amp;feature=related"&gt;modern dance with a webcam-operated CFD backdrop&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Speaking of the &lt;a href="http://pouet.net"&gt;demoscene&lt;/a&gt;, here are a couple recent productions:&lt;br /&gt;&lt;br /&gt;Fairlight Cncd - Agenda Circling Forth. Millions of particles. Nice forest scene.&lt;br /&gt;&lt;br /&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/ON4N0yGz4n8&amp;hl=en_US&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/ON4N0yGz4n8&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;Farbrausch - fr-043: rove. Good ambience.&lt;br /&gt;&lt;br /&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/k_oTQd93eRI&amp;hl=en_US&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/k_oTQd93eRI&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;United Force &amp; Digital Dynamite - Wir sind Einstein. See also &lt;a href="http://www.youtube.com/watch?v=R5mQI8159Ho&amp;feature=related"&gt;The Golden Path&lt;/a&gt; for more trippiness.&lt;br /&gt;&lt;br /&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/vvnMzGf7FGk&amp;hl=en_US&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/vvnMzGf7FGk&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;I do wonder if we'll ever have user interfaces with those kinds of production values. The recent trend has been to lock the GUI down so hard that you can't even change the theme, sometimes you can't even change the colors (OS X, I'm looking at you.) On the other hand, web browsers seem to be picking up some easy themeability with those background-image themes of Chrome and Firefox. Squeeze creativity out from one place and it goes to another.&lt;br /&gt;&lt;br /&gt;But still. There aren't many GUIs that look nice and fun. Well, except in games. In games they're the selling point. The fancier and better your game GUI looks (i.e. the thing we call "graphics"), the better your game will sell. It's all about the experience, the fancy wrapping around the game logic crack cocaine. Sure, you want to _play_ the game, but having the process of doing that look and feel awesome makes the experience so much better.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5103406499726689782-4441621920597677279?l=fhtr.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://fhtr.blogspot.com/feeds/4441621920597677279/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5103406499726689782&amp;postID=4441621920597677279' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4441621920597677279'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5103406499726689782/posts/default/4441621920597677279'/><link rel='alternate' type='text/html' href='http://fhtr.blogspot.com/2010/04/early-and-recent-computer-art.html' title='Early (and recent) computer art'/><author><name>Ilmari Heikkinen</name><uri>https://profiles.google.com/115293744081058969329</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh4.googleusercontent.com/-hGdc5eNUzzM/AAAAAAAAAAI/AAAAAAAAGrE/a-ZpyFpUcaU/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
