texture()

GLSL ES 3.00's texture() and Integer Texture Access

texture() returns filtered floats. WebGL 2 can also store raw integers (formats such as R8UI or R32I) and read them through usampler2D or isampler2D. Integer textures cannot be filtered, so you address texels with texelFetch(sampler, ivec2 texel, lod); textureSize(sampler, lod) gives the dimensions. Here BookNest's six ratings, stored as tenths in a 6x1 R8UI texture, drive a bar chart drawn in the fragment shader:

BookNest's ratings from an integer textureHTML
<canvas id="c" width="1200" height="280" style="width:600px;height:140px"></canvas>
<script src="webgl-helper.js"></script>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl2');                    // GLH.paint reuses this context
const ratings = new Uint8Array([46, 43, 48, 41, 45, 44]);  // books 1-6, in tenths
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());         // on texture unit 0
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8UI, 6, 1, 0, gl.RED_INTEGER, gl.UNSIGNED_BYTE, ratings);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);   // integer textures
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);   // cannot be filtered
GLH.paint(canvas, `#version 300 es
precision mediump float;
precision mediump usampler2D;                              // integer samplers need a precision
uniform usampler2D uRatings;                               // defaults to unit 0
in vec2 vUV;
out vec4 outColor;
void main() {
  int count = textureSize(uRatings, 0).x;                  // 6
  int book = int(vUV.x * float(count));
  float rating = float(texelFetch(uRatings, ivec2(book, 0), 0).r) / 10.0;
  float bar = step(vUV.y * 5.0, rating) * step(0.15, fract(vUV.x * float(count)))
            * step(fract(vUV.x * float(count)), 0.85);
  float grid = step(0.97, fract(vUV.y * 5.0));             // a line at every whole star
  vec3 color = mix(vec3(0.97, 0.96, 0.93), vec3(0.88, 0.60, 0.06), bar);
  outColor = vec4(mix(color, vec3(0.55), grid * (1.0 - bar)), 1.0);
}`);
window.__done = true;
</script>
Browser output of Listing 3.22
Browser output of 22

The bars show 4.6, 4.3, 4.8, 4.1, 4.5 and 4.4 stars, with a gray line at each whole star. The two NEAREST lines matter: without them, the default mipmapped filter left the integer texture incomplete, every lookup returned zero, and the first capture of this listing showed an empty grid with no error at all.

BookNest's ratings stored in an R8UI integer texture and drawn with texelFetchHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f4ee; }
  canvas { display: block; width: 100%; max-width: 600px; }
</style>
<canvas id="c" width="1200" height="280"></canvas>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl2');
const ratings = new Uint8Array([46, 43, 48, 41, 45, 44]);  // books 1-6, in tenths
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());         // on texture unit 0
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8UI, 6, 1, 0, gl.RED_INTEGER, gl.UNSIGNED_BYTE, ratings);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);   // integer textures
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);   // cannot be filtered

const vs = `#version 300 es
in vec2 aPos; out vec2 vUV;
void main() { vUV = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;
const fs = `#version 300 es
precision mediump float;
precision mediump usampler2D;                              // integer samplers need a precision
uniform usampler2D uRatings;                               // defaults to unit 0
in vec2 vUV;
out vec4 outColor;
void main() {
  int count = textureSize(uRatings, 0).x;                  // 6
  int book = int(vUV.x * float(count));
  float rating = float(texelFetch(uRatings, ivec2(book, 0), 0).r) / 10.0;
  float bar = step(vUV.y * 5.0, rating) * step(0.15, fract(vUV.x * float(count)))
            * step(fract(vUV.x * float(count)), 0.85);
  float grid = step(0.97, fract(vUV.y * 5.0));             // a line at every whole star
  vec3 color = mix(vec3(0.97, 0.96, 0.93), vec3(0.88, 0.60, 0.06), bar);
  outColor = vec4(mix(color, vec3(0.55), grid * (1.0 - bar)), 1.0);
}`;
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
  const s = gl.createShader(type);
  gl.shaderSource(s, src); gl.compileShader(s);
  if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
  gl.attachShader(program, s);
}
gl.bindAttribLocation(program, 0, 'aPos');
gl.linkProgram(program);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, 1,1, -1,-1, 1,1, -1,1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
</script>