Surma
surma.dev · Personal blog · on HN since 2021
Surma, a web developer, writes about WebAssembly, WebGPU, Rust, image dithering, and performance on the web.
What HN saysHN commenters recommend his dithering article as a great explanation, including blue noise, and his WebGPU tutorial as an easier way into GPU programming.
67HN people shared it
34linked it in comments
34stories submitted
2.2Kstory points
1 year agolast shared
2021people per year2026
Most-shared posts
- 1Ditherpunk: The article I wish I had about monochrome image dithering28 people · 1.3K points · first shared 2021 · 200 comments on HN
- 2WebGPU – All of the cores, none of the canvas7 people · 395 points · first shared 2022 · 134 comments on HN
- 3Rust to WebAssembly the Hard Way7 people · 8 points · first shared 2023 · discussion on HN
- 4Is WebAssembly magic performance pixie dust?5 people · 474 points · first shared 2021 · 248 comments on HN
- 5I turned JavaScript into a compiled language (for fun and WASM)4 people · 20 points · first shared 2022 · 2 comments on HN
- 6WebAssembly optimization with AssemblyScript4 people · 7 points · first shared 2021 · discussion on HN
- 7Compiling C to WebAssembly without Emscripten3 people · 1 points · first shared 2021 · discussion on HN
- 8The Cost of Convenience2 people · 3 points · first shared 2022 · discussion on HN
- 9When should you be using Web Workers?2 people · 2 points · first shared 2021 · discussion on HN
- 10Case study: Moving a Three.js-based WebXR app off-main-thread1 people · 4 points · first shared 2021 · discussion on HN
- 11Comlink and WebRTC: P2P RPC1 people · 2 points · first shared 2022 · discussion on HN
- 12Coding Interviews: Optimize for Iteration1 people · 2 points · first shared 2022 · discussion on HN
- 13React and Redux and Comlink = Off-main-thread1 people · 1 points · first shared 2021 · discussion on HN
- 14Why your phone’s portrait mode fakes the blur1 people · 1 points · first shared 2023 · discussion on HN
- 15Index1 people · first shared 2021
What HN people wrote when they linked it
zeroq · 2025-10-28
slightly related: it would be great to have a list of TOP 100 recurring links on HN. :)
on topic: https://surma.dev/things/ditherpunk/ is a great companion read to the subject
wenc · 2025-07-12
“Dithering” is the key — except this seems to have been done by hand.
When I was a kid, I owned a monochrome display that could only display at CGA resolutions “640x400” 1-bit (and 320x200). Many games and art and didn’t support that showed up garbled.
Then I got hold of Deluxe Paint that would load pictures in color and dither them with an algo called Floyd Steinberg. And the pictures that I saw on my friends VGA monitors suddenly looked beautiful on my monochrome screen.
See examples https://surma.dev/things/ditherpunk/
Games like Monkey Island were also ditherered for monochrome displays and they looked great.
datene · 2023-10-11
This excellent article on dithering also explains blue noise in a simple way
https://surma.dev/things/ditherpunk/
hutzlibu · 2023-06-19
Interesting, but very heavy read. And this is about the low level access towards it via system libaries.
A more simple way to get into GPU programming, would be WebGPU.
I can recommend this tutorial as an introduction:
https://surma.dev/things/webgpu/
GPU programming is really a different way of programming, even though the code looks mostly familiar. And if you are used to single threaded, it will give you a massive headache thinking about, how to do everything in parallel, so that everything is not blocking everything.
The raw power is tempting, so much that I evaluate now how to do allmost everything on the GPU, but many things simply are not suitable to do there.
Basically, you want to avoid conditions and loops. Cannot do recursion. Debugging is a pain and non obvious things can make everything very slow, until you roughly understand, what is going on inside.
andsoitis · 2022-12-28
The most popular modern graphics (GPU) APIs are DirectX 12 (Microsoft), Metal (Apple), and Vulkan (Khronos Group).
Targeting all three can be tough, hence WebGPU which lets you write to a single API. Support on non-web runtimes have been appearing as well, so you're not necessarily limited to the browser (e.g. https://github.com/samdauwe/webgpu-native-examples).
Good read: https://surma.dev/things/webgpu/
Tutorial for learning Vulkan: https://www.vulkan.org/learn#vulkan-tutorials
nsilvestri · 2021-06-08
Lucas Pope's section of the update video [0] has me incredibly excited. He's one of my favorite game developers, and seeing him work more with 1-bit 3D games after Obra Dinn has piqued my interest. Also, a blog on dithering I came across after playing Obra Dinn was one of the more interesting tech blogs I've read [1]. Also see Lucas Pope's post on how he did dithering in-game [2].
[0] https://youtu.be/DeWGukDrc1U?t=455
[1] https://surma.dev/things/ditherpunk/
[2] https://forums.tigsource.com/index.php?topic=40832.msg1363742#msg13637…
jpap · 2021-02-13
Unfortunately not when you're performing error diffusion, where the residuals are added to neighboring pixels. If you're doing it in the non-linear domain, you're going to get a different result during that diffusion step, even when you're dithering to a target 1-bit/pixel image.
You can see this visually in Surma's excellent blog post [1]: look for the gradient strips in the "Gamma" section.
[1] https://surma.dev/things/ditherpunk/
infofarmer · 2021-01-30
A great visual intro to dithering here [1] and a specific filter based on Bill Atkinson’s algorithm for the original Mac here [2]
[1] https://surma.dev/things/ditherpunk/
[2] https://www.tinrocket.com/content/hyperdither/
kragen · 2025-10-28
The major dithering algorithm that's missing from this list is blue-noise dithering. This is very similar to "ordered dithering"; you can think of ordered dithering as either thresholding the pixel values with a different threshold value on each pixel, following a regular pattern, or as adding a different offset value to each pixel, following a regular pattern, and thresholding the result with a constant threshold. Blue-noise dithering replaces the regular pattern with a random pattern that's been high-pass filtered. This has all the advantages of ordered dithering, in particular avoiding "crawling" patterns during animation, but avoids the repetitive patterns and line artifacts it introduces.
https://nelari.us/post/quick_and_dirty_dithering/ is the best quick introduction to the technique that I've seen. There's a more comprehensive introduction at https://momentsingraphics.de/BlueNoise.html. https://bartwronski.com/2016/10/30/dithering-part-three-real-world-2d-… also demonstrates it, comparing it to other dithering algorithms.
Ulichney introduced blue noise to dithering in 01988 as a refinement of "white-noise dithering", also known as "random dithering", where you just add white noise before thresholding: https://cv.ulichney.com/papers/1988-blue-noise.pdf. Ulichney's paper is also a pretty comprehensive overview of dithering algorithms at the time, and he also makes some interesting observations about high-pass prefiltering ("sharpening", for…
bane · 2024-01-14
Oh man, there is an absolute beauty with 1-bit graphics that I think is deeply underappreciated. When the right dithering algorithm is used, the graphics look like what characters in cyberpunk novels dream of. [1]
I secretly wish the dominant aesthetic in computing was one where everything was mac-like 1-bit dithered graphics, and we only used color when absolutely necessary - when the black and white graphics just couldn't convey what was needed. Like if instead of throwing it away for color, we had just built on top of it.
Side note, I'm also often struck how 1-bit-ish graphics sometimes were used to great effect on the ZXSpectrum, another minimally graphically capable computer of the era. When the game developers were trying to use color the games often looked worse than if they had just stuck with a simpler scheme. Chase H.Q., Bombjack, Pang!, etc. all look amazing to my eye.
1 - https://surma.dev/things/ditherpunk/
amai · 2025-06-08
Then you might enjoy this one, too:
https://surma.dev/things/ditherpunk/
DonHopkins · 2023-10-31
I bet he thinks it's impossible for Floyd-Steinberg error diffusion dithering to represent a picture that is not completely black or white.
Floyd–Steinberg dithering:
https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
Ditherpunk — The article I wish I had about monochrome image dithering:
https://surma.dev/things/ditherpunk/
AshleysBrain · 2023-09-19
We build a full game creation IDE and game engine which is entirely browser-based and almost entirely written in JavaScript (including all the performance-sensitive stuff), and the performance is ridiculously good[1]. You don't need WASM for good performance[2].
[1] https://www.construct.net/en/blogs/construct-official-blog-1/outstandi…
[2] https://surma.dev/things/js-to-asc/
kipple · 2023-08-07
I've used Comlink in a production React app (for client-side text search) and enjoyed it.
The lib author wrote a nice blog post about "why webworkers" and "why comlink" a few years ago: https://surma.dev/things/when-workers/
Plasmoid · 2023-03-09
Ditherpunk is really cool. I found this article about it to be so interesting - https://surma.dev/things/ditherpunk/
sxp · 2023-02-08
A better place to start than this article for devs who know WebGL/OpenGLES would be https://toji.github.io/webgpu-gltf-case-study/ by one of the Google Chrome devs. I also really liked https://surma.dev/things/webgpu/ which was my first in depth intro into rendering via WebGPU.
I would also suggest https://web.dev/gpu-compute/ which is a good intro to WebGPU compute, but it was out of date and had broken APIs the last time I tried it. But it has useful theory if you've never used GPU compute before.
edei · 2023-01-13
Error diffusion dithering should be done in linear color space to avoid the severe increase in perceived lightness of the dithered image.
See https://surma.dev/things/ditherpunk/ under "Gamma" for more info, and some example gradients that clearly demonstrate the effect.
sprash · 2023-01-12
This is interesting if you are after retro effects. State of the art dithering however works with blue noise threshold textures[1] because we have plenty of memory available these days. Here is a nice comparison of all kinds of dithering algorithms that also shows the clear superiority of blue noise dithering: [2].
1.: http://momentsingraphics.de/BlueNoise.html
2.: https://surma.dev/things/ditherpunk/
bj-rn · 2022-12-19
I found this quite nice (explaining and comparing different methods of dithering):
https://surma.dev/things/ditherpunk/
ricardobeat · 2022-07-15
These comparisons are always highly biased by the kind of work being done. V8 is so good at optimizing code, it's usually possible to reach similar performance in JS.
See https://surma.dev/things/js-to-asc/
Not that relevant, but it looks pretty terrible on a hi-dpi screen, it's probably not accounting for the pixel density when creating the canvas. Looks much better on a normal screen.
modeless · 2022-07-14
I think WASM is ready for production use. Emscripten does produce big blobs of JavaScript. If it bothers you it's possible to do WASM without Emscripten. If your embedded code doesn't use a lot of libc then it might actually work for you. https://surma.dev/things/c-to-webassembly/
The big gotcha used to be debugging but Chrome dev tools actually has DWARF support now. Of course as an embedded developer you're probably already used to poor debugging support.
smcl · 2022-06-03
You could approximate this by taking a picture as normal, then resizing and applying some dithering I think. There was a nice article submitted to HN recently on this: https://surma.dev/things/ditherpunk/
viceroyalbean · 2021-10-22
Your mention of Return of the Obra Dinn reminded me of this article[1] which I found quite fascinating about dithering, the technique used for the game's art style. One of the neat things about these types of small indie games is that they can do unique things with (among other things) art style which bigger budget games that need a wider appeal can't afford to.
[1] https://surma.dev/things/ditherpunk/
O_H_E · 2021-08-22
While reading this[1] I just remembered you guys and thought I'd throw in the idea of compiling your "native" code to WebAssembly, in case it might fit your case.
https://surma.dev/things/js-to-asc/
pjmlp · 2021-07-18
Actually you can do the same with clang.
Emscripten just installs a bunch of stuff to make it easier to use existing libraries.
If you just want to use standard library, no need for extra stuff.
https://surma.dev/things/c-to-webassembly/
sdenton4 · 2021-05-06
Cool, yeah Floyd Sternberg is definitely period appropriate!
This article on dithering made the rounds a few months ago, and was a fascinating read; the hilbert curve method at the end (Riemersma) might be a good fit and get rid of some of the line effects (if that's of interest). And the Atkinson error diffusion sounds like it even has some history in old macs.
https://surma.dev/things/ditherpunk/
There's a really nice blog post on video stable dithering in Return to the Obra Dinn, but in that case it's a 3d game, and they've got direct access to the scene geometry. I'm curious if there's a nice way to use video encoding pixel tracking vectors to reduce swimminess...
ghayes · 2021-04-25
FWIW, AssemblyScript[0] adds its own garbage collection runtime into Wasm. The team had a good post recently on the performance implications[1]
[0] https://www.assemblyscript.org/
[1] https://surma.dev/things/js-to-asc/index.html
cnity · 2021-01-05
This guys' articles are wonderful. I was experimenting with compiling C to WASM[0] and Surma's was really helpful.
[0]: https://surma.dev/things/c-to-webassembly/index.html
uoaei · 2024-10-25
Rabbit hole ahoy:
https://surma.dev/things/ditherpunk/
lukan · 2024-06-27
"Collision detection is usually a tree search"
Yes, because of the very limited numbers of CPU cores. With a GPU you can just assign one core to one particle.
Here is a simple approach to do it with WebGPU:
https://surma.dev/things/webgpu/
It uses the very simple approach, of testing every particle with EVERY other particle. Still very performant (the simulation, the choosen rendering with canvas is very slow)
I currently try to do something like this, but optimised. With the naive approache here and Pixi instead of canvas, I get to 20000 particles 120 fps on an old laptop. I am curious how far I get with an optimized version. But yes, the danger is in calculating and rendering blocking each other. So I have to use the CPU in a smart way, to limit the data being pushed to the GPU. And while I prepare the data on the CPU, the GPU can do the graphic rendering. Like I said, it is way harder to do right this way. When the simulation behaves weird, debugging is pain.