PaintProfilerView.js 13 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. export class PaintProfilerView extends UI.HBox{constructor(showImageCallback){super(true);this.registerRequiredCSS('layer_viewer/paintProfiler.css');this.contentElement.classList.add('paint-profiler-overview');this._canvasContainer=this.contentElement.createChild('div','paint-profiler-canvas-container');this._progressBanner=this.contentElement.createChild('div','full-widget-dimmed-banner hidden');this._progressBanner.textContent=Common.UIString('Profiling\u2026');this._pieChart=new PerfUI.PieChart({chartName:ls`Profiling Results`,size:55,formatter:this._formatPieChartTime.bind(this)});this._pieChart.element.classList.add('paint-profiler-pie-chart');this.contentElement.appendChild(this._pieChart.element);this._showImageCallback=showImageCallback;this._canvas=this._canvasContainer.createChild('canvas','fill');this._context=this._canvas.getContext('2d');this._selectionWindow=new PerfUI.OverviewGrid.Window(this._canvasContainer);this._selectionWindow.addEventListener(PerfUI.OverviewGrid.Events.WindowChanged,this._onWindowChanged,this);this._innerBarWidth=4*window.devicePixelRatio;this._minBarHeight=window.devicePixelRatio;this._barPaddingWidth=2*window.devicePixelRatio;this._outerBarWidth=this._innerBarWidth+this._barPaddingWidth;this._pendingScale=1;this._scale=this._pendingScale;this._reset();}
  2. static categories(){if(PaintProfilerView._categories){return PaintProfilerView._categories;}
  3. PaintProfilerView._categories={shapes:new PaintProfilerCategory('shapes',Common.UIString('Shapes'),'rgb(255, 161, 129)'),bitmap:new PaintProfilerCategory('bitmap',Common.UIString('Bitmap'),'rgb(136, 196, 255)'),text:new PaintProfilerCategory('text',Common.UIString('Text'),'rgb(180, 255, 137)'),misc:new PaintProfilerCategory('misc',Common.UIString('Misc'),'rgb(206, 160, 255)')};return PaintProfilerView._categories;}
  4. static _initLogItemCategories(){if(PaintProfilerView._logItemCategoriesMap){return PaintProfilerView._logItemCategoriesMap;}
  5. const categories=PaintProfilerView.categories();const logItemCategories={};logItemCategories['Clear']=categories['misc'];logItemCategories['DrawPaint']=categories['misc'];logItemCategories['DrawData']=categories['misc'];logItemCategories['SetMatrix']=categories['misc'];logItemCategories['PushCull']=categories['misc'];logItemCategories['PopCull']=categories['misc'];logItemCategories['Translate']=categories['misc'];logItemCategories['Scale']=categories['misc'];logItemCategories['Concat']=categories['misc'];logItemCategories['Restore']=categories['misc'];logItemCategories['SaveLayer']=categories['misc'];logItemCategories['Save']=categories['misc'];logItemCategories['BeginCommentGroup']=categories['misc'];logItemCategories['AddComment']=categories['misc'];logItemCategories['EndCommentGroup']=categories['misc'];logItemCategories['ClipRect']=categories['misc'];logItemCategories['ClipRRect']=categories['misc'];logItemCategories['ClipPath']=categories['misc'];logItemCategories['ClipRegion']=categories['misc'];logItemCategories['DrawPoints']=categories['shapes'];logItemCategories['DrawRect']=categories['shapes'];logItemCategories['DrawOval']=categories['shapes'];logItemCategories['DrawRRect']=categories['shapes'];logItemCategories['DrawPath']=categories['shapes'];logItemCategories['DrawVertices']=categories['shapes'];logItemCategories['DrawDRRect']=categories['shapes'];logItemCategories['DrawBitmap']=categories['bitmap'];logItemCategories['DrawBitmapRectToRect']=categories['bitmap'];logItemCategories['DrawBitmapMatrix']=categories['bitmap'];logItemCategories['DrawBitmapNine']=categories['bitmap'];logItemCategories['DrawSprite']=categories['bitmap'];logItemCategories['DrawPicture']=categories['bitmap'];logItemCategories['DrawText']=categories['text'];logItemCategories['DrawPosText']=categories['text'];logItemCategories['DrawPosTextH']=categories['text'];logItemCategories['DrawTextOnPath']=categories['text'];PaintProfilerView._logItemCategoriesMap=logItemCategories;return logItemCategories;}
  6. static _categoryForLogItem(logItem){const method=logItem.method.toTitleCase();const logItemCategories=PaintProfilerView._initLogItemCategories();let result=logItemCategories[method];if(!result){result=PaintProfilerView.categories()['misc'];logItemCategories[method]=result;}
  7. return result;}
  8. onResize(){this._update();}
  9. async setSnapshotAndLog(snapshot,log,clipRect){this._reset();this._snapshot=snapshot;if(this._snapshot){this._snapshot.addReference();}
  10. this._log=log;this._logCategories=this._log.map(PaintProfilerView._categoryForLogItem);if(!this._snapshot){this._update();this._pieChart.setTotal(0);this._selectionWindow.setEnabled(false);return;}
  11. this._selectionWindow.setEnabled(true);this._progressBanner.classList.remove('hidden');this._updateImage();const profiles=await snapshot.profile(clipRect);this._progressBanner.classList.add('hidden');this._profiles=profiles;this._update();this._updatePieChart();}
  12. setScale(scale){const needsUpdate=scale>this._scale;const predictiveGrowthFactor=2;this._pendingScale=Math.min(1,scale*predictiveGrowthFactor);if(needsUpdate&&this._snapshot){this._updateImage();}}
  13. _update(){this._canvas.width=this._canvasContainer.clientWidth*window.devicePixelRatio;this._canvas.height=this._canvasContainer.clientHeight*window.devicePixelRatio;this._samplesPerBar=0;if(!this._profiles||!this._profiles.length){return;}
  14. const maxBars=Math.floor((this._canvas.width-2*this._barPaddingWidth)/this._outerBarWidth);const sampleCount=this._log.length;this._samplesPerBar=Math.ceil(sampleCount/maxBars);let maxBarTime=0;const barTimes=[];const barHeightByCategory=[];let heightByCategory={};for(let i=0,lastBarIndex=0,lastBarTime=0;i<sampleCount;){let categoryName=(this._logCategories[i]&&this._logCategories[i].name)||'misc';const sampleIndex=this._log[i].commandIndex;for(let row=0;row<this._profiles.length;row++){const sample=this._profiles[row][sampleIndex];lastBarTime+=sample;heightByCategory[categoryName]=(heightByCategory[categoryName]||0)+sample;}
  15. ++i;if(i-lastBarIndex===this._samplesPerBar||i===sampleCount){const factor=this._profiles.length*(i-lastBarIndex);lastBarTime/=factor;for(categoryName in heightByCategory){heightByCategory[categoryName]/=factor;}
  16. barTimes.push(lastBarTime);barHeightByCategory.push(heightByCategory);if(lastBarTime>maxBarTime){maxBarTime=lastBarTime;}
  17. lastBarTime=0;heightByCategory={};lastBarIndex=i;}}
  18. const paddingHeight=4*window.devicePixelRatio;const scale=(this._canvas.height-paddingHeight-this._minBarHeight)/maxBarTime;for(let i=0;i<barTimes.length;++i){for(const categoryName in barHeightByCategory[i]){barHeightByCategory[i][categoryName]*=(barTimes[i]*scale+this._minBarHeight)/barTimes[i];}
  19. this._renderBar(i,barHeightByCategory[i]);}}
  20. _renderBar(index,heightByCategory){const categories=PaintProfilerView.categories();let currentHeight=0;const x=this._barPaddingWidth+index*this._outerBarWidth;for(const categoryName in categories){if(!heightByCategory[categoryName]){continue;}
  21. currentHeight+=heightByCategory[categoryName];const y=this._canvas.height-currentHeight;this._context.fillStyle=categories[categoryName].color;this._context.fillRect(x,y,this._innerBarWidth,heightByCategory[categoryName]);}}
  22. _onWindowChanged(){this.dispatchEventToListeners(Events.WindowChanged);this._updatePieChart();if(this._updateImageTimer){return;}
  23. this._updateImageTimer=setTimeout(this._updateImage.bind(this),100);}
  24. _updatePieChart(){const window=this.selectionWindow();if(!this._profiles||!this._profiles.length||!window){return;}
  25. let totalTime=0;const timeByCategory={};for(let i=window.left;i<window.right;++i){const logEntry=this._log[i];const category=PaintProfilerView._categoryForLogItem(logEntry);timeByCategory[category.color]=timeByCategory[category.color]||0;for(let j=0;j<this._profiles.length;++j){const time=this._profiles[j][logEntry.commandIndex];totalTime+=time;timeByCategory[category.color]+=time;}}
  26. this._pieChart.setTotal(totalTime/this._profiles.length);for(const color in timeByCategory){this._pieChart.addSlice(timeByCategory[color]/this._profiles.length,color);}}
  27. _formatPieChartTime(value){return Number.millisToString(value*1000,true);}
  28. selectionWindow(){if(!this._log){return null;}
  29. const screenLeft=this._selectionWindow.windowLeft*this._canvas.width;const screenRight=this._selectionWindow.windowRight*this._canvas.width;const barLeft=Math.floor(screenLeft/this._outerBarWidth);const barRight=Math.floor((screenRight+this._innerBarWidth-this._barPaddingWidth/2)/this._outerBarWidth);const stepLeft=Number.constrain(barLeft*this._samplesPerBar,0,this._log.length-1);const stepRight=Number.constrain(barRight*this._samplesPerBar,0,this._log.length);return{left:stepLeft,right:stepRight};}
  30. _updateImage(){delete this._updateImageTimer;let left;let right;const window=this.selectionWindow();if(this._profiles&&this._profiles.length&&window){left=this._log[window.left].commandIndex;right=this._log[window.right-1].commandIndex;}
  31. const scale=this._pendingScale;this._snapshot.replay(scale,left,right).then(image=>{if(!image){return;}
  32. this._scale=scale;this._showImageCallback(image);});}
  33. _reset(){if(this._snapshot){this._snapshot.release();}
  34. this._snapshot=null;this._profiles=null;this._selectionWindow.reset();this._selectionWindow.setEnabled(false);}}
  35. export const Events={WindowChanged:Symbol('WindowChanged')};export class PaintProfilerCommandLogView extends UI.ThrottledWidget{constructor(){super();this.setMinimumSize(100,25);this.element.classList.add('overflow-auto');this._treeOutline=new UI.TreeOutlineInShadow();UI.ARIAUtils.setAccessibleName(this._treeOutline.contentElement,ls`Command Log`);this.element.appendChild(this._treeOutline.element);this._log=[];}
  36. setCommandLog(log){this._log=log;this._treeItemCache=new Map();this.updateWindow({left:0,right:this._log.length});}
  37. _appendLogItem(logItem){let treeElement=this._treeItemCache.get(logItem);if(!treeElement){treeElement=new LogTreeElement(this,logItem);this._treeItemCache.set(logItem,treeElement);}else if(treeElement.parent){return;}
  38. this._treeOutline.appendChild(treeElement);}
  39. updateWindow(selectionWindow){this._selectionWindow=selectionWindow;this.update();}
  40. doUpdate(){if(!this._selectionWindow||!this._log.length){this._treeOutline.removeChildren();return Promise.resolve();}
  41. const root=this._treeOutline.rootElement();for(;;){const child=root.firstChild();if(!child||child._logItem.commandIndex>=this._selectionWindow.left){break;}
  42. root.removeChildAtIndex(0);}
  43. for(;;){const child=root.lastChild();if(!child||child._logItem.commandIndex<this._selectionWindow.right){break;}
  44. root.removeChildAtIndex(root.children().length-1);}
  45. for(let i=this._selectionWindow.left,right=this._selectionWindow.right;i<right;++i){this._appendLogItem(this._log[i]);}
  46. return Promise.resolve();}}
  47. export class LogTreeElement extends UI.TreeElement{constructor(ownerView,logItem){super('',!!logItem.params);this._logItem=logItem;this._ownerView=ownerView;this._filled=false;}
  48. onattach(){this._update();}
  49. async onpopulate(){for(const param in this._logItem.params){LogPropertyTreeElement._appendLogPropertyItem(this,param,this._logItem.params[param]);}}
  50. _paramToString(param,name){if(typeof param!=='object'){return typeof param==='string'&&param.length>100?name:JSON.stringify(param);}
  51. let str='';let keyCount=0;for(const key in param){if(++keyCount>4||typeof param[key]==='object'||(typeof param[key]==='string'&&param[key].length>100)){return name;}
  52. if(str){str+=', ';}
  53. str+=param[key];}
  54. return str;}
  55. _paramsToString(params){let str='';for(const key in params){if(str){str+=', ';}
  56. str+=this._paramToString(params[key],key);}
  57. return str;}
  58. _update(){const title=createDocumentFragment();title.createTextChild(this._logItem.method+'('+this._paramsToString(this._logItem.params)+')');this.title=title;}}
  59. export class LogPropertyTreeElement extends UI.TreeElement{constructor(property){super();this._property=property;}
  60. static _appendLogPropertyItem(element,name,value){const treeElement=new LogPropertyTreeElement({name:name,value:value});element.appendChild(treeElement);if(value&&typeof value==='object'){for(const property in value){LogPropertyTreeElement._appendLogPropertyItem(treeElement,property,value[property]);}}}
  61. onattach(){const title=createDocumentFragment();const nameElement=title.createChild('span','name');nameElement.textContent=this._property.name;const separatorElement=title.createChild('span','separator');separatorElement.textContent=': ';if(this._property.value===null||typeof this._property.value!=='object'){const valueElement=title.createChild('span','value');valueElement.textContent=JSON.stringify(this._property.value);valueElement.classList.add('cm-js-'+(this._property.value===null?'null':typeof this._property.value));}
  62. this.title=title;}}
  63. export class PaintProfilerCategory{constructor(name,title,color){this.name=name;this.title=title;this.color=color;}}
  64. self.LayerViewer=self.LayerViewer||{};LayerViewer=LayerViewer||{};LayerViewer.PaintProfilerView=PaintProfilerView;LayerViewer.PaintProfilerView.Events=Events;LayerViewer.PaintProfilerCommandLogView=PaintProfilerCommandLogView;LayerViewer.LogTreeElement=LogTreeElement;LayerViewer.LogPropertyTreeElement=LogPropertyTreeElement;LayerViewer.PaintProfilerCategory=PaintProfilerCategory;