fhtr

art with code

2017-11-27

Nokia 8, a week later

[Update 29 Nov 2017] There's a bug with the glance display feature where unlocking the phone after the glance display turns on drops the UI frame rate to 25 or so. This is super annoying. The fix is to turn off the glance display in Settings > Display > Advanced > Glance.

Camera. Hmm. So, I took the Camera2 API sample and built the basic demo camera app to explore wtf is wrong with the camera. Long story short, it's the camera driver + camera app. There are a couple of factors at play here.

First, the bundled camera app is slow to start. You can use the Footej Camera app for much faster startup and manual controls.

Second, the slow shutter speed is due to the camera driver auto-exposure liking slow shutter speeds. And maybe because rolling shutter + AC-induced light flicker makes for stripey pictures at fast shutter speeds and the AE flicker compensation goes "oo, better use a 14Hz shutter to get more even exposure in 50Hz lights".

The viewfinder is slow because it uses the AE shutter speed.

And there's no S-mode because the Camera2 API doesn't have one. There are only P and M modes.

The weirdo colors in very low light, who knows. I'll have to investigate.

And now I've got the beginnings of a camera app that works the way I like. Send money! Like, $25k. (In the words of Burnaburias: "My work in the houses of the Gods is abundant, and now I have begun an undertaking: Send much gold!")
[/Update]


Having grown more accustomed to the Nokia 8, here's the rest of the review:

- It's fast. Minimal jank (on Nougat at least). Better than the Samsung phones in that respect, they've got some UI thread resource loading stuff in the launcher that makes your first impression of the phone be "oh it's dropping frames". The Nokia's speediness is thanks to them using the Google launcher.

- The shape feels good to the hand. The bundled case has a nice feel to it. The case is very tight on the phone, it hasn't come off after dropping the phone on the floor a few times (Tho the case did crack in the corners. The phone is a-OK, thanks case.)

- I like the ringtones. The ringer volume can be set very loud, which is great outdoors.

- The camera is fine in daylight. The color profile is quite Zeiss - a sharp yellow-green with natural saturation levels and luminous whites. I guess. Could be placebo effect / just the light here.

- The camera is useless indoors. Everything's blurry, the focus is all over the place, the color balance is bananas (like, desaturated blue-green bananas veering towards gray).

- The screen's white point is very cold. 8000K+. Compared to the 6700K used on other devices, it's blue blue blue. Not good in the evening, though Oreo's Night Light helps a lot there.



On CSS Modules

In our most recent project, we used React with CSS Modules. Each component had its own SASS stylesheet module in addition to the global stylesheet. This was pretty great in that I could work on one component without having to worry about my code or stylesheet changes impacting other components.

CSS Modules also cut down on merge conflicts, as we could keep the changes localised. If we had a single massive stylesheet, every time two people are styling components, there'd be a pretty big chance of a merge conflict and repo messiness. Moving to a pull request -driven workflow might work there, but it seems overkill for two-person projects.

One thing we didn't figure out was how to customize the look of a sub-component. E.g. we had a bunch of share buttons that had the same functionality but different parameters and looks. We could've made a separate component for each, but then we'd have to maintain multiple components.

In the end we went with ".shareButtons > div > a"-type CSS selectors for the share buttons. Which sucks.

I don't know if there's exists a better way. For self-written components, can always pass in props to change the styles, but for third-party components that'd be a mess. Maybe CSS Modules could adopt some Shadow DOM -style things to point inside included modules. Shadow DOM's CSS Custom Properties and inheritance -driven styling ("share-button { share-image: url(foo.jpg); color: white; }") is a bit too "here's an invisible API, have fun" to my tastes. It does get you looser coupling, but that's better done with props. For nitty-gritty styling you want to tighly couple your custom style to the component DOM because component authors can't be expected to add a custom var for every single possible style tweak. And when you do that kind of coupling, you're expected to fix it to a specific version of the component and you're visually testing it all the time anyway (I mean, that's the whole point of overriding parts of the component stylesheet).

Maybe if there was a nice way to explicitly override component stylesheets from the outside. Qualified imports? "@import('MyShareButton') as sb; sb::.container > sb::.button { letter-spacing: 0.1em; }". That ::. sure is hard to visually parse. Maybe it's a function "sb(.container) > sb(.button) {}" or "sb(.container > .button) {}" or "sb(.container { .button {} .batton {} })"?


2017-11-10

Nokia 8 first impressions, iPhone X thoughts

[Update, Nov 26 Oreo update.] Gmail badge fixed, phone network name fixed, I can abuse the Night Light mode to tone the display white point warmer (by default it's super cold blue light: bright outdoors but eye-stabbing indoors. I've found no other way to adjust the white point but setting the Night Light mode always on (Custom Schedule 6am to 5:59am) at a low intensity.) Oreo likes to drop the UI framerate to 30fps for no reason, which is annoying. After a few minutes it jumps back to 60fps and ???? What's going on. Overheating from Night Light? Who knows, let's see if goes away after a few days... [Update 2] It's a Glance display bug. Turn off Glance display and it stops happening.


Got the tempered blue version of the Nokia 8. Around US$500 in Hong Kong. The chassis looks and feels great. The phone is very fast. Loud speaker. The LCD screen's great and shows the clock, calendar events and notifications when it's off.

The dark blue matte aluminium gets a bit smudged by fingerprints. Might be less of an issue with a silvery color - my ancient iPod Touch 5 doesn't smudge. The gold-colored iPad does smudge though. (Digression: the iPod Touch 5 is maybe the best-looking phone form factor Apple device. iPhones in comparison are marred by the antenna lines and weird design choices. Apart from the 3G, which is a great design but a bit thick. The new glass-back iPhones are better in terms of the overall design, but they're glassy.)

