diff --git a/src/lib/canvas-2d-batch-renderers.ts b/src/lib/canvas-2d-batch-renderers.ts new file mode 100644 index 000000000..a217e4f34 --- /dev/null +++ b/src/lib/canvas-2d-batch-renderers.ts @@ -0,0 +1,69 @@ +// This file contains a collection of classes which make it easier to perform +// batch rendering of Canvas2D primitives. The advantage of this over just doing +// ctx.beginPath() ... ctx.rect(...) ... ctx.endPath() is that you can construct +// several different batch renderers are the same time, then decide on their +// paint order at the end. +// +// See FlamechartPanZoomView.renderOverlays for an example of how this is used. + +export interface TextArgs { + text: string + x: number + y: number +} + +export class BatchCanvasTextRenderer { + private argsBatch: TextArgs[] = [] + + text(args: TextArgs) { + this.argsBatch.push(args) + } + + fill(ctx: CanvasRenderingContext2D, color: string) { + if (this.argsBatch.length === 0) return + ctx.fillStyle = color + for (let args of this.argsBatch) { + ctx.fillText(args.text, args.x, args.y) + } + this.argsBatch = [] + } +} + +export interface RectArgs { + x: number + y: number + w: number + h: number +} + +export class BatchCanvasRectRenderer { + private argsBatch: RectArgs[] = [] + + rect(args: RectArgs) { + this.argsBatch.push(args) + } + + private drawPath(ctx: CanvasRenderingContext2D) { + ctx.beginPath() + for (let args of this.argsBatch) { + ctx.rect(args.x, args.y, args.w, args.h) + } + ctx.closePath() + this.argsBatch = [] + } + + fill(ctx: CanvasRenderingContext2D, color: string) { + if (this.argsBatch.length === 0) return + ctx.fillStyle = color + this.drawPath(ctx) + ctx.fill() + } + + stroke(ctx: CanvasRenderingContext2D, color: string, lineWidth: number) { + if (this.argsBatch.length === 0) return + ctx.strokeStyle = color + ctx.lineWidth = lineWidth + this.drawPath(ctx) + ctx.stroke() + } +} diff --git a/src/views/flamechart-pan-zoom-view.tsx b/src/views/flamechart-pan-zoom-view.tsx index 7253fa096..3e30e41f3 100644 --- a/src/views/flamechart-pan-zoom-view.tsx +++ b/src/views/flamechart-pan-zoom-view.tsx @@ -14,6 +14,7 @@ import {style} from './flamechart-style' import {h, Component} from 'preact' import {css} from 'aphrodite' import {ProfileSearchResults} from '../lib/profile-search' +import {BatchCanvasTextRenderer, BatchCanvasRectRenderer} from '../lib/canvas-2d-batch-renderers' interface FlamechartFrameLabel { configSpaceBounds: Rect @@ -171,24 +172,6 @@ export class FlamechartPanZoomView extends Component { const width = frame.end - frame.start const y = this.props.renderInverted ? this.configSpaceSize().y - 1 - depth : depth @@ -251,44 +241,40 @@ export class FlamechartPanZoomView extends Component this.props.configSpaceViewportRect.bottom()) return if (configSpaceBounds.hasIntersectionWith(this.props.configSpaceViewportRect)) { - let outlineColor: string | null = null - if (this.props.searchResults?.getMatchForFrame(frame.node.frame)) { - ctx.fillStyle = Colors.ORANGE - - // TODO(jlfwong): This is really inefficient. Fix it! const physicalRectBounds = configToPhysical.transformRect(configSpaceBounds) - ctx.fillRect( - Math.round(physicalRectBounds.left() + frameOutlineWidth / 2), - Math.round(physicalRectBounds.top() + frameOutlineWidth / 2), - Math.round(Math.max(0, physicalRectBounds.width() - frameOutlineWidth)), - Math.round(Math.max(0, physicalRectBounds.height() - frameOutlineWidth)), - ) + matchedFrameBatch.rect({ + x: Math.round(physicalRectBounds.left() + frameOutlineWidth / 2), + y: Math.round(physicalRectBounds.top() + frameOutlineWidth / 2), + w: Math.round(Math.max(0, physicalRectBounds.width() - frameOutlineWidth)), + h: Math.round(Math.max(0, physicalRectBounds.height() - frameOutlineWidth)), + }) } if (this.props.selectedNode != null && frame.node.frame === this.props.selectedNode.frame) { - if (frame.node === this.props.selectedNode) { - outlineColor = Colors.DARK_BLUE - } else if (ctx.strokeStyle !== Colors.PALE_DARK_BLUE) { - outlineColor = Colors.PALE_DARK_BLUE - } + let batch = + frame.node === this.props.selectedNode + ? directlySelectedOutlineBatch + : indirectlySelectedOutlineBatch - if (outlineColor != null) { - // TODO(jlfwong): This is really inefficient. Fix it! - const physicalRectBounds = configToPhysical.transformRect(configSpaceBounds) - ctx.strokeStyle = outlineColor - ctx.strokeRect( - Math.round(physicalRectBounds.left() + 1 + frameOutlineWidth / 2), - Math.round(physicalRectBounds.top() + 1 + frameOutlineWidth / 2), - Math.round(Math.max(0, physicalRectBounds.width() - 2 - frameOutlineWidth)), - Math.round(Math.max(0, physicalRectBounds.height() - 2 - frameOutlineWidth)), - ) - } + const physicalRectBounds = configToPhysical.transformRect(configSpaceBounds) + batch.rect({ + x: Math.round(physicalRectBounds.left() + 1 + frameOutlineWidth / 2), + y: Math.round(physicalRectBounds.top() + 1 + frameOutlineWidth / 2), + w: Math.round(Math.max(0, physicalRectBounds.width() - 2 - frameOutlineWidth)), + h: Math.round(Math.max(0, physicalRectBounds.height() - 2 - frameOutlineWidth)), + }) } } for (let child of frame.children) { @@ -355,16 +330,39 @@ export class FlamechartPanZoomView extends Component