Diego
Diego

Reputation: 649

why is WebGL slower than Canvas 2D in my game?

I am adding WebGL support in my game, but I have a strange problem : it runs even slower than in Canvas 2D rendering mode, and I do not understand why.

I checked on both Firefox PC, Chrome PC, and Chrome Android, they run WebGL demos on the web with hundreds of sprites smoothly though, so I definitly made an error in my code.

Firefox's profiler says the whole game uses only 7% of the ressources, the rendering part takes only 1.2%. It is just the title screen of the game and there are only five sprites to draw. It is slow though...

update : Chrome's profiler says idle is only 4%, program is a huge 93%, and render 2.6%.
When using Canvas 2D things are very different, 76% idle, 16% program, 2.3% for the drawing function.
There definitly is a problem in my WebGL rendering code.

update : Android Chrome's profiler (on JXD S5110) always says program is ~39%, drawArrays is ~ 8%, bufferData ~5%, and bindTexture is 3%. Everything else is quite negligible.

If a function of mines was wasting all the ressources I would know what to do, but here the bottlenecks seem to be "program" (the browser itself ?) and webgl methods, two things I can't edit.

Please someone have a look at my code and tell me what I did wrong.

Here are my shaders

<script id="2d-vertex-shader" type="x-shader/x-vertex">
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    uniform vec2 u_resolution;
    uniform vec2 u_translation;
    uniform vec2 u_rotation;
    varying vec2 v_texCoord;
    void main()
    {
        // Rotate the position
        vec2 rotatedPosition = vec2(
             a_position.x * u_rotation.y + a_position.y * u_rotation.x,
             a_position.y * u_rotation.y - a_position.x * u_rotation.x);

        // Add in the translation.
        vec2 position = rotatedPosition + u_translation;

        // convert the rectangle from pixels to 0.0 to 1.0
        vec2 zeroToOne = a_position / u_resolution;

        // convert from 0->1 to 0->2
        vec2 zeroToTwo = zeroToOne * 2.0;

        // convert from 0->2 to -1->+1 (clipspace)
        vec2 clipSpace = zeroToTwo - 1.0;

        gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

        // pass the texCoord to the fragment shader
        // The GPU will interpolate this value between points
        v_texCoord = a_texCoord;
    }
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;

    // our texture
    uniform sampler2D u_image;

    // the texCoords passed in from the vertex shader.
    varying vec2 v_texCoord;

    void main()
    {
         // Look up a color from the texture.
         gl_FragColor = texture2D(u_image, vec2(v_texCoord.s, v_texCoord.t));
    }
</script>

Here is the creation code of my canvas and their contexts when in WebGL mode. (I use to use several layered canvas in order to avoid drawing the backgrounds and foregrounds at every frame while they never change, that is why canvas and contexts are in arrays.)

// Get A WebGL context
liste_canvas[c] = document.createElement("canvas") ;
document.getElementById('game_div').appendChild(liste_canvas[c]);
liste_ctx[c] = liste_canvas[c].getContext('webgl',{premultipliedAlpha:false}) || liste_canvas[c].getContext('experimental-webgl',{premultipliedAlpha:false});
var gl = liste_ctx[c] ;
gl.viewport(0, 0, game.res_w, game.res_h);

// setup a GLSL program
gl.vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
gl.fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
gl.program = createProgram(gl, [gl.vertexShader, gl.fragmentShader]);
gl.useProgram(gl.program);

// look up where the vertex data needs to go.
positionLocation = gl.getAttribLocation(gl.program, "a_position");
texCoordLocation = gl.getAttribLocation(gl.program, "a_texCoord");

// provide texture coordinates for the rectangle.
texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0.0,  0.0,
    1.0,  0.0,
    0.0,  1.0,
    0.0,  1.0,
    1.0,  0.0,
    1.0,  1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable( gl.BLEND ) ;

gl.posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, gl.posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    0.0,  0.0,
    1.0,  0.0,
    0.0,  1.0,
    0.0,  1.0,
    1.0,  0.0,
    1.0,  1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

In the .onload function of my images, I add

var gl = liste_ctx[c] ;

this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this );

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);

And here is the WebGL part of my draw_sprite() function :

var gl = liste_ctx[c] ;

gl.bindTexture(gl.TEXTURE_2D, sprites[d_sprite].texture);

var resolutionLocation = gl.getUniformLocation(gl.program, "u_resolution");
gl.uniform2f(resolutionLocation, liste_canvas[c].width, liste_canvas[c].height);

gl.bindBuffer(gl.ARRAY_BUFFER, gl.posBuffer);

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     topleft_x , topleft_y ,
     topright_x , topright_y ,
     bottomleft_x , bottomleft_y ,
     bottomleft_x , bottomleft_y ,
     topright_x , topright_y ,
     bottomright_x , bottomright_y ]), gl.STATIC_DRAW);

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

What did I do wrong ?

Upvotes: 0

Views: 1426

Answers (2)

Diego
Diego

Reputation: 649

What was making it so slow was using several webgl canvas, I use only one now and it works way better. But it is still a bit slower than Canvas 2D though, and the profiler says 65% is idle while it lags as hell so I really don't understand...

edit : I think I got it. Since my computer is running WinXP, hardware acceleration for WebGL can't be enabled, so the browsers use software rendering, and that explains why 'program' is huge in Chrome's profiler. However, hardware acceleration seems to work for 2d context, that is why it is faster.

Upvotes: 1

Josh
Josh

Reputation: 3588

This may help: What do the "Not optimized" warnings in the Chrome Profiler mean?

Relevant links:

  1. https://groups.google.com/forum/#!topic/v8-users/_oZ4fUSitRY
  2. https://github.com/petkaantonov/bluebird/wiki/Optimization-killers

For "optimized too many times", that means the function parameters / behavior change too much, so Chrome keeps having to re-optimize it.

Upvotes: 1

Related Questions