CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
layered-texture-cache.mjs669 linesDownload Raw Back to canvas
1import * as util from '../../../util/index.mjs';2import * as math from '../../../math.mjs';3import Heap from '../../../heap.mjs';4import * as is from '../../../is.mjs';5import defs from './texture-cache-defs.mjs';6 7var defNumLayers = 1; // default number of layers to use8var minLvl = -4; // when scaling smaller than that we don't need to re-render9var maxLvl = 2; // when larger than this scale just render directly (caching is not helpful)10var maxZoom = 3.99; // beyond this zoom level, layered textures are not used11var deqRedrawThreshold = 50; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile12var refineEleDebounceTime = 50; // time to debounce sharper ele texture updates13var disableEleImgSmoothing = true; // when drawing eles on layers from an ele cache ; crisper and more performant when true14var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame15var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time16var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing17var deqFastCost = 0.9; // % of frame time to be used when >60fps18var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch19var invalidThreshold = 250; // time threshold for disabling b/c of invalidations20var maxLayerArea = 4000 * 4000; // layers can't be bigger than this21var maxLayerDim = 32767; // maximum size for the width/height of layer canvases22var alwaysQueue = true; // never draw all the layers in a level on a frame; draw directly until all dequeued23var useHighQualityEleTxrReqs = true; // whether to use high quality ele txr requests (generally faster and cheaper in the longterm)24 25var useEleTxrCaching = true; // whether to use individual ele texture caching underneath this cache26 27// var log = function(){ console.log.apply( console, arguments ); };28 29var LayeredTextureCache = function( renderer ){30  var self = this;31  var r = self.renderer = renderer;32  var cy = r.cy;33 34  self.layersByLevel = {}; // e.g. 2 => [ layer1, layer2, ..., layerN ]35 36  self.firstGet = true;37 38  self.lastInvalidationTime = util.performanceNow() - 2*invalidThreshold;39 40  self.skipping = false;41 42  self.eleTxrDeqs = cy.collection();43 44  self.scheduleElementRefinement = util.debounce( function(){45    self.refineElementTextures( self.eleTxrDeqs );46 47    self.eleTxrDeqs.unmerge( self.eleTxrDeqs );48  }, refineEleDebounceTime );49 50  r.beforeRender(function( willDraw, now ){51    if( now - self.lastInvalidationTime <= invalidThreshold ){52      self.skipping = true;53    } else {54      self.skipping = false;55    }56  }, r.beforeRenderPriorities.lyrTxrSkip);57 58  var qSort = function(a, b){59    return b.reqs - a.reqs;60  };61 62  self.layersQueue = new Heap( qSort );63 64  self.setupDequeueing();65};66 67var LTCp = LayeredTextureCache.prototype;68 69var layerIdPool = 0;70var MAX_INT = Math.pow(2, 53) - 1;71 72LTCp.makeLayer = function( bb, lvl ){73  var scale = Math.pow( 2, lvl );74 75  var w = Math.ceil( bb.w * scale );76  var h = Math.ceil( bb.h * scale );77 78  var canvas = this.renderer.makeOffscreenCanvas(w, h);79 80  var layer = {81    id: (layerIdPool = ++layerIdPool % MAX_INT ),82    bb: bb,83    level: lvl,84    width: w,85    height: h,86    canvas: canvas,87    context: canvas.getContext('2d'),88    eles: [],89    elesQueue: [],90    reqs: 091  };92 93  // log('make layer %s with w %s and h %s and lvl %s', layer.id, layer.width, layer.height, layer.level);94 95  var cxt = layer.context;96  var dx = -layer.bb.x1;97  var dy = -layer.bb.y1;98 99  // do the transform on creation to save cycles (it's the same for all eles)100  cxt.scale( scale, scale );101  cxt.translate( dx, dy );102 103  return layer;104};105 106LTCp.getLayers = function( eles, pxRatio, lvl ){107  var self = this;108  var r = self.renderer;109  var cy = r.cy;110  var zoom = cy.zoom();111  var firstGet = self.firstGet;112 113  self.firstGet = false;114 115  // log('--\nget layers with %s eles', eles.length);116  //log eles.map(function(ele){ return ele.id() }) );117 118  if( lvl == null ){119    lvl = Math.ceil( math.log2( zoom * pxRatio ) );120 121    if( lvl < minLvl ){122      lvl = minLvl;123    } else if( zoom >= maxZoom || lvl > maxLvl ){124      return null;125    }126  }127 128  self.validateLayersElesOrdering( lvl, eles );129 130  var layersByLvl = self.layersByLevel;131  var scale = Math.pow( 2, lvl );132  var layers = layersByLvl[ lvl ] = layersByLvl[ lvl ] || [];133  var bb;134 135  var lvlComplete = self.levelIsComplete( lvl, eles );136  var tmpLayers;137 138  var checkTempLevels = function(){139    var canUseAsTmpLvl = function( l ){140      self.validateLayersElesOrdering( l, eles );141 142      if( self.levelIsComplete( l, eles ) ){143        tmpLayers = layersByLvl[l];144        return true;145      }146    };147 148    var checkLvls = function( dir ){149      if( tmpLayers ){ return; }150 151      for( var l = lvl + dir; minLvl <= l && l <= maxLvl; l += dir ){152        if( canUseAsTmpLvl(l) ){ break; }153      }154    };155 156    checkLvls( +1 );157    checkLvls( -1 );158 159    // remove the invalid layers; they will be replaced as needed later in this function160    for( var i = layers.length - 1; i >= 0; i-- ){161      var layer = layers[i];162 163      if( layer.invalid ){164        util.removeFromArray( layers, layer );165      }166    }167  };168 169  if( !lvlComplete ){170    // if the current level is incomplete, then use the closest, best quality layerset temporarily171    // and later queue the current layerset so we can get the proper quality level soon172 173    checkTempLevels();174 175  } else {176    // log('level complete, using existing layers\n--');177    return layers;178  }179 180  var getBb = function(){181    if( !bb ){182      bb = math.makeBoundingBox();183 184      for( var i = 0; i < eles.length; i++ ){185        math.updateBoundingBox( bb, eles[i].boundingBox() );186      }187    }188 189    return bb;190  };191 192  var makeLayer = function( opts ){193    opts = opts || {};194 195    var after = opts.after;196 197    getBb();198 199    var w = Math.ceil( bb.w * scale );200    var h = Math.ceil( bb.h * scale );201    202    if( w > maxLayerDim || h > maxLayerDim ){203      return null;204    }205 206    var area = w * h;207 208    if( area > maxLayerArea ){209      return null;210    }211 212    var layer = self.makeLayer( bb, lvl );213 214    if( after != null ){215      var index = layers.indexOf( after ) + 1;216 217      layers.splice( index, 0, layer );218    } else if( opts.insert === undefined || opts.insert ){219      // no after specified => first layer made so put at start220      layers.unshift( layer );221    }222 223    // if( tmpLayers ){224      //self.queueLayer( layer );225    // }226 227    return layer;228  };229 230  if( self.skipping && !firstGet ){231    // log('skip layers');232    return null;233  }234 235  // log('do layers');236 237  var layer = null;238  var maxElesPerLayer = eles.length / defNumLayers;239  var allowLazyQueueing = alwaysQueue && !firstGet;240 241  for( var i = 0; i < eles.length; i++ ){242    var ele = eles[i];243    var rs = ele._private.rscratch;244    var caches = rs.imgLayerCaches = rs.imgLayerCaches || {};245 246    // log('look at ele', ele.id());247 248    var existingLayer = caches[ lvl ];249 250    if( existingLayer ){251      // reuse layer for later eles252      // log('reuse layer for', ele.id());253      layer = existingLayer;254      continue;255    }256 257    if(258      !layer259      || layer.eles.length >= maxElesPerLayer260      || !math.boundingBoxInBoundingBox( layer.bb, ele.boundingBox() )261    ){262      // log('make new layer for ele %s', ele.id());263 264      layer = makeLayer({ insert: true, after: layer });265 266      // if now layer can be built then we can't use layers at this level267      if( !layer ){ return null; }268 269      // log('new layer with id %s', layer.id);270    }271 272    if( tmpLayers || allowLazyQueueing ){273      // log('queue ele %s in layer %s', ele.id(), layer.id);274      self.queueLayer( layer, ele );275    } else {276      // log('draw ele %s in layer %s', ele.id(), layer.id);277      self.drawEleInLayer( layer, ele, lvl, pxRatio );278    }279 280    layer.eles.push( ele );281 282    caches[ lvl ] = layer;283  }284 285  // log('--');286 287  if( tmpLayers ){ // then we only queued the current layerset and can't draw it yet288    return tmpLayers;289  }290 291  if( allowLazyQueueing ){292    // log('lazy queue level', lvl);293    return null;294  }295 296  return layers;297};298 299// a layer may want to use an ele cache of a higher level to avoid blurriness300// so the layer level might not equal the ele level301LTCp.getEleLevelForLayerLevel = function( lvl, pxRatio ){302  return lvl;303};304 305LTCp.drawEleInLayer = function( layer, ele, lvl, pxRatio ){306  var self = this;307  var r = this.renderer;308  var context = layer.context;309  var bb = ele.boundingBox();310 311  if( bb.w === 0 || bb.h === 0 || !ele.visible() ){ return; }312 313  lvl = self.getEleLevelForLayerLevel( lvl, pxRatio );314 315  if( disableEleImgSmoothing ){ r.setImgSmoothing( context, false ); }316 317  if( useEleTxrCaching ){318    r.drawCachedElement( context, ele, null, null, lvl, useHighQualityEleTxrReqs );319  } else { // if the element is not cacheable, then draw directly320    r.drawElement( context, ele );321  }322 323  if( disableEleImgSmoothing ){ r.setImgSmoothing( context, true ); }324};325 326LTCp.levelIsComplete = function( lvl, eles ){327  var self = this;328  var layers = self.layersByLevel[ lvl ];329 330  if( !layers || layers.length === 0 ){ return false; }331 332  var numElesInLayers = 0;333 334  for( var i = 0; i < layers.length; i++ ){335    var layer = layers[i];336 337    // if there are any eles needed to be drawn yet, the level is not complete338    if( layer.reqs > 0 ){ return false; }339 340    // if the layer is invalid, the level is not complete341    if( layer.invalid ){ return false; }342 343    numElesInLayers += layer.eles.length;344  }345 346  // we should have exactly the number of eles passed in to be complete347  if( numElesInLayers !== eles.length ){ return false; }348 349  return true;350};351 352LTCp.validateLayersElesOrdering = function( lvl, eles ){353  var layers = this.layersByLevel[ lvl ];354 355  if( !layers ){ return; }356 357  // if in a layer the eles are not in the same order, then the layer is invalid358  // (i.e. there is an ele in between the eles in the layer)359 360  for( var i = 0; i < layers.length; i++ ){361    var layer = layers[i];362    var offset = -1;363 364    // find the offset365    for( var j = 0; j < eles.length; j++ ){366      if( layer.eles[0] === eles[j] ){367        offset = j;368        break;369      }370    }371 372    if( offset < 0 ){373      // then the layer has nonexistent elements and is invalid374      this.invalidateLayer( layer );375      continue;376    }377 378    // the eles in the layer must be in the same continuous order, else the layer is invalid379 380    var o = offset;381 382    for( var j = 0; j < layer.eles.length; j++ ){383      if( layer.eles[j] !== eles[o+j] ){384        // log('invalidate based on ordering', layer.id);385 386        this.invalidateLayer( layer );387        break;388      }389    }390  }391};392 393LTCp.updateElementsInLayers = function( eles, update ){394  var self = this;395  var isEles = is.element( eles[0] );396 397  // collect udpated elements (cascaded from the layers) and update each398  // layer itself along the way399  for( var i = 0; i < eles.length; i++ ){400    var req = isEles ? null : eles[i];401    var ele = isEles ? eles[i] : eles[i].ele;402    var rs = ele._private.rscratch;403    var caches = rs.imgLayerCaches = rs.imgLayerCaches || {};404 405    for( var l = minLvl; l <= maxLvl; l++ ){406      var layer = caches[l];407 408      if( !layer ){ continue; }409 410      // if update is a request from the ele cache, then it affects only411      // the matching level412      if( req && self.getEleLevelForLayerLevel( layer.level ) !== req.level ){413        continue;414      }415 416      update( layer, ele, req );417    }418  }419};420 421LTCp.haveLayers = function(){422  var self = this;423  var haveLayers = false;424 425  for( var l = minLvl; l <= maxLvl; l++ ){426    var layers = self.layersByLevel[l];427 428    if( layers && layers.length > 0 ){429      haveLayers = true;430      break;431    }432  }433 434  return haveLayers;435};436 437LTCp.invalidateElements = function( eles ){438  var self = this;439 440  if( eles.length === 0 ){ return; }441 442  self.lastInvalidationTime = util.performanceNow();443 444  // log('update invalidate layer time from eles');445 446  if( eles.length === 0 || !self.haveLayers() ){ return; }447 448  self.updateElementsInLayers( eles, function invalAssocLayers( layer, ele, req ){449    self.invalidateLayer( layer );450  } );451};452 453LTCp.invalidateLayer = function( layer ){454  // log('update invalidate layer time');455 456  this.lastInvalidationTime = util.performanceNow();457 458  if( layer.invalid ){ return; } // save cycles459 460  var lvl = layer.level;461  var eles = layer.eles;462  var layers = this.layersByLevel[ lvl ];463 464   // log('invalidate layer', layer.id );465 466  util.removeFromArray( layers, layer );467  // layer.eles = [];468 469  layer.elesQueue = [];470 471  layer.invalid = true;472 473  if( layer.replacement ){474    layer.replacement.invalid = true;475  }476 477  for( var i = 0; i < eles.length; i++ ){478    var caches = eles[i]._private.rscratch.imgLayerCaches;479 480    if( caches ){481      caches[ lvl ] = null;482    }483  }484};485 486LTCp.refineElementTextures = function( eles ){487  var self = this;488 489  // log('refine', eles.length);490 491  self.updateElementsInLayers( eles, function refineEachEle( layer, ele, req ){492    var rLyr = layer.replacement;493 494    if( !rLyr ){495      rLyr = layer.replacement = self.makeLayer( layer.bb, layer.level );496      rLyr.replaces = layer;497      rLyr.eles = layer.eles;498 499       // log('make replacement layer %s for %s with level %s', rLyr.id, layer.id, rLyr.level);500    }501 502    if( !rLyr.reqs ){503      for( var i = 0; i < rLyr.eles.length; i++ ){504        self.queueLayer( rLyr, rLyr.eles[i] );505      }506 507       // log('queue replacement layer refinement', rLyr.id);508    }509  } );510};511 512LTCp.enqueueElementRefinement = function( ele ){513  if( !useEleTxrCaching ){ return; }514 515  this.eleTxrDeqs.merge( ele );516  this.scheduleElementRefinement();517};518 519LTCp.queueLayer = function( layer, ele ){520  var self = this;521  var q = self.layersQueue;522  var elesQ = layer.elesQueue;523  var hasId = elesQ.hasId = elesQ.hasId || {};524 525  // if a layer is going to be replaced, queuing is a waste of time526  if( layer.replacement ){ return; }527 528  if( ele ){529    if( hasId[ ele.id() ] ){530      return;531    }532 533    elesQ.push( ele );534    hasId[ ele.id() ] = true;535  }536 537  if( layer.reqs ){538    layer.reqs++;539 540    q.updateItem( layer );541  } else {542    layer.reqs = 1;543 544    q.push( layer );545  }546};547 548LTCp.dequeue = function( pxRatio ){549  var self = this;550  var q = self.layersQueue;551  var deqd = [];552  var eleDeqs = 0;553 554  while( eleDeqs < maxDeqSize ){555    if( q.size() === 0 ){ break; }556 557    var layer = q.peek();558 559    // if a layer has been or will be replaced, then don't waste time with it560    if( layer.replacement ){561       // log('layer %s in queue skipped b/c it already has a replacement', layer.id);562      q.pop();563      continue;564    }565 566    // if this is a replacement layer that has been superceded, then forget it567    if( layer.replaces && layer !== layer.replaces.replacement ){568       // log('layer is no longer the most uptodate replacement; dequeued', layer.id)569      q.pop();570      continue;571    }572 573    if( layer.invalid ){574       // log('replacement layer %s is invalid; dequeued', layer.id);575      q.pop();576      continue;577    }578 579    var ele = layer.elesQueue.shift();580 581    if( ele ){582       // log('dequeue layer %s', layer.id);583 584      self.drawEleInLayer( layer, ele, layer.level, pxRatio );585 586      eleDeqs++;587    }588 589    if( deqd.length === 0 ){590      // we need only one entry in deqd to queue redrawing etc591      deqd.push( true );592    }593 594    // if the layer has all its eles done, then remove from the queue595    if( layer.elesQueue.length === 0 ){596      q.pop();597 598      layer.reqs = 0;599 600       // log('dequeue of layer %s complete', layer.id);601 602      // when a replacement layer is dequeued, it replaces the old layer in the level603      if( layer.replaces ){604        self.applyLayerReplacement( layer );605      }606 607      self.requestRedraw();608    }609  }610 611  return deqd;612};613 614LTCp.applyLayerReplacement = function( layer ){615  var self = this;616  var layersInLevel = self.layersByLevel[ layer.level ];617  var replaced = layer.replaces;618  var index = layersInLevel.indexOf( replaced );619 620  // if the replaced layer is not in the active list for the level, then replacing621  // refs would be a mistake (i.e. overwriting the true active layer)622  if( index < 0 || replaced.invalid ){623     // log('replacement layer would have no effect', layer.id);624    return;625  }626 627  layersInLevel[ index ] = layer; // replace level ref628 629  // replace refs in eles630  for( var i = 0; i < layer.eles.length; i++ ){631    var _p = layer.eles[i]._private;632    var cache = _p.imgLayerCaches = _p.imgLayerCaches || {};633 634    if( cache ){635      cache[ layer.level ] = layer;636    }637  }638 639   // log('apply replacement layer %s over %s', layer.id, replaced.id);640 641  self.requestRedraw();642};643 644LTCp.requestRedraw = util.debounce( function(){645  var r = this.renderer;646 647  r.redrawHint( 'eles', true );648  r.redrawHint( 'drag', true );649  r.redraw();650}, 100 );651 652LTCp.setupDequeueing = defs.setupDequeueing({653  deqRedrawThreshold: deqRedrawThreshold,654  deqCost: deqCost,655  deqAvgCost: deqAvgCost,656  deqNoDrawCost: deqNoDrawCost,657  deqFastCost: deqFastCost,658  deq: function( self, pxRatio ){659    return self.dequeue( pxRatio );660  },661  onDeqd: util.noop,662  shouldRedraw: util.trueify,663  priority: function( self ){664    return self.renderer.beforeRenderPriorities.lyrTxrDeq;665  }666});667 668export default LayeredTextureCache;669 
basant307/AI_Governance_Project · CoolFace