"use strict";
var cellSize = 2;
// make a texture as our source
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = "rgb(128, 128, 128)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "rgb(12, 34, 56)";
ctx.fillRect(20, 30, 1, 1);
ctx.fillStyle = "rgb(254, 243, 232)";
ctx.fillRect(270, 140, 1, 1);
var canvas = document.createElement("canvas");
var m4 = twgl.m4;
var gl = canvas.getContext("webgl");
var ext = gl.getExtension("WEBGL_draw_buffers");
if (!ext) {
alert("sample requires WEBGL_draw_buffers");
}
var fsSrc = document.querySelector("#minmax-fs").text.replace("$(cellSize)s", cellSize);
var programInfo = twgl.createProgramInfo(gl, ["vs", fsSrc]);
var unitQuadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
var srcTex = twgl.createTexture(gl, {
src: ctx.canvas,
min: gl.NEAREST,
mag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
});
var framebuffers = [];
var w = ctx.canvas.width;
var h = ctx.canvas.height;
while (w > 1 || h > 1) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
// creates a framebuffer and creates and attaches 2 RGBA/UNSIGNED textures
var fbi = twgl.createFramebufferInfo(gl, [
{ min: gl.NEAREST, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
{ min: gl.NEAREST, mag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
], w, h);
ext.drawBuffersWEBGL([ext.COLOR_ATTACHMENT0_WEBGL, ext.COLOR_ATTACHMENT1_WEBGL]);
framebuffers.push(fbi);
}
// need separate FBs to read the output
var lastFBI = framebuffers[framebuffers.length - 1];
var minFBI = twgl.createFramebufferInfo(gl, [
{ attachment: lastFBI.attachments[0] }
], 1, 1);
var maxFBI = twgl.createFramebufferInfo(gl, [
{ attachment: lastFBI.attachments[1] }
], 1, 1);
var uniforms = {
u_srcResolution: [ctx.canvas.width, ctx.canvas.height],
u_minTexture: srcTex,
u_maxTexture: srcTex,
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, unitQuadBufferInfo);
var w = ctx.canvas.width;
var h = ctx.canvas.height;
framebuffers.forEach(function(fbi, ndx) {
w = Math.max(1, (w + cellSize - 1) / cellSize | 0);
h = Math.max(1, (h + cellSize - 1) / cellSize | 0);
uniforms.u_dstResolution = [w, h];
twgl.bindFramebufferInfo(gl, fbi);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, unitQuadBufferInfo);
uniforms.u_minTexture = fbi.attachments[0];
uniforms.u_maxTexture = fbi.attachments[1];
uniforms.u_srcResolution = [w, h];
});
var p = new Uint8Array(4);
twgl.bindFramebufferInfo(gl, minFBI);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, p);
log("min: ", p[0], p[1], p[2]);
twgl.bindFramebufferInfo(gl, maxFBI);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, p);
log("max: ", p[0], p[1], p[2]);
function log() {
var elem = document.createElement("pre");
elem.appendChild(document.createTextNode(Array.prototype.join.call(arguments, " ")));
document.body.appendChild(elem);
}
<script id="vs" type="not-js">
attribute vec4 position;
void main() {
gl_Position = position;
}
</script>
<script id="minmax-fs" type="not-js">
#extension GL_EXT_draw_buffers : require
precision mediump float;
#define CELL_SIZE $(cellSize)s
uniform sampler2D u_minTexture;
uniform sampler2D u_maxTexture;
uniform vec2 u_srcResolution;
uniform vec2 u_dstResolution;
void main() {
// compute the first pixel the source cell
vec2 srcPixel = floor(gl_FragCoord.xy) * float(CELL_SIZE);
// one pixel in source
vec2 onePixel = vec2(1) / u_srcResolution;
// uv for first pixel in cell. +0.5 for center of pixel
vec2 uv = (srcPixel + 0.5) / u_srcResolution;
vec4 minColor = vec4(1);
vec4 maxColor = vec4(0);
for (int y = 0; y < CELL_SIZE; ++y) {
for (int x = 0; x < CELL_SIZE; ++x) {
vec2 off = uv + vec2(x, y) * onePixel;
minColor = min(minColor, texture2D(u_minTexture, off));
maxColor = max(maxColor, texture2D(u_maxTexture, off));
}
}
gl_FragData[0] = minColor;
gl_FragData[1] = maxColor;
}
</script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>