class WebGLProcessor {
initializeGLContext = (canvas, version) => {
canvas.width = window.innerWidth * 0.99;
canvas.height = window.innerHeight * 0.85;
var gl = canvas.getContext(version ? 'webgl' : 'webgl2');
const ext = gl.getExtension("EXT_color_buffer_float");
if (!ext) {
console.log("Unable to render to floating point textures.");
}
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
gl.lineWidth(0.5);
return gl;
};
clearBuffer = (gl) => {
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
};
createShader = (gl, type, shaderText) => {
var shader = gl.createShader(type);
gl.shaderSource(shader, shaderText);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
}
return shader;
};
createProgram = (gl, vertexShader, fragmentShader) => {
var program = gl.createProgram();
gl.attachShader(program, this.createShader(gl, gl.VERTEX_SHADER, document.getElementById(vertexShader).text.trim()));
gl.attachShader(program, this.createShader(gl, gl.FRAGMENT_SHADER, document.getElementById(fragmentShader).text.trim()));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
}
return program;
};
bindVertexBuffer = (gl, relatedVertices) => {
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(relatedVertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return buffer;
};
bindTextureBuffer = (gl, img, aspectRatio) => {
var texBuffer = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texBuffer);
if (img.width) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, aspectRatio.width, aspectRatio.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, img);
}
// set filtering options
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texBuffer;
};
prepareFramebuffer = (gl, width, height, type, filter) => {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
if (type === gl.UNSIGNED_BYTE) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, type, null);
} else {
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
console.log(`Can ${status === gl.FRAMEBUFFER_COMPLETE ? "" : "NOT "}render to R32`);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32F, width, height, 0, gl.RED, type, null);
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
return {texture: texture, framebuffer: framebuffer};
};
transferDataToGPU = (gl, program, linkedVariable, buffer, dimensions) => {
var vertices = gl.getAttribLocation(program, linkedVariable);
gl.enableVertexAttribArray(vertices);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(vertices, dimensions, gl.FLOAT, gl.FALSE, 0, 0);
return vertices;
};
transferBufferDataToGPU = (gl, buffer, vertices, dimensions) => {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(vertices, dimensions, gl.FLOAT, gl.FALSE, 0, 0);
};
transferTextureToGPU = (gl, tex, index) => {
gl.activeTexture(gl.TEXTURE0 + index);
gl.bindTexture(gl.TEXTURE_2D, tex);
};
activateTextureByIndex = (gl, program, gpuRef, gpuTextureIndex) => {
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, gpuRef), gpuTextureIndex);
}
};
var gl, processor, pseudoImg, img;
var rectCoords = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
var texCoords = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
let pixelCount = 0;
document.addEventListener('DOMContentLoaded', () => {
processor = new WebGLProcessor();
gl = processor.initializeGLContext(document.getElementById('canvas'));
var showProgram = processor.createProgram(gl, 'new-vs', 'show-fs');
I am currently developing a project that involves calculating the count of pixels with values less than a specified number. For example:
var totalPixels = imageWidth * imageHeight;
var pixelCount = 0;
var thresholdValue = 229;
for (var i = 0; i < totalPixels; i++) {
var currentPixelValue = buffer[i];
if (currentPixelValue < thresholdValue) pixelCount++;
}
console.log('Pixel Count:', pixelCount);
In the above code snippet, I am computing the pixel count for all pixels with values below 229. Although it works fine, it can be time-consuming. Is there a way to perform this computation on the GPU? I understand that using loops in the GPU is not recommended, but I'm curious whether it's feasible. If so, could you provide some pseudocode?
Edit: On reviewing this example, I noticed that we can calculate the count for a smaller image like 256 x 1. However, how can we achieve the same for images with dimensions img.width and img.height?
<script id="max-fs" type="not-js">
precision mediump float;
uniform sampler2D u_texture;
void main() {
vec4 maxColor = vec4(0);
// For a 256x1 texture, iterate over every pixel
for (int i = 0; i < 256; ++i) {
// Calculate center points of pixels
vec2 uv = vec2((float(i) + 0.5) / 256.0, 0.5);
// Get the maximum value of the pixel
maxColor = max(maxColor, texture2D(u_texture, uv));
}
gl_FragColor = maxColor;
}
</script>
Although my shader appears as follows, it does not produce the desired outcome:
<script id="pixelCounter-fs" type="not-js">
precision mediump float;
uniform sampler2D u_texture;
uniform vec2 u_resolution;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec4 colorInfo = texture2D(u_texture, uv);
if (colorInfo.r < 0.89453125) {
discard;
}
gl_FragColor = vec4(vec3(colorInfo.r), 1.0);
}
</script>