Supporting high DPI devices with WebAssembly, emscripten and GLFW
--
This is a story of how I modified my project to support high DPI devices. There was surprisingly little information available on how to do this with these technologies so I thought I would write up my experiences.
I’m working on a project that involves rendering a WebGL canvas in the browser. My code is written in C++ and uses GLFW for interacting with OpenGL. It is then compiled to WebAssembly (wasm) using emscripten so it can run in the browser.
I was delighted at how easy it was to create the app with these technologies, however I was disheartened when I started to try drawing lines on my canvas and I saw this…
On my mobile and laptop, the lines appeared really blurry. After some googling I found out that this is due to my high DPI screens. In very simplified terms, my device was telling my front end that it had fewer pixels than it actually did, because otherwise the whole webpage might be rendered really small. This meant my canvas was being rendered at a lower resolution than the screen and then stretched out. For a more accurate and detailed description of how high DPI screens work you can check out this article.
It turns out that this is a solved problem, and I found this really awesome article that talked me through what I needed to do to make my website look great on high DPI screens. It boils down to this…
A canvas
element has two attributes for width and height:
- first there is the drawing buffer width and height, i.e. how many pixels the canvas can draw to. You can set this on the element directly with
<canvas width="400", height="300" />
- then there is the width and height of the displayed canvas element. You can set this using CSS. In React you might do
<canvas style={{height: 300, width: 400}}/>
All you need to do, is set the CSS attributes to the size of the element on screen, and then set the canvas element to the same values scaled up by your DPI or window.devicePixelRatio
.
The catch…
So here’s the catch, when I tried this in my project it didn’t work. I tried scaling the values in glfwSetWindowSize
, glfwCreateWindow
and also glViewport
to control the display buffer size, whilst using CSS to control the element size. Didn’t work. Somehow, no matter what CSS I wrote, it never made any difference to the size of my element — this was always dictated by just the values I passed into glfwSetWindowSize
.
The solution…
After a lot of head-scratching and trying a lot of different things, I eventually landed on the following, remarkably simple, solution. Whenever I resize my window, instead of doing just…
… I now do…
Notice that I am using emscripten_set_element_css_size
to set the CSS size of the element immediately after setting the glfw window and viewport size.
Et voila
And there we have the crisp lines that I had hoped for.
I wrote this up because, when I faced this problem, I couldn’t find a good explanation of how to solve it. If you’ve had the same problem, hopefully you found this useful.