2
votes

I know very little about OpenGL or WebGL. I was wondering if it is possible to pass a fragment shader an argument. Specifically, I'd like to pass it a multidimensional array with the colors to set for each pixel, for example:

[0][0][0] = 1

would be the red component of the pixel at (0, 0).

2
If you're that new to WebGL you might be interested in some tutorials on it.gman

2 Answers

3
votes

The standard way of doing this is by using a texture rather than passing an array.

1
votes

Simon's answer is right on. But took me about 6 hours to actually get it working.Here is my full working code as an HTML file. Hopefully it will save you from all the trouble I went through tonight.

How to use: Click anywhere on the canvas to set or remove a tile.

Tested In: Chrome, IE, Edge:

<!DOCTYPE HTML >
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TEXTURE AS TILEMAP</title>
<!-- AUTHOR: John Mark Isaac Madison           -->
<!-- EMAIL : [email protected]              -->
<!-- SSSSSSSSS SHADER_SECTION START  SSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<style>

    p{ font-size:12pt;}
    h3,p,input,button,br{ 
        padding:0px; 
        margin:0px; 
        font-family:"Andale Mono";
    }
    button,input{
        padding:10px;
    }
</style>
<script id="VERT_SHADER" type="NOT_JAVASCRIPT">
  precision highp float;

  attribute vec2 a_position;
  void main() {
    gl_Position = vec4(a_position, 0, 1);
  }
</script>

<!-- Simpler shader for when you need to       -->
<!-- simplify and hunt down a problem          -->
<script id="FRAG_SHADER_SIMPLE" type="NOT_JAVASCRIPT">
  #ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
    precision highp int;
  #else
    precision mediump float;
    precision mediump int;
  #endif

    uniform int HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT;
    uniform sampler2D u_texture; //<-- "uSampler" here: https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html

  void main(){
    gl_FragColor = vec4( 0.5, 0.5, 0.5, 1.0);
  }

</script>

<script id="FRAG_SHADER" type="NOT_JAVASCRIPT">

    //Must declare precision before declaring
    //any uniforms:
    ////////////////////////////////////////////////
    #ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
    precision highp int;
    #else
    precision mediump float;
    precision mediump int;
    #endif
    ////////////////////////////////////////////////

    //:Width and height of tilemap in TILES:
    uniform int CMAP_WID;
    uniform int CMAP_HIG;

    //SOURCE: https://stackguides.com/questions/36324295/webgl-access-buffer-from-shader
    //: QUOTE[ 
    //:    Make sure your texture filtering 
    //:    is set to gl.NEAREST. 
    //: ]QUOTE  
    //SDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSD//

    uniform int HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT;
    uniform sampler2D u_texture; //<-- "uSampler" here: https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
    vec2 textureSize = vec2(128.0, 128.0);
    vec4 getValueFromTexture(float index) {
       float column = mod(index, textureSize.x);
       float row    = floor(index / textureSize.x);
       vec2 uv = vec2(
          (column + 0.5) / textureSize.x,
          (row    + 0.5) / textureSize.y);
       return texture2D(u_texture, uv);
    }

    //Integer version of function:
    vec4 TVi( int index_i ){
        return getValueFromTexture( float(index_i) );
    }
    //SDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSDSD//

  #define CANVAS_WID  640.0
  #define CANVAS_HIG  480.0

  #define TIL_WID     64.0
  #define TIL_HIG     64.0

  //Uniforms exposed to HTML/JAVASCRIPT:
  uniform float TIL_WID_EDIT;
  uniform float TIL_HIG_EDIT;

  //Time modulates between 0 and 1:
  uniform float TIME_TICKER;

  //Matches iTime of shadertoy website;
  uniform float TIME_UPUP; 

  //Hard code to support a 16x16 tilemap:
  uniform float TIL_MAP[256];
  //uniform float TIL_MAP[8];

  float til_wid;
  float til_hig;


//TFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTFTF//

  vec2 RenderTileBase(){
    vec2 OUT_DATA;


    //Don't render until tile-map size in tiles
    //has been set by javascript:
    if(CMAP_WID == 0){ 
        return OUT_DATA; 
    }
    if(CMAP_HIG == 0){ 
        return OUT_DATA; 
    }

    //If uniforms have not set by user,
    //use the default values set by the #define(s)
    //==========================================//
    if(TIL_WID_EDIT > 0.0){
      til_wid = TIL_WID_EDIT;
    }else{
      til_wid = TIL_WID;
    }

    if(TIL_HIG_EDIT > 0.0){
      til_hig = TIL_HIG_EDIT;
    }else{
      til_hig = TIL_HIG;
    }
    //==========================================//

    //NOTE: on "gl_FragCoord" range:
    //******************************************//
    //web-gl: In terms of pixel/canvas coords.
    //        X-AXIS: 0 to canvas.width - 1
    //        Y-AXIS: 0 to canvas.height- 1
    //OpenGL: In terms of 0 to 1.
    //******************************************//

    //:Calculate number of tiles shown on screen:
    //:This may be fractional:
    float NUM_TIL_X = CANVAS_WID / til_wid;
    float NUM_TIL_Y = CANVAS_HIG / til_hig;

    //DETERMINE WHAT tile you are on:
    float TC_X; //tile coordinate X
    float TC_Y; //tile coordinate Y

    TC_X = floor( gl_FragCoord.x / til_wid );
    TC_Y = floor( gl_FragCoord.y / til_hig );
    int    i_X = int( TC_X ); //integer version of TC_X
    int    i_Y = int( TC_Y ); //integer version of TC_Y
    int    DEX = (i_Y * CMAP_WID) + i_X;

    vec2 FC_MOD;
    FC_MOD.x = gl_FragCoord.x;
    FC_MOD.y = gl_FragCoord.y;

    //You want all tiles to have the full range 
    //of colors, so you always modulate by 
    //CANVAS_WID and CANVAS_HIG, You scale by the 
    //# of tiles on each axis which means the 
    //gradient becomes steeper as the # of tiles
    //increases.
    FC_MOD.x = mod( gl_FragCoord.x*NUM_TIL_X, CANVAS_WID );
    FC_MOD.y = mod( gl_FragCoord.y*NUM_TIL_Y, CANVAS_HIG );

    //[N]ormalize values into range 0 to 1:
    //NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//
    float norm_X = (FC_MOD.x) / CANVAS_WID;
    float norm_Y = (FC_MOD.y) / CANVAS_HIG;

    float NMAP_X; // norm_X value mapped.
    float NMAP_Y; // norm_Y value mapped.
    //NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//

    //Use [B]lue channel because why not?
    //BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
    float GRAD_X = gl_FragCoord.x / CANVAS_WID;
    float GRAD_Y = gl_FragCoord.y / CANVAS_HIG;
    if(GRAD_X > 1.0 || GRAD_X < 0.0){ return OUT_DATA; }
    if(GRAD_Y > 1.0 || GRAD_Y < 0.0){ return OUT_DATA; }

    float tile_value;

    float DEX_F = float(DEX);
    vec4 V4 ; //tile_RGBA

    if( HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT > 0 ){
      V4 = getValueFromTexture( DEX_F );
      tile_value = V4.x;
    }

    if( tile_value==0.0 ){
        NMAP_X = 0.0;
        NMAP_Y = 0.0;
    }else{
        NMAP_X =     TIME_TICKER;
        NMAP_Y = 1.0-TIME_TICKER;
    }
    //BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//


    OUT_DATA.x = NMAP_X;
    OUT_DATA.y = NMAP_Y;
    return OUT_DATA;

  }

  void main() {

    vec2 uv = RenderTileBase();
    gl_FragColor = vec4( uv.x, uv.y, 0, 1.0);
    return;


  }

</script>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSS SHADER_SECTION END  SSSSSSSSSS -->