Back to the Nokia 8. Gmail shows a 9,999+ unread mails red badge. This is solvable, my Samsung Note 5 doesn't have that. [Fixed in the Oreo update]

In daylight, the camera quality is great. Indoors, the camera app optimizes for ISO instead of shutter speed, which makes taking photos of a bouncing six-month old an exercise in motion blur (thx 1/14 shutter speed at ISO 250.)

The camera hardware is not bad. Looking at full quality JPEG output, the noise is level is OK up to ISO 800. The camera app screws it up though.

The chassis design is generally excellent, very business-like. Might be the only phone around that doesn't look crazy. The volume and power buttons in particular look great. The shape feels good.

I've got a couple nitpicks though: the NOKIA logo on the back is recessed and looks like a sticker. It could be engraved or flush with the phone. The headphone jack doesn't have a shiny bevel and doesn't look great. The charging port might work with a bevel too. I'm not a fan of the "Designed by HMD, Made in China"-text at the back. The front face off-center Nokia logo placement is retro but grows on you. The front face fingerprint reader is recessed, which makes it gather specks of dust. Having it flush or with an Apple-style bevel would look better. The front-facing camera is a bit off-center from the hole in the face plate, and has a hard plastic looking border. This could be fixed with an alignment guide and a proper border material. Ditto for the back cameras. The front plate black plastic could maybe have a bit matte reflection so that it's not so plastic. The front speaker grille fabric gathers specks of dust. Metal mesh would be nice.

The phone appears as TA-1052 on the local network and Spotify. Which is.. confusing. [Fixed in the Oreo update]

The font on the lock screen and on the home screen clock is bolder than the glance screen font, and has a more spread out letter spacing. I prefer the glance screen thin font.

Good battery life.

Ok, that's it for the Nokia 8.

iPhone X. Played with it in the Apple Store. It works surprisingly well given the big hardware and UI overhaul. But, it's just an iPhone. Those were my thoughts, "Oh, it works surprisingly well." followed by ".. that's it?" That's really it. The software is iPhone, the identity is iPhone (with a bit of extra bling from the glass back). It's an iPhone.

The feel is quite Android though, with the bottom swipe to unlock and settings swipe down. The notch sucks in the landscape mode, looks ridiculous. The swipe gestures are invisible but quick to learn.

It's sort of like a Samsung version of the iPhone 4 in terms of the design language. Plus the notch.

The design philosophies are quite different. Samsung Note 8 is a slightly toned-down bling bling design phone. LG G6 is the techy phone searching for an identity. HTC U11 is the crazy night out phone. iPhone is the fancy party phone. Xiaomi Mi Mix 2 is the concept phone. Nokia 8 is the business phone - the dark blue two-piece suit.

Wrap up: Nokia 8 - slightly flawed execution of a great design, needs a better camera app. iPhone X - great execution of a flawed concept.

2017-10-21

Post-Brexit trade numberwaving

Looking at the OEC trade information for the UK and other countries, we can take some wild guesses at UK's future trade prospects. UK's GDP was $2.6T in 2016. UK exported $0.4T worth of goods and services, and imported $0.6T.

Going with the idea that the exports of a country are a pretty good indicator of its economy, let's gaze into our crystal ball.

UK's main export destinations are the EU at roughly 50% of total exports, the US at 15% and China at 4.5%. This is because the UK is located in Europe, so it's easy to export goods to Europe. It's much more difficult to export goods to the US, a country that shares a language with the UK. Indeed, the size of the EU economy without the UK is slightly smaller than the US economy, but the UK exports more than three times as much goods to the rest of the EU compared to the US.

