WebGL Simple 2D Example 15

Speed Up with shorts and bytes.

• It's faster to set a short or byte than a float.
• Our texture positions are in pixels so they don't need floating points.
• Our rgb are 0-255 so they can be type byte.
• Now we can draw 10,000 of them with random transparency/tint without lag! 60 FPS on an old android phone!
• Comment on GitHub https://github.com/curtastic/gl1
My texture:

Full JavaScript code on this page: (or you can View Page Source)

```var gl, glExtension, canvas, vertices, verticesAsShort, verticesAsByte, vertexIndex=0
// UPDATE: Each image takes 28 bytes (4 floats of image pos + 4 shorts of texPos + 4 bytes rgba) 16+8+4
var bytesPerImage=28

// UPDATE: Let's draw lots of images!
var guys = []
for(var i=0; i<10000; i++) {
var size = Math.random()*99+9
guys.push({
x:Math.random()*600, y:Math.random()*600,
width:size, height:size,
r:Math.random()*255, g:Math.random()*255, b:Math.random()*255,
a:1-Math.random()*Math.random(),
speed:Math.random()*5
})
}

function drawImage(x, y, width, height, texX, texY, texWidth, texHeight, r, g, b, a) {
// Set 12 slots in vertices. Overwrite what was there last frame.
var i = vertexIndex
vertices[i++] = x
vertices[i++] = y
vertices[i++] = width
vertices[i++] = height

// UPDATE: multiply by 2 to get to the same position in verticesAsShort because it has twice as many slots.
i *= 2
verticesAsShort[i++] = texX
verticesAsShort[i++] = texY
verticesAsShort[i++] = texWidth
verticesAsShort[i++] = texHeight

// UPDATE: multiply by 2 to get to the same position in verticesAsByte because it has twice as many slots.
i *= 2
verticesAsByte[i++] = r
verticesAsByte[i++] = g
verticesAsByte[i++] = b
verticesAsByte[i++] = a*255

vertexIndex += bytesPerImage
}

// Draw rectangle by drawing the white pixel in our PNG.
function drawRectangle(x, y, width, height, r, g, b, a) {
drawImage(x, y, width, height, 1, 1, 1, 1, r, g, b, a)
}

function gameLoop() {
window.requestAnimationFrame(gameLoop)

// Draw a wide rectangle.
drawRectangle(0,370, 200,50, 200,100,10,1)

// UPDATE: Draw the moving images.
for(var i=0; i < guys.length; i++) {
var guy = guys[i]
var frame = (guy.x*.07+i)&1
drawImage(
guy.x,guy.y, guy.width,guy.height,
frame*32,32, 32,32,
guy.r, guy.g, guy.b, guy.a
)
guy.x+=guy.speed
if(guy.x > 500)guy.x-=500
guy.y++
if(guy.y > 500)guy.y-=500
}

glRender()
}

function glRender() {
// Clear the screen.
gl.clear(gl.COLOR_BUFFER_BIT)

// Send to webGL the section of vertices[] that we acually used this frame.
gl.bufferSubData(gl.ARRAY_BUFFER, 0, verticesAsByte.subarray(0,vertexIndex))

// Draw all the images.
// 6 is the amount of points per image since they are made of 2 triangles.
// vertexIndex/bytesPerImage is the amount of images we drew this frame.
glExtension.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, vertexIndex/bytesPerImage)

// Reset vertexIndex. We overwrite our vertices[] array every frame.
vertexIndex = 0
}

function glSetup() {
gl = myCanvas.getContext("experimental-webgl")

// This extension allows us to repeat the draw vertex operation 6 times (to make 2 triangles) on the same 12 slots in
//  vertices[] so we only have to put the image data into vertices[] once for each image each time we want to draw an image.
glExtension = gl.getExtension("ANGLE_instanced_arrays")

// Set the background color to sky blue.
gl.clearColor(.5, .7, 1, 1)

var vertCode =
"attribute vec2 coordinates;" +
"attribute vec2 drawSize;" +
"attribute vec2 whichCorner;" +
"attribute vec4 rgba;" +
"attribute vec2 texPos;" +
"attribute vec2 texPartSize;" +

"varying highp vec4 rgbaForFrag;" +
"varying highp vec2 texPosForFrag;" +
"uniform vec2 canvasSize;" +
"uniform vec2 texSize;" +

"void main(void) {" +
" vec2 drawPos;" +
// Calculate which of the 4 corners of the image we are drawing now.
// Then divide the position by our current canvas size.
" drawPos = (coordinates + drawSize*whichCorner) / canvasSize * 2.0;" +

// We are passing in only 2D coordinates. Then Z is always 0.0 and the divisor is always 1.0
" gl_Position = vec4(drawPos.x - 1.0, 1.0 - drawPos.y, 0.0, 1.0);" +
// Pass the color and transparency to the fragment shader.
" rgbaForFrag = rgba / 255.0;" +
// Pass the texture position to the fragment shader. Now supports 4 corners, all with the same vertex data.
" texPosForFrag = (texPos + texPartSize*whichCorner) / texSize;" +
"}"

// Create a vertex shader object.

var fragCode =
"varying highp vec4 rgbaForFrag;" +
"varying highp vec2 texPosForFrag;" +
"uniform sampler2D sampler;" +
"void main(void) {" +
" gl_FragColor = texture2D(sampler, texPosForFrag) * rgbaForFrag;" +
"}"

// Tell webGL to use both my shaders.

// Tell webGL when drawing a triangle to access the same vertex data twice so we can pass in 4 corners of a rectangle,
//  to draw 2 triangles which would normally need 6 points worth of vertex data.
// Map triangle vertexes to our multiplier array, for which corner of the image drawn's rectangle each triangle point is at.
// This shows what order to access the gl.ARRAY_BUFFER below, so it can still make 2 triangles using those 4 points,
//  so first it will make a point at the top-left, then the bottom-left, then the top-right, then (the second triangle starts)
//  the top-right again, then bottom-left again, then bottom right (which is index 3 which refers to 1,1 in my ARRAY_BUFFER).
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer())
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([0, 1, 2, 2, 1, 3]), gl.STATIC_DRAW)

// Our multiplier array for width/height so we can get to each corner of the image drawn.
// index 0 has 0,0 which will be the top left of the image we are drawing because size will get multiplied to 0.
// index 1 has 0,1 which will be the bottom left of the image we are drawing since width will get *0 but height gets *1. etc.
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 0,1, 1,0, 1,1]), gl.STATIC_DRAW)

// whichCorner will access the array above, so we can get to each point in the rectangle image we want to draw.
//  draw size will be multiplied by this so 0,0 will be the top left of the image, and 1,1 will be the bottom right.
gl.enableVertexAttribArray(attribute)
gl.vertexAttribPointer(attribute, 2, gl.FLOAT, false, 0, 0)

// Now set a default array buffer to read our vertices[] from.
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
// Make a buffer big enough to have all the data for the max images we can show at the same time.
var arrayBuffer = new ArrayBuffer(100000 * bytesPerImage)
gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.DYNAMIC_DRAW)
vertices = new Float32Array(arrayBuffer)
// UPDATE: Make it so we can access the array as short and byte also.
verticesAsShort = new Int16Array(arrayBuffer)
verticesAsByte = new Uint8Array(arrayBuffer)

// Tell webGL to read 2 floats from the vertex array for each vertex
// and store them in my vec2 shader variable I've named "coordinates"
gl.vertexAttribPointer(attribute, 2, gl.FLOAT, false, bytesPerImage, 0)
gl.enableVertexAttribArray(attribute)
glExtension.vertexAttribDivisorANGLE(attribute, 1)

// Then read width and height from the vertex array and store them in "drawSize".
gl.vertexAttribPointer(attribute, 2, gl.FLOAT, false, bytesPerImage, 8)
gl.enableVertexAttribArray(attribute)
glExtension.vertexAttribDivisorANGLE(attribute, 1)

// Then read texX,texY as short from the vertex array and store them in "texPos".
// UPDATE: changed to short.
gl.vertexAttribPointer(attribute, 2, gl.SHORT, false, bytesPerImage, 16)

gl.enableVertexAttribArray(attribute)
glExtension.vertexAttribDivisorANGLE(attribute, 1)

// Also read 2 slots for size of the part of the texture you are drawing.
// UPDATE: changed to short.
gl.vertexAttribPointer(attribute, 2, gl.SHORT, false, bytesPerImage, 20)

gl.enableVertexAttribArray(attribute)
glExtension.vertexAttribDivisorANGLE(attribute, 1)

// Then read r,g,b,a as byte from my vertices[] array and store them in "rgba".
// UPDATE: changed to byte.
gl.vertexAttribPointer(attribute, 4, gl.UNSIGNED_BYTE, false, bytesPerImage, 24)

gl.enableVertexAttribArray(attribute)
glExtension.vertexAttribDivisorANGLE(attribute, 1)

// Tell webGL that when we set the opacity, it should be semi transparent above what was already drawn.
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.enable(gl.BLEND)

var image = new Image()
{
// Create a gl texture from our JS image object.
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture())
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
gl.activeTexture(gl.TEXTURE0)

// Tell gl that when draw images scaled up, smooth it.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)

// Save texture dimensions in our shader.

}
image.src = "tiles.png"

// Call onresize to set the initial canvas size.
window.onresize()
}

window.onresize = function() {
// To test we'll make the canvas be 1/3 the browser width. Try resizing your browser.
var width = Math.floor(innerWidth / 3)
var height = 500
myCanvas.style.width = width+"px"
myCanvas.style.height = height+"px"
myCanvas.setAttribute("width", width)
myCanvas.setAttribute("height", height)

// Set the viewport size to be the whole canvas.
gl.viewport(0, 0, width, height)
// Set our shader variable for canvas size. It's a vec2 that holds both width and height.

}

glSetup()
gameLoop()
}
```
HTML:
`					`