<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->   
<script id="BOILER_PLATE_CODE" type="text/javascript">

    function ON_LOADED_FUNCTION(){
        console.log("[ON_LOADED_FUNCTION]");
        main();
    }

    //SOURCE:https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
    var HACK_CANVAS = document.createElement('canvas');
    HACK_CANVAS.width = 128*128;
    HACK_CANVAS.height= 1;

    //Global Array Container:
    var GLOBAL_GAC;

    var TIME_UPUP   = 0;
    var TIME_0255   = 0;
    var TIME_2PI    = 0.0;
    var TIME_TICKER = 0.0;
    function UPDATE_TIMER(){
      TIME_UPUP = TIME_UPUP + 0.01;
      TIME_0255 += 0.75;
      if(TIME_0255 > 255){ TIME_0255 = 0; }
      TIME_2PI = (TIME_0255 / 255) * (Math.PI * 2);

      //Use cos to modulate value between 0 and 1:
      //To do so, need to offset by 1 and divide
      //by two to transform [-1, 1] range
      //into [0, 1] range.
      TIME_TICKER = ( Math.cos(TIME_2PI) + 1) / 2;

      //console.log( TIME_TICKER);
      SET_ATTR("TIME_TICKER", TIME_TICKER);
      SET_ATTR("TIME_UPUP"  , TIME_UPUP  );
    }

    //:Takes the gl context object, if the input
    //:is null, we likely failed to get the
    //:context.
    function HAS_OPEN_GL_CHECK(gl){
      // Only continue if WebGL is 
      // available and working
      if (!gl) {
        var msg = "";
        msg += "[Unable to initialize WebGL.]";
        msg += "[your browser or machine may]";
        msg +=  "[not support it.]"
        alert( msg );
        return;
      }
    }

    function GET_INPUT_BOX_VALUE( elem_id ){
        var box; //DOM input box
        var val; //Value in input box.
        box = document.getElementById( elem_id );
        val = box.value;
        return (0 + val); //cast to number.
    }

    function PUT_WID(){
        assert_program_and_gl_exist();
        var val = GET_INPUT_BOX_VALUE("INPUT_WID");
        var res = SET_ATTR_UI("TIL_WID_EDIT", val);

        //For click listener on canvas:
        if(res){ GLOBAL_TIL_WID = val; }
    }

    function PUT_HIG(){
        assert_program_and_gl_exist();
        var val = GET_INPUT_BOX_VALUE("INPUT_HIG");
        var res = SET_ATTR_UI("TIL_HIG_EDIT", val);

        //For click listener on canvas:
        if(res){ GLOBAL_TIL_HIG = val; }
    }

    //Integer Version of set-attribute helper:
    function SET_ATTR_INT(gl_var_name, val){
        var loc; //<--location of variable
        loc = gl.getUniformLocation(
            program,
            gl_var_name
        );

        gl.useProgram(program);
        gl.uniform1i(loc, val );
    }

    //Returns TRUE if successful:
    function SET_ATTR(gl_var_name, val){

        var loc; //<--location of variable.
        loc = gl.getUniformLocation(
            program    , 
            gl_var_name
        );
        gl.useProgram(program);
        gl.uniform1f(loc, val);

        return true;
    }

    //Version of SET_ATTR used for the
    //User Interface (UI):
    function SET_ATTR_UI(gl_var_name, val){
        if(val < 0 || val > 256 ){
            alert("choose value between 0 to 256");

            //Call was ignored, so return false:
            return false;
        }

        return SET_ATTR(gl_var_name, val);
    }


    function assert_program_and_gl_exist(){
        if(!program){慌("[NO_PROGRAM_EXISTS]");}
        if(!gl     ){慌("[NO_GL_EXISTS]");}
    }

    //慌: "disconcerted, be confused, lose one's head"
    //慌: In Code: ~Panic~
    function 慌( panic_message ){
        console.log( panic_message );
        alert      ( panic_message );
        throw      ( panic_message );
    }

    function makeOpenGlContextUsingCanvas(c){

        //:Try what works in chrome and all the
        //:respectable browsers first:
        gl = c.getContext("webgl");

        if(!gl){
            console.log("[Probably_In_IE]");
            gl = c.getContext("experimental-webgl");
        }else{
            console.log("[Probably_NOT_IE]");
        }

        HAS_OPEN_GL_CHECK( gl );
        return gl;
    }

    function CANVAS_CLICK_FUNCTION(event){
        var mp = getMousePos(canvas,event);

        //Correct to match openGL orientation:
        mp.y = canvas.height - mp.y;

        console.log( mp );       

        //Determine tile clicked on:
        var tx = mp.x / GLOBAL_TIL_WID;
        var ty = mp.y / GLOBAL_TIL_HIG;
        tx = Math.floor(tx);
        ty = Math.floor(ty);
        console.log("tx:", tx, "ty:", ty);

        //Convert to index:
        var dex;
        dex = (GLOBAL_GAC.WID * ty) + tx;
        val = GLOBAL_GAC.Get_R( dex );
        if(val!=0){
            val = 0;
        }else{
            val = 1;
        }
        GLOBAL_GAC.Put_R( dex , val );

        //Update The Shader With New TileMap:
        GLOBAL_GAC.pushToGL();
    }

    function getMousePos(GMP_canvas, evt) {
        var rect = GMP_canvas.getBoundingClientRect();
        return {
          x: evt.clientX - rect.left,
          y: evt.clientY - rect.top
        };
    }

    //: No "var" prefix, making them global:
    function initGlobals(){

        //The width and height in PIXELS of a
        //single tile on our tile-map:
        GLOBAL_TIL_WID = 64;
        GLOBAL_TIL_HIG = 64;



        canvas = document.querySelector("#glCanvas");
        if(!canvas){
            alert("FAILED_TO_GET_CANVAS");
        }else{
            console.log("[GOT_CANVAS]");
        }

        //Add listener to canvas:
        canvas.addEventListener('click',CANVAS_CLICK_FUNCTION);


        gl = makeOpenGlContextUsingCanvas(canvas);


        //These dimensions are hard-coded into
        //fragment shader code, so be careful
        //about changing them:
        canvas.width = 640;
        canvas.height= 480;

        buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(
        gl.ARRAY_BUFFER, 
        new Float32Array([
          -1.0, -1.0, 
           1.0, -1.0, 
          -1.0,  1.0, 
          -1.0,  1.0, 
           1.0, -1.0, 
           1.0,  1.0]), 
        gl.STATIC_DRAW
        );

        //G == Global Container.
        //To fix problems with rendering in I.E.
        //(Internet Explorer)
        //GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG//
        var G = {}; 
        G.canvas = canvas;
        G.gl     = gl;
        G.buffer = buffer;

        if( ! G.canvas ||
            ! G.gl     ||
            ! G.buffer  ){
            慌("[Global_Container_Broken]");
        }

        return G;
        //GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG//
    }

    function main(){

      G = initGlobals();
      HAS_OPEN_GL_CHECK( G );

      gl.viewport(0,0,gl.drawingBufferWidth , 
                      gl.drawingBufferHeight);

      setup();
      render();


      SETUP_TILE_MAP_DATA();

    }

    //Need texture/buffer container: 斗
    var GLArrayContainer = function(){
        var _self = this;

        //web-gl texture handle:
        this.TEX = null;

        //Pixel Array:
        this.PIX = null;

        //Number of pixels in Pixel Array:
        this.PIX_NUM = 0;
        this.WID     = 0; //width in pixels.
        this.HIG     = 0; //height in pixels. Sure pixels?

        //Publically Exposed Functions:
        //PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//
        this.setPixelByIndex   = _setPixelByIndex;
        this.makePattern       = _makePattern    ;
        this.solidFill         = _solidFill      ;
        this.pushToGL          = _pushToGL       ;

        this.Get_R             = _get_R;
        this.Put_R             = _put_R;

        //PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//

        //: i == index.
        function _get_R( i ){
            var RED_INDEX = 0;
            var r = _self.PIX[(i*4) + RED_INDEX];
            return r;
        }

        //: i == index.
        function _put_R( i, val){
            var RED_INDEX = 0;
            _self.PIX[(i*4) + RED_INDEX] = val;
        }

        function _setPixelByIndex(i, r,g,b, a){
            //Multiply by 4 to get address
            //of first component of pixel [i]
            //Because each pixel is 4-bytes:
            var b = i*4;

            //RGBA (alpha last)  (NOT : argb)
            _self.PIX[ b + 0 ] = r;
            _self.PIX[ b + 1 ] = g;
            _self.PIX[ b + 2 ] = b;
            _self.PIX[ b + 3 ] = a;
        }

        function _solidFill(){
        if(_self.PIX_NUM <= 0){慌("[BAD_PIX_NUM]");}

        var r,g,b;
            for(var i = 0; i < _self.PIX_NUM; i++){
                _setPixelByIndex(i,225,22,64,255);
            }
        }

        //Make a checker pattern:
        function _makePattern(){
            var EVERY_OTHER = false;
            var i_r = 0;
            var i_g = 0;
            var i_b = 0;
            for(var i = 0; i < _self.PIX_NUM; i++){
                EVERY_OTHER = (!EVERY_OTHER);
                if( EVERY_OTHER ){
                    i_r = 255;
                    i_g = 255;
                    i_b = 255;
                }else{
                    //alert("HEY");
                    i_r = 0;
                    i_g = 0;
                    i_b = 0;
                }
                _setPixelByIndex(
                    i,i_r,i_g,i_b,255
                );
            }
        }

        //Push changes to Web-GL so fragment
        //shader can use the values:
        function _pushToGL(){
            if(!_self.TEX){慌("[TEX_PROBLEM]");}
            console.log("[push_to_gl]");


            gl.activeTexture( gl.TEXTURE1 );
            gl.bindTexture(
                gl.TEXTURE_2D, 
                _self.TEX
            );

           //Will get error:
           //[ WebGL: INVALID_OPERATION:                ]
           //[ texParameter: no texture bound to target ]
           //If no texture is bound to active slot before doing this.
           //:SOURCE:https://stackguides.com/questions/42358462/no-texture-bound-to-the-unit-0
           gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
           gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);

            //:will also throw error if no texture
            //in active texture slot:
            gl.texImage2D(
                gl.TEXTURE_2D    , 
                0                , //LEVEL
                gl.RGBA          , //internalFormat,
                _self.WID        , 
                _self.HIG        , 
                0                , //border
                gl.RGBA          , //srcFormat 
                gl.UNSIGNED_BYTE , //srcType
                _self.PIX          //<--ArrayBufferView object
            );

            SET_ATTR_INT(
           "HAS_TEXTURE_BEEN_PUSHED_FROM_JAVASCRIPT",
           1 );

           //Let shader know tile-map width and height:
           SET_ATTR_INT("CMAP_WID", _self.WID);
           SET_ATTR_INT("CMAP_HIG", _self.HIG);

        }

    }

    TILE_MAP_HAS_BEEN_SETUP = false;
    function SETUP_TILE_MAP_DATA(){
        if(TILE_MAP_HAS_BEEN_SETUP){ 
            alert("[MAP_ALREADY_SETUP]");
            return; 
        }
        TILE_MAP_HAS_BEEN_SETUP = true;

        //Decided on 128x128: 
        //Because it is maximum dimensions of
        //a project I am working on.
        const WID = 128; 
        const HIG = 128; 

        //4 components/bytes in 1 ARGB pixel: 
        const NUM_COM_ARGB = 4; //4;
        const ARRAY_LENGTH = WID*HIG*NUM_COM_ARGB;
        const pixel = new Uint8Array( ARRAY_LENGTH );

        //Create Texure slot at TEXTURE1 because
        //seems to be bug "there is no texture bound to unit 0"
        //in chrome.
        gl.activeTexture(gl.TEXTURE1); 
        const texture = gl.createTexture();
        if(!texture){慌("[NOT_TEXTURE]");}

        //Our texture sampler needs to be bound to 
        //the same texture slot. Hence the "1"
        //https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html
        gl.uniform1i(
            gl.getUniformLocation(
                 program   , 
                "u_texture" //<--Sampler2D's Name.
            ), 
            1  //<--Texture Slot To Bind To.
        );

        //Our pixel array is 4-component, so we
        //can use alignment 4. An alignment of
        //1 will also work.
        //SOURCE: https://webglfundamentals.org/webgl/lessons/webgl-data-textures.html
        const alignment = 4;
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);


      //Populate our helper container: 
      //PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//
      GLOBAL_GAC         = new GLArrayContainer();
      GLOBAL_GAC.PIX     = pixel;
      GLOBAL_GAC.TEX     = texture;      
      GLOBAL_GAC.PIX_NUM = WID*HIG;
      GLOBAL_GAC.WID     = WID;
      GLOBAL_GAC.HIG     = HIG;
      GLOBAL_GAC.makePattern();
      GLOBAL_GAC.pushToGL();
      //setTimeout( 1, GLOBAL_GAC.pushToGL() );
      //PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP//  
    }

    function setup(){
        var frag_dom = document.getElementById("FRAG_SHADER");
        var frag_src = frag_dom.text;
        console.log( frag_src );

        F = createShader(
            gl,gl.FRAGMENT_SHADER, frag_src
        );

        var vert_dom = document.getElementById("VERT_SHADER");
        var vert_src = vert_dom.text;
        console.log( vert_src );

        V = createShader(
            gl, gl.VERTEX_SHADER, vert_src
        );

        //**** MAKE "program" a GLOBAL VAR  ****//
        program = createProgram(gl,V,F);
        gl.useProgram( program );

        if(!program){ 慌("[PROGRAM_IS_NULL]");}
    }

    function render(){
      window.requestAnimationFrame(render,canvas);

      // Set clear color to black, fully opaque
      gl.clearColor(0.0, 0.0, 0.5, 1.0);

      // Clear the color buffer with specified clear color
      gl.clear(gl.COLOR_BUFFER_BIT);

      //Directly before call to gl.drawArrays:
      positionLocation = gl.getAttribLocation(program, "a_position");
      gl.enableVertexAttribArray( positionLocation );
      gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

      UPDATE_TIMER();

      gl.drawArrays(gl.TRIANGLES, 0, 6);
    }

    function createShader(gl,type,source){
        //:Error Check For Bad Inputs:
        if(!gl    ){慌("[NULL_GL]");}
        if(!type  ){慌("[NULL_TY]");}
        if(!source){慌("[NULL_SR]");}

        var shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        var res = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
        if( res ){
            console.log("[SHADER_COMPILED!]");
            return shader;
        }

        console.log(gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        慌("[FAILED_TO_COMPILE_SHADER]");
    }

    //:gl  : openGL context :
    //:vert: vertex shader  :
    //:frag: fragment shader:
    function createProgram(gl,vert, frag){
        var program = gl.createProgram();
        gl.attachShader(program, vert);
        gl.attachShader(program, frag);
        gl.linkProgram (program);
        var res = gl.getProgramParameter(program, gl.LINK_STATUS);
        if( res ){
            console.log("[PROGRAM_CREATED!]");
            return program;
        }

        console.log(gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
    }

</script>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->


</head> 
<!-- HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -->

<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->                    
<body onload="ON_LOADED_FUNCTION()" >
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->

    <h3> Web-GL: Texture As Tilemap           <h3>
    <p> Author: John Mark Isaac Madison <p>
    <p> Email : [email protected]    <p>

    <canvas id="glCanvas"></canvas>

    </br>
    <button onClick="PUT_WID();">TILE_WIDTH__IN_PIXELS</button> 
    <input type="text" id="INPUT_WID" value="32">
    </br>

    </br>
    <button onClick="PUT_HIG();">TILE_HEIGHT_IN_PIXELS</button> 
    <input type="text" id="INPUT_HIG" value="32">
    </br>

<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
</body>
</html>