Search code examples
javascriptcanvasrenderingframe-ratelight

Rendering lights in JavaScript so slow. Any idea?


I have a light-rendering method written in JavaScript, which can set the brightness of each object. If I call rendering objects with this filter in the main loop, it'll be very slow (8-10 fps). Is it too much for JavaScript, or just an unoptimized solution?

If I call it outside of the main loop then it's OK.

Here is the Animator class, which has that method I wrote about. You should call the renderImage method with the brightness switch.

Animator: { // Animator class 
  renderImage: function(ImageObject,imageX,imageY,filterData = []) {
  /*
    filterData [] =
    0 -> type
    1 -> value for filtering
  */
  switch( filterData[0] )
  {
    case 'none':
      engComponents.ctx.drawImage(ImageObject,imageX,imageY);
      break;

    case 'brightness':
      engComponents.ctx.drawImage(ImageObject,imageX,imageY);
      pixels = engComponents.ctx.getImageData(imageX,imageY,imageX+50,imageY+50);
      data = pixels.data;
      for (var i=0; i<data.length; i+=4) {
        data[i] += filterData[1];
        data[i+1] += filterData[1];
        data[i+2] += filterData[1];
      }
      engComponents.ctx.putImageData(pixels,imageX,imageY);
      break;
  }

Thank you for any helping!


Solution

  • get/putImageData are fairly slow operations. If you in addition need to do this for each object, then expecting real-time performance update is perhaps a little too much to hope for. It's fully possible to do with compiled code and direct access to a display card, but with JavaScript and canvas including all the fail safes it comes with, we are forced to dwell in trickery.

    One thing you could (probably should) do is to cache variants of the images/objects in question before the game starts. Define some key brightness values and generate a sprite-sheet of those. Then use an index for brightness instead of actual brightness, so you simply draw the cell with the intended brightness to screen instead of calculating during each frame.

    Lets take this guy for instance:

    Imgur

    Now generate a sprite sheet with various brightness values - do this in Photoshop or dynamically in the code (latter shown in demo below):

    sprite

    var sctx = document.getElementById("sprite").getContext("2d"),
        ctx = document.getElementById("main").getContext("2d"),
        img = new Image;
        
    img.crossOrigin = "";
    img.onload = genSprite;
    img.src = "https://i.sstatic.net/pHgNx.png";  // 106x120
    
    function genSprite() {
    
      sctx.canvas.width = 5 * this.width;          // set sprite size: image width x cells
      sctx.canvas.height = this.width;
      sctx.drawImage(this, 0, 0);                  // draw in image to brighten
      
      
      // generate some brightness cells
      var bStep = 25,                              // brightness step per cell
          max = 5,                                 // max sprites
          idata = sctx.getImageData(0, 0, this.width, this.height), // get once only
          data = idata.data,                       
          len = data.length;
      
      for(var i = 0; i < max; i++) {               // iterate to increase values
        for(var p = 0; p < len; p++) {
          data[p++] += bStep;
          data[p++] += bStep;
          data[p++] += bStep;
        }
        sctx.putImageData(idata, this.width * i, 0); // update at cell pos. with new values
      }
      
      // now we have our sprite-sheet, release the Kraken!
      game();
    }
    
    /*==========================================================
    This part is just to demonstrate you can draw many instances
    with varying brightness without suffer from low frame-rate
    ==========================================================*/
    
    function game() {
    
      var dudes = [], // holds Dude objects
          max = 70,   // num. of dudes
          i = 0;
      
      // create game dudes
      while(i++ < max) dudes.push(new Dude(sctx.canvas, ctx));
      
      // animate
      (function loop() {
        ctx.clearRect(0, 0, 500, 500);
        var i = 0;
        while(dude = dudes[i++]) dude.update();  // update dude
        requestAnimationFrame(loop)
      })();
      
    }
    
    function Dude(sprite, ctx) {
      var b = (4 * Math.random())|0,   // brightness index (fractional is ok)
          db = 0.25,                   // step for brightness, we round it to integer later
          w = ctx.canvas.width,        // cache some values
          h = ctx.canvas.height,
          x = w * 0.5,                 // center of canvas
          y = h * 0.5,
          v = 1 + 4 * Math.random(),   // random velocity
          a = Math.PI*2*Math.random(), // random angle
          dx = Math.cos(a) * v,        // steps based on angle
          dy = Math.sin(a) * v;
      
      // updates position and brightness cell
      this.update = function() {
        
        // position:
        x += dx;
        y += dy;
        if (x < -106 || x > w || y < -120 || y > h) {
          x = w * 0.5;
          y = h * 0.5;
        }
        
        // brightness:
        b += db;
        if (b <= 0 || b >= 4) db = -db;
    
        // clip the cell from sprite-sheet and draw to main canvas
        // Note the 106*(b|0) - (b|0) will force integer number which we need.
        ctx.drawImage(sprite, 106*(b|0), 0, 102, sprite.height,  x, y, 106, sprite.height);
      };
    }
    #sprite {border:1px solid #000;margin-bottom:4px;}
    <canvas id="sprite"></canvas><br>
    <canvas id="main" width=500 height=500></canvas>