Using Multiple Textures
This demonstration uses four textures. We wait until all the images have loaded before processing them in a similar way to the single texture on the previous page. For successive textures we need to set the active texture to the currently processed texture and also to bind that texture. When rendering, it is easy to switch between textures by binding and to select the appropriate couple of triangles to display.
You need to remember to add each image that you use to Resources (in the left panel of the Smart Mobile Studio IDE) so that they will be copied to the res folder. Images in which the number of pixels in both the x and y directions is a power of two are better for scaling.
The code follows a screenshot of the program in action. The images on the walls of the box are screenshots of some popular student programs.

Multiple Textures
This code compiles with Versions 2.2 and 3.0 of Smart Mobile Studio to give an effective HTML file on a server as shown above. Some browsers do not show the images when opening the HTML file directly from a local folder on a PC.
unit Form1; interface uses W3C.TypedArray, SmartCL.System, SmartCL.Graphics, SmartCL.Controls, SmartCL.Components, SmartCL.Forms, SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.WebGL, Khronos.WebGl, GLS.Vectors, GLS.Base, W3C.Canvas2DContext; type TForm1 = class(TW3form) protected FFilepaths: array[0..3] of string = ['res/crazypaint.jpg', 'res/destination.png', 'res/Screenshot-5.jpg', 'res/adventure.png']; FCanvas: TW3GraphicContext; gl: JWebGLRenderingContext; rc: TGLRenderingContext; FCubeBuffer, FTexCoordBuffer: TGLArrayBuffer; FFragmentShader: TGLFragmentShader; FVertexShader: TGLVertexShader; FShaderProgram: TGLShaderProgram; FVertexPosAttrib, FTexCoordAttrib, FNumTextures, FLastImageNum: integer; FImages: array of TW3Image; data: array of JImageData; textures: array of JWebGLTexture; procedure InitializeObject; override; procedure Resize; override; procedure SetupScene; procedure Render; procedure ProcessImage(Sender : TObject); end; implementation procedure TForm1.InitializeObject; begin inherited; FNumTextures := length(FFilepaths); FLastImageNum := FNumTextures - 1; for var i := 0 to FLastImageNum do begin FImages[i] := TW3Image.Create(nil); FImages[i].LoadFromURL(FFilepaths[i]); FImages[i].OnLoad := ProcessImage; end; FCanvas := TW3GraphicContext.Create(Self.Handle); gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl')); rc := TGLRenderingContext.Create; rc.gl := gl; end; procedure TForm1.ProcessImage(Sender : TObject); begin dec (FNumTextures); if FNumTextures = 0 then begin for var i := 0 to FLastImageNum do if FImages[i].Handle and FImages[i].Ready then data[i] := JImageData(FImages[i].Handle) else ShowMessage('Error: image unready'); SetupScene; Render; end; end; procedure TForm1.Resize; begin inherited; FCanvas.Handle.width := Min(Width, 500); FCanvas.Handle.height := Min(Height, 500); end; procedure TForm1.SetupScene; begin gl.clearColor(0.4, 0.4, 0.8, 1.0); // Set clear color to blue, fully opaque gl.clearDepth(1.0); // Clear everything gl.enable(gl.DEPTH_TEST); // Enable depth testing gl.depthFunc(gl.LEQUAL); // Near things obscure far things FTexCoordBuffer := TGLArrayBuffer.Create(rc); FTexCoordBuffer.SetData([ 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0 ], abuStatic); FCubeBuffer := TGLArrayBuffer.Create(rc); FCubeBuffer.SetData([ //Vertices of cube //front face: 0, 1, 2, 0, 2, 3 -1.0, -1.0, 1.0, //0 1.0, -1.0, 1.0, //1 1.0, 1.0, 1.0, //2 -1.0, -1.0, 1.0, //0 1.0, 1.0, 1.0, //2 -1.0, 1.0, 1.0, //3 //back face: 4, 5, 6, 4, 6, 7 -1.0, -1.0, -1.0, //4 -1.0, 1.0, -1.0, //5 1.0, 1.0, -1.0, //6 -1.0, -1.0, -1.0, //4 1.0, 1.0, -1.0, //6 1.0, -1.0, -1.0, //7 //right face: 16, 17, 18, 16, 18, 19 1.0, -1.0, -1.0, //16 same as 7 1.0, 1.0, -1.0, //17 same as 6 1.0, 1.0, 1.0, //18 same as 2 1.0, -1.0, -1.0, //16 same as 7 1.0, 1.0, 1.0, //18 same as 2 1.0, -1.0, 1.0, //19 same as 1 //left face: 20, 21, 22, 20, 22, 23 -1.0, -1.0, -1.0, //20 same as 4 -1.0, -1.0, 1.0, //21 same as 0 -1.0, 1.0, 1.0, //22 same as 3 -1.0, -1.0, -1.0, //20 same as 4 -1.0, 1.0, 1.0, //22 same as 3 -1.0, 1.0, -1.0 //23 same as 5 ], abuStatic); // create vertex shader FVertexShader := TGLVertexShader.Create(rc); if not FVertexShader.Compile(#" attribute vec3 aVertexPosition; attribute vec2 a_texCoord; varying vec2 v_texCoord; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; void main(void) { gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); v_texCoord = a_texCoord; }") then raise Exception.Create(FVertexShader.InfoLog); // create fragment shader FFragmentShader := TGLFragmentShader.Create(rc); FFragmentShader.Compile(#" precision mediump float; uniform sampler2D u_image; varying vec2 v_texCoord; void main(void) { gl_FragColor = texture2D(u_image, v_texCoord); }"); // create shader program and link shaders FShaderProgram := TGLShaderProgram.Create(rc); FShaderProgram.Link(FVertexShader, FFragmentShader); FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition"); FTexCoordAttrib := FShaderProgram.AttribLocation("a_texCoord"); for var i := 0 to FLastImageNum do begin textures[i] := gl.createTexture; gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, textures[i]); // Set the parameters so we can render any size image. 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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); // Upload the image into the texture. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data[i]); end; end; procedure TForm1.Render; var ProjectionMatrix, ModelViewMatrix : Matrix4; ModelViewStack : array of Matrix4; procedure RenderCube(x, y, z : Float); begin ModelViewStack.Push(ModelViewMatrix); //save the matrix ModelViewMatrix := ModelViewMatrix.Translate([x, y, z]); ModelViewMatrix := ModelViewMatrix.RotateX(0.5); ModelViewMatrix := ModelViewMatrix.RotateY(0.9); ModelViewMatrix := ModelViewMatrix.RotateZ(0.2 ); FShaderProgram.SetUniform('uModelViewMatrix', ModelViewMatrix); FCubeBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0); FTexCoordBuffer.VertexAttribPointer(FTexCoordAttrib, 2, False, 0, 0); gl.activeTexture(gl.TEXTURE0); for var i := 0 to FLastImageNum do begin gl.bindTexture(gl.TEXTURE_2D, textures[i]); gl.drawArrays(gl.TRIANGLES, i * 6, 6); end; ModelViewMatrix := ModelViewStack.Pop; //restore the matrix end; begin // set viewport to bounds of canvas gl.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height); // clear background gl.clear(gl.COLOR_BUFFER_BIT); FShaderProgram.Use; //Camera field of view of 45 shows the cube just fitting into the canvas ProjectionMatrix := Matrix4.CreatePerspective(40, FCanvas.width / FCanvas.height, 0.1, 100); ModelViewMatrix := Matrix4.Identity; FShaderProgram.SetUniform('uProjectionMatrix', ProjectionMatrix); gl.enableVertexAttribArray(FVertexPosAttrib); gl.enableVertexAttribArray(FTexCoordAttrib); RenderCube(0, 0, -5); end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.