Comparing the US and Chinese exports, we have a 3:1 ratio for the US, but the nominal GDPs are only 2:1. If we look at China vs India (around 1.1% of UK's exports), we have a 4:1 ratio in exports and a 5:1 in GDPs. What does this all mean? Exports are relative to GDP but there's quite a lot of play in ease of doing trade. (Also: Spain vs. Russia. Similar size economies, but the UK exports 4x more to Spain.)

Next, let's compare trade with EU vs. non-EU countries. Hungary and Morocco have economies of a  similar size (Hungary's GDP is 25% larger). The UK exports $1.8bn to Hungary and $1.13bn to Morocco. Each dollar in Hungarian GDP ends up buying 27% more UK exports than a dollar of Moroccan GDP.

How about US trade? Morocco exports $1.06bn to the US, compared to the $5.44bn exported by Hungary. China? Morocco exports $0.55bn to China. Hungary exports $3.46bn to China.

If the Hungary-Morocco comparison is apt (and hey, it probably isn't, how about comparing Austria and Israel (halve EU and China exports, double US exports)), breaking away from the EU might cause a 25% drop in exports to EU countries, and a potential 75% drop in exports to the US and China.

Going by that, the UK's future exports would be $0.17T to the EU, $16bn to the US and $5bn to China. Total exports would fall from $0.4T to $0.3T and the UK GDP would go from $2.6T to $1.95T.

(In the Austria-Israel-case, UK would go to $0.1T to the EU, $0.1T to the US, $0.01T to China, total exports $0.35T, GDP $2.3T.)

2017-10-09

Ethereum algorithmically

Ethereum is a cryptocurrency with a mining process designed to stress random access memory bandwidth. The basic idea in mining Ethereum is to find a a 64-bit number that hashes with a given seed to a 64-bit number that's smaller than the target number.

Think of it like cryptographic lottery. You pick a number, hash it, and compare the hash to the target. If you got a hash that's below the target, you win 5 ETH.

What makes this difficult is the hashing function. Ethereum uses a hashing function that first expands the 32-byte seed into a 16 MB intermediate data structure using a memory-hard hashing function (if you have less than X bytes of RAM, the hash takes exponentially longer to compute), then expands the 16 MB intermediate data structure into a multi-gigabyte main data structure. Hashing a number generates a pseudo-random walk through the main data structure, where you do 64 rounds of "read 128 bytes from location X and update the hash and location X based on the read bytes."

While the computation part of the Ethereum hashing function isn't super cheap, it pales in comparison to the time spent doing random memory accesses. Here's the most expensive line in the Ethereum compute kernel: addToMix = mainDataStructure[X]. If you turn X into a constant, the hash function goes ten times faster.

Indeed, you can get a pretty accurate estimate for the mining speed of a device by taking its memory bandwidth and dividing it by 64 x 128 bytes = 8192 B.

Zo. What is one to do.

Maximize memory bandwidth. Equip every 4 kB block of RAM with a small ALU that can receive an execution state, do a bit of integer math, and pass the execution state to another compute unit. In 4 GB of RAM, you'd have a million little compute units. If it takes 100 ns to send 128 bytes + execution state from one compute unit to another, you'd get 1.28 PB/s aggregate memory bandwidth. Yep, that's over a million gigabytes per second.

With a million GB/s, you could mine ETH at 150 GH/s. At the moment, 25 MH/s of compute power nets you about $1 a day. 150 GH/s would be $6000 per day. If you can fab ten thousand of them, you'd make sixty million a day. Woooooinflation.


2017-10-08

Fast marching cubes in JavaScript

Marching cubes! Where do they march? What is their tune? The name of their leader, a mystery if any.

Marching cubes works like this:

  1. You have a 3D array of bits and want to create a mesh out of it.
  2. Read a 2x2x2 cube from the array.
  3. Generate a mesh based on the values of the cube.
  4. Repeat for every 2x2x2 cube in the array and concatenate the meshes.

The individual cube meshes work like Lego blocks, they click together to form a seamless mesh.

How to do it kinda fast:

  1. Create cached meshes and normals for each different 2x2x2 bit cube (there are 2^8 of them). You get an array like cubeMeshes[eightBitCubeIndex].
  2. Create a binary array based on the original data. Unroll loops to process in chunks of 8, do it SIMD-like, pass over the original data and spit out ones and zeroes into a Uint8Array. (You could put 8 bits per byte, but it's a hassle.) 
  3. Create a cube index Uint8Array that's going to be filled with the eight-bit cube indexes of each 2x2x2 cube in the data.
  4. Fill the cube index array by marching a 2x2x2 cube over the binary array and converting the read cube values into eight-bit cube indexes. Increment total mesh vertex count by cubeMeshLengths[eightBitCubeIndex].
  5. Allocate Float32Arrays for vertices and normals based on the total mesh vertex count.
  6. Iterate over the cube index array. Write the mesh corresponding to the cube index to the vertex array, offset each vertex with the xyz-coordinates of the cube index. Write the normals corresponding to the cube index to the vertex array.

Source: fastIsosurface.js - demo

This runs in ~150ms on an Intel i7 7700HQ for a 7 million element data array (256x256x109).

Future directions

As you may notice from the source, it's SIMD-friendly, in case you can use SIMD. The algorithm parallelizes easily too.

Web Workers with transferable objects? Transform feedback in WebGL 2 + a reducer kernel to remove empty triangles? Do it in a fragment shader to a float render target? Magic?

Handwaving

The test dataset contains 7 million 16-bit uints which takes about 14 megabytes of RAM. This means that it won't fit in the 7700HQ's 4x1.5MB L3 cache, much less the 4x256kB L2 or the 4x32kB L1.

By compressing the dataset into a bit array, it would fit in 7 megabits, or 875 kB. Processing that with four cores (8 threads) would keep the read operations in the L2 cache. Chunking the processing into 30 kB cubes would keep the reads mostly in the L1 cache.

The output array for the marching cubes step consists of a byte per cube. The original input array has two bytes per element. The bit array has one byte or one bit per element. The output vertex arrays have up to 90 floats, or 360 bytes per cube (but they're very sparse, the average is 1-2 bytes per cube). There's roughly one cube per input array element.

Taking the sum of the above, we get about 1 + 2 + 1 + 1 = 5 bytes per cube. We could process 6000 cubes in a 32kB L1 cache. That might come to 64x10x10 input elements that output 63x9x9 cubes for total memory use of 29406 bytes and 5103 cubes.

How fast would that be? Let's see. You need to read in the input data. That's going to come from RAM at 40 GB/s => something like 0.05 ns per cube. You can crunch it into the binary array as you go: two comparisons, a logical AND, and a store to L1 would work out to 2 ns per input element at 3GHz. For per-cube time, divide by 8 as each element is used by 8 cubes: 0.25ns per cube.

Then read through it with a 2x2x2 window for the cube generation, do a couple multiply-adds. Updating the window requires avg 4 reads per step plus processing to generate the cube indexes, say 4x7 cycles in total.

Then write the vertices to the vertex array. This might take 6 cycles for the array fetch and write.

Add some fudge, 3 GHz clock rate. Each cube takes 4x7 + 6 = 34 cycles. Estimated runtime 12ns per cube (+ 0.25ns for input data processing). Need 10 million cubes for the mesh: 120 ms. Do it in parallel in four L1 caches => 30 ms.

But, uh, it already runs in 150 ms for some meshes. And crunching the input data takes 20 ms of that. In JavaScript. What.

2017-09-23

WebGL 2.0

Started doing some vanilla WebGL 2.0 development this month. I like it. I haven't really ventured further into the new API features than doing 3D textures and GLES 3.00 shaders (which are very nice).

The new parts of the API feel a bit like this: you've got buffers and a shader program. What you do is plug the buffers into the inputs and outputs of the shader and run it. Uniforms? You can use a buffer for that. Textures? You can texImage from a buffer. After you've run your program over your vertex buffers, you can readPixels into a buffer. And there are functions for copying buffer data between buffers (and texture data from one texture to another). You can even write the vertex shader output to a buffer with transform feedback.

The fun tricks this opens up are myriad. Use a vertex shader to create a texture? Sure. Update your shader uniforms with a fragment shader? Uh ok. Generate a mesh in a fragment shader and drop it into a vertex array? Yeaaah maybe. All of this without having to read the data back into JavaScript. I wonder how far you could take that. Run an app in shaders with some interrupt mechanism to tell JavaScript to fetch the results and do something in the browserland.

There is still a dichotomy between buffers and textures, so there are some hoops to jump through if you're so inclined.

Blog Archive

About Me

My photo

Built art installations, web sites, graphics libraries, web browsers, mobile apps, desktop apps, media player themes, many nutty prototypes, much bad code, much bad art.

Have freelanced for Verizon, Google, Mozilla, Warner Bros, Sony Pictures, Yahoo!, Microsoft, Valve Software, TDK Electronics.

Ex-Chrome Developer Relations.