DEV Community

loading...

WebGL month. Day 12. Highdpi displays and webgl viewport

lesnitsky profile image Andrei Lesnitsky Updated on ・3 min read

Day 12. Highdpi displays and webgl viewport

This is a series of blog posts related to WebGL. New post will be available every day

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo

Hey 👋 Welcome back to WebGL month

All previous tutorials where done on a default size canvas, let's make the picture bigger!

We'll need to tune a bit of css first to make body fill the screen

📄 index.html

      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>WebGL Month</title>
+ 
+     <style>
+     html, body {
+       height: 100%;
+     }
+ 
+     body {
+       margin: 0;
+     }
+     </style>
    </head>
    <body>
      <canvas></canvas>

Now we can read body dimensions

📄 src/texture.js

  const canvas = document.querySelector('canvas');
  const gl = canvas.getContext('webgl');

+ const width = document.body.offsetWidth;
+ const height = document.body.offsetHeight;
+ 
  const vShader = gl.createShader(gl.VERTEX_SHADER);
  const fShader = gl.createShader(gl.FRAGMENT_SHADER);


And set canvas dimensions

📄 src/texture.js

  const width = document.body.offsetWidth;
  const height = document.body.offsetHeight;

+ canvas.width = width;
+ canvas.height = height;
+ 
  const vShader = gl.createShader(gl.VERTEX_SHADER);
  const fShader = gl.createShader(gl.FRAGMENT_SHADER);


Ok, canvas size changed, but our picture isn't full screen, why?

Turns out that changing canvas size isn't enought, we also need to specify a viwport. Treat viewport as a rectangle which will be used as drawing area and interpolate it to [-1...1] clipspace

📄 src/texture.js


      gl.uniform2fv(programInfo.uniformLocations.resolution, [canvas.width, canvas.height]);

+     gl.viewport(0, 0, canvas.width, canvas.height);
+ 
      gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
  });

Now our picture fills the whole document, but it is a bit blurry. Obvious reason – our texture is not big enough, so it should be stretched and loses quality. That's correct, but there is another reason.

Modern displays fit higher amount of actual pixels in a physical pixel size (apple calls it retina). There is a global variable devicePixelRatio which might help us.

📄 src/texture.js

  const width = document.body.offsetWidth;
  const height = document.body.offsetHeight;

- canvas.width = width;
- canvas.height = height;
+ canvas.width = width * devicePixelRatio;
+ canvas.height = height * devicePixelRatio;

  const vShader = gl.createShader(gl.VERTEX_SHADER);
  const fShader = gl.createShader(gl.FRAGMENT_SHADER);

Ok, now our canvas has an appropriate size, but it is bigger than body on retina displays. How do we fix it?
We can downscale canvas to a physical size with css width and height property

📄 src/texture.js

  canvas.width = width * devicePixelRatio;
  canvas.height = height * devicePixelRatio;

+ canvas.style.width = `${width}px`;
+ canvas.style.height = `${height}px`;
+ 
  const vShader = gl.createShader(gl.VERTEX_SHADER);
  const fShader = gl.createShader(gl.FRAGMENT_SHADER);


Just to summarize, width and height attributes of canvas specify actual size in pixels, but in order to make picture sharp on highdpi displays we need to multiply width and hegiht on devicePixelRatio and downscale canvas back with css

Now we can alos make our canvas resizable

📄 src/texture.js


      gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
  });
+ 
+ 
+ window.addEventListener('resize', () => {
+     const width = document.body.offsetWidth;
+     const height = document.body.offsetHeight;
+ 
+     canvas.width = width * devicePixelRatio;
+     canvas.height = height * devicePixelRatio;
+ 
+     canvas.style.width = `${width}px`;
+     canvas.style.height = `${height}px`;
+ 
+     gl.viewport(0, 0, canvas.width, canvas.height);
+ });

Oops, canvas clears after resize. Turns out that modification of width or height attribute forces browser to clear canvas (the same for 2d context), so we need to issue a draw call again.

📄 src/texture.js

      canvas.style.height = `${height}px`;

      gl.viewport(0, 0, canvas.width, canvas.height);
+ 
+     gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
  });

That's it for today, see you tomorrow 👋

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo

Discussion (0)

pic
Editor guide