I'm working on a WebGL OpenGL ES project that renders my scene to a texture and then uses that texture to render to the window.
I noticed that this causes an odd shimmering on the edges of textures. When I revert the project back to rendering directly to the window the shimmering goes away.
The FBO is set to the same size as the window and the texture is drawn to the window pixel for pixel.
What is causing this to happen? Is it fixable? I am using GL_NEAREST on the FBO texture and GL_LINEAR on all other textures. I'd rather not use GL_CLAMP_TO_EDGE as that would cause other problems.
[
Framebuffer creation
/* Returns true if a framebuffer object is created */
Display.prototype.createFramebuffer = function(name) {
var gl = this.gl; // Sanity Save
var size = 1024;
var fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
fb.width = size;
fb.height = size;
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, fb.width, fb.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
var rb = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fb.width, fb.height);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
if(gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { return false; }
this.fbo[name] = {fb: fb, rb: rb, tex: tex};
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return true;
};
Before drawing scene
/* Draw Geometry */
gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo.world.fb); // Enable world framebuffer
gl.viewport(0, 0, this.window.width, this.window.height); // Resize to canvas
gl.clearColor(0.5, 0.5, 0.5, 1.0); // Opaque grey backdrop
After a push in the right direction (and plenty of trial and error) I figured out the problem.
The reason this is happening is because hardware anti-aliasing is disabled when rendering to an FBO texture. In order to fix it you need to implement your own AA.
To fix this for my project I attempted a crude MSAA post shader in combination with upscaling my FBO render to 2x the window resolution and that reduced the shimmering to a nearly unnoticeable level.
FXAA might be another good solution but I couldn't get it to work for me personally.
If there are some better ways to go about this let me know!
Related
Initializing webgl with canvas.getContext("webgl", {stencil : true}) requests a stencil buffer, but not all browsers will actually give you one (for me, Firefox 79.0 on Ubuntu 20.04 LTS doesn't works but Chrome 84.0.4147.89 does. My graphics card is NVIDIA RTX 2060, I'm using the nvidia-driver-440-server driver).
I would like to know how widely supported stencil buffers are, but I can't find information about what browsers are supported. The functions like glStencilOp, which are the only things I can find support information for, can still be used, they just don't do anything with 0 stencil bits.
Is there a list of browsers that support this feature?
Honestly that sounds like a bug in firefox although given the spec lets an implementation fail to provide a stencil buffer on the canvas for any reason whatsoever it's not technically a bug. I would consider filling one. Test with a Chromium browser just to check that this is Firefox choosing not to provide a stencil buffer and not a driver issue or something else.
You should be able to always make a DEPTH_STENCIL renderbuffer. There is no version of WebGL that allows an implementation to not support that. So, you can work around the bug by rendering to a texture + depth stencil renderbuffer attached to a framebuffer and then render the framebuffer color texture to the canvas.
Here's a test. you should see a red square with the bottom right corner green. that will be inside a blue square which is inside a purple square.
The blue square is to show the extents of the framebuffer texture. If the green square was not being masked by the stencil buffer it would bleed into the blue.
The purple square is to show the size of the canvas and that we are drawing the framebuffer texture smaller than the full canvas. This is all just to show that stencil buffers work on your machine. For your own solution you'd want to draw a quad made out of vertices instead of using points like below, and you'd want to make the texture and renderbuffer attached to the frame buffer the same size as your canvas.
"use strict";
function main() {
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 64.0;
}
`;
const fs = `
precision mediump float;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, "position");
// Create a texture to render to
const targetTextureWidth = 128;
const targetTextureHeight = 128;
const targetTexture = createTexture(gl);
{
// define size and format of level 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
}
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
// create a depth-stencil renderbuffer
const depthStencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
// make a depth-stencil buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
function createTexture(gl, color) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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);
if (color) {
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
}
return tex;
}
// create a red texture and a green texture
const redTex = createTexture(gl, [255, 0, 0, 255]);
const greenTex = createTexture(gl, [0, 255, 0, 255]);
gl.enable(gl.STENCIL_TEST);
gl.useProgram(program);
gl.clearColor(0, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, redTex);
gl.stencilFunc(
gl.ALWAYS, // the test
1, // reference value
0xFF, // mask
);
gl.stencilOp(
gl.KEEP, // what to do if the stencil test fails
gl.KEEP, // what to do if the depth test fails
gl.REPLACE, // what to do if both tests pass
);
// draw a 64x64 pixel red rect in middle
gl.drawArrays(gl.POINTS, 0, 1);
gl.stencilFunc(
gl.EQUAL, // the test
1, // reference value
0xFF, // mask
);
gl.stencilOp(
gl.KEEP, // what to do if the stencil test fails
gl.KEEP, // what to do if the depth test fails
gl.KEEP, // what to do if both tests pass
);
// draw a green 64x64 pixel square in the
// upper right corner. The stencil will make
// it not go outside the red square
gl.vertexAttrib2f(posLoc, 0.5, 0.5);
gl.bindTexture(gl.TEXTURE_2D, greenTex);
gl.drawArrays(gl.POINTS, 0, 1);
// draw the framebuffer's texture to
// the canvas. we should see a 32x32
// red square with the bottom right corner
// green showing the stencil worked. That will
// be surrounded by blue to show the texture
// we were rendering to is larger than the
// red square. And that will be surrounded
// by purple since we're drawing a 64x64
// point on a 128x128 canvas which we clear
// purple.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttrib2f(posLoc, 0.0, 0.0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>
If you change the renderbuffer format to DEPTH_COMPONENT16 and the attachment point to DEPTH_ATTACHMENT then you'll see green square is no longer masked by the stencil
"use strict";
function main() {
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
gl_PointSize = 64.0;
}
`;
const fs = `
precision mediump float;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const posLoc = gl.getAttribLocation(program, "position");
// Create a texture to render to
const targetTextureWidth = 128;
const targetTextureHeight = 128;
const targetTexture = createTexture(gl);
{
// define size and format of level 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
}
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
// create a depth-stencil renderbuffer
const depthStencilBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
// make a depth-stencil buffer and the same size as the targetTexture
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
function createTexture(gl, color) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
// set the filtering so we don't need mips
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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);
if (color) {
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(color));
}
return tex;
}
// create a red texture and a green texture
const redTex = createTexture(gl, [255, 0, 0, 255]);
const greenTex = createTexture(gl, [0, 255, 0, 255]);
gl.enable(gl.STENCIL_TEST);
gl.useProgram(program);
gl.clearColor(0, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, redTex);
gl.stencilFunc(
gl.ALWAYS, // the test
1, // reference value
0xFF, // mask
);
gl.stencilOp(
gl.KEEP, // what to do if the stencil test fails
gl.KEEP, // what to do if the depth test fails
gl.REPLACE, // what to do if both tests pass
);
// draw a 64x64 pixel red rect in middle
gl.drawArrays(gl.POINTS, 0, 1);
gl.stencilFunc(
gl.EQUAL, // the test
1, // reference value
0xFF, // mask
);
gl.stencilOp(
gl.KEEP, // what to do if the stencil test fails
gl.KEEP, // what to do if the depth test fails
gl.KEEP, // what to do if both tests pass
);
// draw a green 64x64 pixel square in the
// upper right corner. The stencil will make
// it not go outside the red square
gl.vertexAttrib2f(posLoc, 0.5, 0.5);
gl.bindTexture(gl.TEXTURE_2D, greenTex);
gl.drawArrays(gl.POINTS, 0, 1);
// draw the framebuffer's texture to
// the canvas. we should see a 32x32
// red square with the bottom right corner
// green showing the stencil worked. That will
// be surrounded by blue to show the texture
// we were rendering to is larger than the
// red square. And that will be surrounded
// by purple since we're drawing a 64x64
// point on a 128x128 canvas which we clear
// purple.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1, 0, 1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.vertexAttrib2f(posLoc, 0.0, 0.0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas width="128" height="128"></canvas>
You're supposed to be able to call gl.getContextAttributes to check if you got a stencil buffer or not so you can use the suggested solution if it tells you you didn't get a stencil buffer on the canvas.
When you only want the color part of the frame, you can store depth in a render buffer.
This works fine for me on both webgl1 and webgl2.
When you also need to use the depth part you can't use a render buffer, you need to store it in a texture.
In webgl1 you need to get an extension to support it.
This also works fine for me in webgl1.
const gl = canvas.getContext('webgl');
const depthTextureExtension = gl.getExtension('WEBGL_depth_texture');
if (!depthTextureExtension) {
alert('Depth textures not supported');
}
This feature should be available by default in webgl2.
Yet when I replace the code above with this:
const gl = canvas.getContext('webgl2');
My code fails to render and I can not find any explanation why.
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
this.depth = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.depth);
checkGLErrors(gl); // OK
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null);
checkGLErrors(gl); // for webgl2: 1281 INVALID_VALUE
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
this.framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this.depth, 0);
In WebGL2 DEPTH_COMPONENT is not a valid internal format. Use DEPTH_COMPONENT16, DEPTH_COMPONENT24 or DEPTH_COMPONENT32F
Example from here
const level = 0;
const internalFormat = gl.DEPTH_COMPONENT24;
const border = 0;
const format = gl.DEPTH_COMPONENT;
const type = gl.UNSIGNED_INT;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
targetTextureWidth, targetTextureHeight, border,
format, type, data);
Whenever I use textures in webgl Firefox (Firefox Developer Edition 50.0a2 for OSX, to be excact) outputs these warnings in the console:
Error: WebGL: texSubImage2D: Incurred CPU-side conversion, which is
very slowError: WebGL: texSubImage2D: Incurred CPU pixel conversion,
which is very slowError: WebGL: texSubImage2D: Chosen format/type
incurred an expensive reformat: 0x1908/0x1401
Is there any way to avoid that? I have tried all combinations of allowed formats and types for the texImage2D call, but I get conversion on the CPU no matter what I try.
Here is a minimal example showing what I am doing:
var gl = document.querySelector('canvas').getContext('webgl');
var textureSize = 512;
var canvas = document.createElement('canvas');
canvas.width = textureSize;
canvas.height = textureSize;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(0, 1, 0, 0.0)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, 400, 400);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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);
<canvas />
Your sample doesn't print warnings in firefox 48 on OSX so I can only guess but
A 2D canvas uses premultiplied alpha. WebGL, by default, uses un-premultipled alpha for textures. That means in order to transfer the contents of the canvas texture it has to be converted to premultiplied alpha which depending on how that's implemented could be slow.
If you don't need un-premultiplied alpha in your texture then you can tell WebGL you want premultiplied data when called texImage2D and texSubImage2D by calling gl.pixelStorei and tell it like this
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
In this case the browser can possibly just use the canvas data as is. This might make the warning go away. Note if you're just uploading once you probably shouldn't care. If you're uploading every frame then maybe you should.
Be aware though that gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); effects ALL texture uploads including raw data. For example
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA,
1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array([128, 192, 255, 128]));
if UNPACK_PREMULTIPLY_ALPHA_WEBGL is true the browser will do the premultiplication before uploading the texture so [255, 255, 255, 128] will become [64, 96, 128, 128].
UNPACK_FLIP_Y_WEBGL might also affect upload speeds depending on how it's implemented in the browser.
I have a TIFF made up of floating points, not RGBA values, so it shows up as transparent. I used C++ to get the float values, and now I have a matrix of these values. How can I convert them to RGBA using WebGL, then make a texture out of them?
To load floating point values into a texture in WebGL you have to check for and enable floating point textures
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
// Sorry, your browser/GPU/driver doesn't support floating point textures
}
After that you can upload floating point data
var data = new Float32Array(width * height * 4); //
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0 gl.RGBA, gl.FLOAT, data);
But you can't filter that texture so you have to set it to nearest
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
If you want to be able to filter you need to check for and enable that too
var ext = gl.getExtensions("OES_texture_float_linear");
if (!ext) {
// sorry, can't filter floating point textures
}
Rendering to a floating point texture is also an optional feature (using a a floating point textures as a framebuffer attachment). For that you'd attach the texture then check if it works
var fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
// sorry, you can't render to a floating point texture
}
Admittedly i haven't really tried this directly with floating points for a texture, but i have done it with Uint8 values to create a blank texture.
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, new Float32Array(putFloatingPointArrayHere));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.bindTexture(gl.TEXTURE_2D, null);
I successfully created a WebGL texture from an image and drew it into a canvas element.
function initTexture(src) {
texture = gl.createTexture();
texture.image = new Image();
texture.image.onload = function() {
handleLoadedTexture(texture)
}
texture.image.src = src;
}
I also tried to create a texture from one of these datatypes, but without success.
[object ImageData]
[object CanvasPixelArray]
[object CanvasRenderingContext2D]
Is it possible to create a texture just with an image's pixel array?
Or in other words: Is it possible to create a JS Image object out of a pixel array?
Edit:
The pixel array looks like this [r,g,b,a,r,g,b,a,r,g,b,a,...] and each value is in a range of {0..255}.
I want to create a texture with the pixels in the given array.
It's absolutely possible to create a texture with a pixel array! I use the following in my code all the time to create a single pixel, solid color texture.
function createSolidTexture(gl, r, g, b, a) {
var data = new Uint8Array([r, g, b, a]);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
return texture;
}
EDIT: To extrapolate this a little further, most of what you need to know is in the gl.texImage2d call. In order to create a texture from raw RGB(A) data you need an array of unsigned byte values, you need to specify to WebGL what the data represents (RGB or RGBA), and you need to know the dimensions of the texture. A more generalized function would look like this:
function textureFromPixelArray(gl, dataArray, type, width, height) {
var dataTypedArray = new Uint8Array(dataArray); // Don't need to do this if the data is already in a typed array
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, type, width, height, 0, type, gl.UNSIGNED_BYTE, dataTypedArray);
// Other texture setup here, like filter modes and mipmap generation
return texture;
}
// RGB Texture:
// For a 16x16 texture the array must have at least 768 values in it (16x16x3)
var rgbTex = textureFromPixelArray(gl, [r,g,b,r,g,b...], gl.RGB, 16, 16);
// RGBA Texture:
// For a 16x16 texture the array must have at least 1024 values in it (16x16x4)
var rgbaTex = textureFromPixelArray(gl, [r,g,b,a,r,g,b,a...], gl.RGBA, 16, 16);