Search code examples
javascriptcanvasleafletweb-workeroffscreen-canvas

Offscreen-Canvas not rendering some of the time


I am trying to implement a in browser raster drawing plugin for the leaflet library that that extends the leaflets GridLayer api. Essentially for every tile there is function createTile that returns a canvas with some drawing on it. and leaflet shows the tile in correct position.

    
    initialize: function(raster_data){


        this.raster_data = raster_data;
        
    },

    createTile: function (tile_coords) {

        let _tile = document.createElement('canvas');
        
        let _tile_ctx = _tile.getContext('2d');

        // do some drawing here with values from this.raster_data

        return _tile;
    }

This implementation is so far working fine. Than I thought of offloading drawing with offscreen-canvas in a webworker. so I restructured the code like this

    
    initialize: function(raster_data){


        this.raster_data = raster_data;
        this.tile_worker = new Worker('tile_renderer.js')
        
    },

    createTile: function (tile_coords) {

        let _tile = document.createElement('canvas').transferControlToOffscreen();
        
        this.tile_worker.postMessage({
            _tile: _tile,
            _raster_data: this.raster_data
        },[_tile])

        

        return _tile;
    }

This works but every now and then i see a canvas that is just blank. That thing is quite random I don't know start from where and how should I debug this. can this be a problem that I am using a single worker for rendering every tile? any help is appreciated. Here is an example of a blank canvas. example blank canvas


Solution

  • This a known bug: https://crbug.com/1202481

    The issue appears when too many OffscreenCanvases are sent to the Worker serially.

    The workaround is then to batch send all these OffscreenCanvases in a single call to postMessage().
    In your code you could achieve this by storing all the objects to be sent and use a simple debouncing strategy using a 0 timeout to send them all at once:

    createTile: function (tile_coords) {
    
      let _tile = document.createElement('canvas');
    
      _tile.setAttribute('width', 512);
      _tile.setAttribute('height', 512);
    
      let _off_tile = _tile.transferControlToOffscreen();
    
    
      this.tiles_to_add.push( _off_tile ); // store for later
      clearTimeout( this.batch_timeout_id ); // so that the callback is called only once
      this.batch_timeout_id = setTimeout( () => { 
        // send them all at once
        this.tileWorker.postMessage( { tiles: this.tiles_to_add }, this.tiles_to_add );
        this.tiles_to_add.length = 0;
      });
    
      return _tile;
    
    }
    

    Live example: https://artistic-quill-tote.glitch.me/