mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-12-25 19:31:50 +03:00
Set up Prettier and run it on the whole codebase
* Install prettier, set up the config file, and run it on all ts and tsx files. * Install eslint and configure it with just eslint-plugin-prettier to check to make sure that prettier has been run. * Add a basic .travis.yml that runs eslint. There are other style things that might be nice to enforce with ESLint/TSLint, like using const, import order, etc, but this commit just focuses on prettier, which gets most of the way there. One annoying issue for now is that typescript-eslint-parser gives a big warning message since they haven't updated to officially support TypeScript 2.8 yet. We aren't even using any ESLint rules that need the parser, but if we don't include it, ESLint will crash. TS2.8 support is hopefully coming really soon, though: https://github.com/eslint/typescript-eslint-parser/pull/454 As for the prettier config specifically, see https://prettier.io/docs/en/options.html for the available options. Config settings that seem non-controversial: Semicolons: You don't use semicolons. (I prefer semicolons, but either way is fine.) Quote style: Looks like you consistently use single quotes outside JSX and double quotes in JSX, which is the `singleQuote: true` option. Config settings worth discussion: Line width: You don't have a specific max. I put 100 since I think it's a good number for people (like both of us, probably) who find 80 a bit cramped. (At Benchling we use 110.) Prettier has a big red warning box recommending 80, but I still prefer 100ish. Bracket spacing: This is `{foo}` vs `{ foo }` for imports, exports, object literals, and destructuring. Looks like you're inconsistent but lean toward spaces. I personally really dislike bracket spacing (it feels inconsistent with arrays and function calls), but I'm certainly fine with it and Prettier has it enabled by default, so I kept it enabled. Trailing comma style: Options are "no trailing commas", "trailing commas for everything exception function calls and parameter lists", and "trailing commas everywhere". TypeScript can handle trailing commas everywhere, so there isn't a concern with tooling. You're inconsistent, and it looks like you tend to not have trailing commas, but I think it's probably best to just have them everywhere, so I enabled them. JSX Brackets: You're inconsistent about this, I think. I'd prefer to just keep the default and wrap the `>` to the next line. Arrow function parens: I only found two cases of arrow functions with one param (both `c => c.frame === frame`), and both omitted the parens, so I kept the default of omitting parens. This makes it mildly more annoying to add a typescript type or additional param, which is a possible reason for always requiring parens. Everything else is non-configurable, although it's possible some places would be better with a `// prettier-ignore` comment (but I usually try to avoid those).
This commit is contained in:
parent
298056a9ef
commit
1bcb88670b
13
.eslintrc.js
Normal file
13
.eslintrc.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
parser: 'typescript-eslint-parser',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
};
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
.cache
|
||||
dist
|
||||
.idea
|
||||
|
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@ -0,0 +1,3 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '9'
|
293
application.tsx
293
application.tsx
@ -1,25 +1,24 @@
|
||||
import {h} from 'preact'
|
||||
import {StyleSheet, css} from 'aphrodite'
|
||||
import {ReloadableComponent, SerializedComponent} from './reloadable'
|
||||
import { h } from 'preact'
|
||||
import { StyleSheet, css } from 'aphrodite'
|
||||
import { ReloadableComponent, SerializedComponent } from './reloadable'
|
||||
|
||||
import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
||||
import {importFromStackprof} from './import/stackprof'
|
||||
import {importFromChromeTimeline, importFromChromeCPUProfile} from './import/chrome'
|
||||
import { importFromBGFlameGraph } from './import/bg-flamegraph'
|
||||
import { importFromStackprof } from './import/stackprof'
|
||||
import { importFromChromeTimeline, importFromChromeCPUProfile } from './import/chrome'
|
||||
import { FlamechartRenderer } from './flamechart-renderer'
|
||||
import { CanvasContext } from './canvas-context'
|
||||
|
||||
import {Profile, Frame} from './profile'
|
||||
import {Flamechart} from './flamechart'
|
||||
import { Profile, Frame } from './profile'
|
||||
import { Flamechart } from './flamechart'
|
||||
import { FlamechartView } from './flamechart-view'
|
||||
import { FontFamily, FontSize, Colors } from './style'
|
||||
|
||||
|
||||
declare function require(x: string): any
|
||||
const exampleProfileURL = require('./sample/perf-vertx-stacks-01-collapsed-all.txt')
|
||||
|
||||
const enum SortOrder {
|
||||
CHRONO,
|
||||
LEFT_HEAVY
|
||||
LEFT_HEAVY,
|
||||
}
|
||||
|
||||
interface ApplicationState {
|
||||
@ -56,7 +55,7 @@ function importProfile(contents: string, fileName: string): Profile | null {
|
||||
// Second pass: Try to guess what file format it is based on structure
|
||||
try {
|
||||
const parsed = JSON.parse(contents)
|
||||
if (Array.isArray(parsed) && parsed[parsed.length - 1].name === "CpuProfile") {
|
||||
if (Array.isArray(parsed) && parsed[parsed.length - 1].name === 'CpuProfile') {
|
||||
console.log('Importing as Chrome CPU Profile')
|
||||
return importFromChromeTimeline(parsed)
|
||||
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
|
||||
@ -97,31 +96,51 @@ export class Toolbar extends ReloadableComponent<ToolbarProps, void> {
|
||||
render() {
|
||||
const help = (
|
||||
<div className={css(style.toolbarTab)}>
|
||||
<a href="https://github.com/jlfwong/speedscope#usage" className={css(style.noLinkStyle)} target="_blank">
|
||||
<a
|
||||
href="https://github.com/jlfwong/speedscope#usage"
|
||||
className={css(style.noLinkStyle)}
|
||||
target="_blank"
|
||||
>
|
||||
<span className={css(style.emoji)}>❓</span>Help
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!this.props.profile) {
|
||||
return <div className={css(style.toolbar)}>
|
||||
<div className={css(style.toolbarLeft)}>{help}</div>
|
||||
🔬speedscope
|
||||
</div>
|
||||
return (
|
||||
<div className={css(style.toolbar)}>
|
||||
<div className={css(style.toolbarLeft)}>{help}</div>
|
||||
🔬speedscope
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <div className={css(style.toolbar)}>
|
||||
<div className={css(style.toolbarLeft)}>
|
||||
<div className={css(style.toolbarTab, this.props.sortOrder === SortOrder.CHRONO && style.toolbarTabActive)} onClick={this.setTimeOrder}>
|
||||
<span className={css(style.emoji)}>🕰</span>Time Order
|
||||
return (
|
||||
<div className={css(style.toolbar)}>
|
||||
<div className={css(style.toolbarLeft)}>
|
||||
<div
|
||||
className={css(
|
||||
style.toolbarTab,
|
||||
this.props.sortOrder === SortOrder.CHRONO && style.toolbarTabActive,
|
||||
)}
|
||||
onClick={this.setTimeOrder}
|
||||
>
|
||||
<span className={css(style.emoji)}>🕰</span>Time Order
|
||||
</div>
|
||||
<div
|
||||
className={css(
|
||||
style.toolbarTab,
|
||||
this.props.sortOrder === SortOrder.LEFT_HEAVY && style.toolbarTabActive,
|
||||
)}
|
||||
onClick={this.setLeftHeavyOrder}
|
||||
>
|
||||
<span className={css(style.emoji)}>⬅️</span>Left Heavy
|
||||
</div>
|
||||
{help}
|
||||
</div>
|
||||
<div className={css(style.toolbarTab, this.props.sortOrder === SortOrder.LEFT_HEAVY && style.toolbarTabActive)} onClick={this.setLeftHeavyOrder}>
|
||||
<span className={css(style.emoji)}>⬅️</span>Left Heavy
|
||||
</div>
|
||||
{help}
|
||||
{this.props.profile.getName()}
|
||||
<div className={css(style.toolbarRight)}>🔬speedscope</div>
|
||||
</div>
|
||||
{this.props.profile.getName()}
|
||||
<div className={css(style.toolbarRight)}>🔬speedscope</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +207,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
flamechartRenderer: null,
|
||||
sortedFlamechart: null,
|
||||
sortedFlamechartRenderer: null,
|
||||
sortOrder: SortOrder.CHRONO
|
||||
sortOrder: SortOrder.CHRONO,
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +224,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
if (this.canvasContext && flamechart && sortedFlamechart) {
|
||||
this.setState({
|
||||
flamechartRenderer: new FlamechartRenderer(this.canvasContext, flamechart),
|
||||
sortedFlamechartRenderer: new FlamechartRenderer(this.canvasContext, sortedFlamechart)
|
||||
sortedFlamechartRenderer: new FlamechartRenderer(this.canvasContext, sortedFlamechart),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -248,7 +267,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
getTotalWeight: profile.getTotalWeight.bind(profile),
|
||||
forEachCall: profile.forEachCall.bind(profile),
|
||||
formatValue: profile.formatValue.bind(profile),
|
||||
getColorBucketForFrame
|
||||
getColorBucketForFrame,
|
||||
})
|
||||
const flamechartRenderer = new FlamechartRenderer(this.canvasContext, flamechart)
|
||||
|
||||
@ -256,29 +275,32 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
getTotalWeight: profile.getTotalNonIdleWeight.bind(profile),
|
||||
forEachCall: profile.forEachCallGrouped.bind(profile),
|
||||
formatValue: profile.formatValue.bind(profile),
|
||||
getColorBucketForFrame
|
||||
getColorBucketForFrame,
|
||||
})
|
||||
const sortedFlamechartRenderer = new FlamechartRenderer(this.canvasContext, sortedFlamechart)
|
||||
|
||||
console.timeEnd('import')
|
||||
|
||||
console.time('first setState')
|
||||
this.setState({
|
||||
profile,
|
||||
flamechart,
|
||||
flamechartRenderer,
|
||||
sortedFlamechart,
|
||||
sortedFlamechartRenderer,
|
||||
loading: false
|
||||
}, () => {
|
||||
console.timeEnd('first setState')
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
profile,
|
||||
flamechart,
|
||||
flamechartRenderer,
|
||||
sortedFlamechart,
|
||||
sortedFlamechartRenderer,
|
||||
loading: false,
|
||||
},
|
||||
() => {
|
||||
console.timeEnd('first setState')
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
loadFromFile(file: File) {
|
||||
this.setState({ loading: true }, () => {
|
||||
requestAnimationFrame(() => {
|
||||
const reader = new FileReader
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener('loadend', () => {
|
||||
this.loadFromString(file.name, reader.result)
|
||||
})
|
||||
@ -290,9 +312,11 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
loadExample = () => {
|
||||
this.setState({ loading: true })
|
||||
const filename = 'perf-vertx-stacks-01-collapsed-all.txt'
|
||||
fetch(exampleProfileURL).then(resp => resp.text()).then(data => {
|
||||
this.loadFromString(filename, data)
|
||||
})
|
||||
fetch(exampleProfileURL)
|
||||
.then(resp => resp.text())
|
||||
.then(data => {
|
||||
this.loadFromString(filename, data)
|
||||
})
|
||||
}
|
||||
|
||||
onDrop = (ev: DragEvent) => {
|
||||
@ -310,11 +334,11 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
onWindowKeyPress = (ev: KeyboardEvent) => {
|
||||
if (ev.key === '1') {
|
||||
this.setState({
|
||||
sortOrder: SortOrder.CHRONO
|
||||
sortOrder: SortOrder.CHRONO,
|
||||
})
|
||||
} else if (ev.key === '2') {
|
||||
this.setState({
|
||||
sortOrder: SortOrder.LEFT_HEAVY
|
||||
sortOrder: SortOrder.LEFT_HEAVY,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -328,10 +352,10 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
}
|
||||
|
||||
flamechartView: FlamechartView | null = null
|
||||
flamechartRef = (view: FlamechartView | null) => this.flamechartView = view
|
||||
flamechartRef = (view: FlamechartView | null) => (this.flamechartView = view)
|
||||
subcomponents() {
|
||||
return {
|
||||
flamechart: this.flamechartView
|
||||
flamechart: this.flamechartView,
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,34 +367,71 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
}
|
||||
|
||||
renderLanding() {
|
||||
return <div className={css(style.landingContainer)}>
|
||||
<div className={css(style.landingMessage)}>
|
||||
<p className={css(style.landingP)}>👋 Hi there! Welcome to 🔬speedscope, an interactive{' '}
|
||||
<a className={css(style.link)} href="http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html">flamegraph</a> visualizer.
|
||||
Use it to help you make your software faster.</p>
|
||||
<p className={css(style.landingP)}>Drag and drop a profile file onto this window to get started,
|
||||
click the big blue button below to browse for a profile to explore, or{' '}
|
||||
<a className={css(style.link)} onClick={this.loadExample}>click here</a>{' '}
|
||||
to load an example profile.</p>
|
||||
return (
|
||||
<div className={css(style.landingContainer)}>
|
||||
<div className={css(style.landingMessage)}>
|
||||
<p className={css(style.landingP)}>
|
||||
👋 Hi there! Welcome to 🔬speedscope, an interactive{' '}
|
||||
<a
|
||||
className={css(style.link)}
|
||||
href="http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html"
|
||||
>
|
||||
flamegraph
|
||||
</a>{' '}
|
||||
visualizer. Use it to help you make your software faster.
|
||||
</p>
|
||||
<p className={css(style.landingP)}>
|
||||
Drag and drop a profile file onto this window to get started, click the big blue button
|
||||
below to browse for a profile to explore, or{' '}
|
||||
<a className={css(style.link)} onClick={this.loadExample}>
|
||||
click here
|
||||
</a>{' '}
|
||||
to load an example profile.
|
||||
</p>
|
||||
|
||||
<div className={css(style.browseButtonContainer)}>
|
||||
<input type="file" name="file" id="file" onChange={this.onFileSelect} className={css(style.hide)} />
|
||||
<label for="file" className={css(style.browseButton)}>Browse</label>
|
||||
<div className={css(style.browseButtonContainer)}>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
id="file"
|
||||
onChange={this.onFileSelect}
|
||||
className={css(style.hide)}
|
||||
/>
|
||||
<label for="file" className={css(style.browseButton)}>
|
||||
Browse
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p className={css(style.landingP)}>
|
||||
See the{' '}
|
||||
<a
|
||||
className={css(style.link)}
|
||||
href="https://github.com/jlfwong/speedscope#usage"
|
||||
target="_blank"
|
||||
>
|
||||
documentation
|
||||
</a>{' '}
|
||||
for information about supported file formats, keyboard shortcuts, and how to navigate
|
||||
around the profile.
|
||||
</p>
|
||||
|
||||
<p className={css(style.landingP)}>
|
||||
speedscope is open source. Please{' '}
|
||||
<a
|
||||
className={css(style.link)}
|
||||
target="_blank"
|
||||
href="https://github.com/jlfwong/speedscope/issues"
|
||||
>
|
||||
report any issues on GitHub
|
||||
</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className={css(style.landingP)}>See the <a className={css(style.link)}
|
||||
href="https://github.com/jlfwong/speedscope#usage" target="_blank">documentation</a> for
|
||||
information about supported file formats, keyboard shortcuts, and how
|
||||
to navigate around the profile.</p>
|
||||
|
||||
<p className={css(style.landingP)}>speedscope is open source.
|
||||
Please <a className={css(style.link)} target="_blank" href="https://github.com/jlfwong/speedscope/issues">report any issues on GitHub</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderLoadingBar() {
|
||||
return <div className={css(style.loading)}></div>
|
||||
return <div className={css(style.loading)} />
|
||||
}
|
||||
|
||||
setSortOrder = (sortOrder: SortOrder) => {
|
||||
@ -383,23 +444,36 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {flamechart, flamechartRenderer, sortedFlamechart, sortedFlamechartRenderer, sortOrder, loading} = this.state
|
||||
const {
|
||||
flamechart,
|
||||
flamechartRenderer,
|
||||
sortedFlamechart,
|
||||
sortedFlamechartRenderer,
|
||||
sortOrder,
|
||||
loading,
|
||||
} = this.state
|
||||
const flamechartToView = sortOrder == SortOrder.CHRONO ? flamechart : sortedFlamechart
|
||||
const flamechartRendererToUse = sortOrder == SortOrder.CHRONO ? flamechartRenderer : sortedFlamechartRenderer
|
||||
const flamechartRendererToUse =
|
||||
sortOrder == SortOrder.CHRONO ? flamechartRenderer : sortedFlamechartRenderer
|
||||
|
||||
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
|
||||
<GLCanvas setCanvasContext={this.setCanvasContext} />
|
||||
<Toolbar setSortOrder={this.setSortOrder} {...this.state} />
|
||||
{loading ?
|
||||
this.renderLoadingBar() :
|
||||
this.canvasContext && flamechartToView && flamechartRendererToUse ?
|
||||
return (
|
||||
<div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
|
||||
<GLCanvas setCanvasContext={this.setCanvasContext} />
|
||||
<Toolbar setSortOrder={this.setSortOrder} {...this.state} />
|
||||
{loading ? (
|
||||
this.renderLoadingBar()
|
||||
) : this.canvasContext && flamechartToView && flamechartRendererToUse ? (
|
||||
<FlamechartView
|
||||
canvasContext={this.canvasContext}
|
||||
flamechartRenderer={flamechartRendererToUse}
|
||||
ref={this.flamechartRef}
|
||||
flamechart={flamechartToView} /> :
|
||||
this.renderLanding()}
|
||||
</div>
|
||||
flamechart={flamechartToView}
|
||||
/>
|
||||
) : (
|
||||
this.renderLanding()
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,23 +483,25 @@ const style = StyleSheet.create({
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: -1,
|
||||
pointerEvents: 'none'
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
loading: {
|
||||
height: 3,
|
||||
marginBottom: -3,
|
||||
background: Colors.DARK_BLUE,
|
||||
transformOrigin: '0% 50%',
|
||||
animationName: [{
|
||||
from: {
|
||||
transform: `scaleX(0)`
|
||||
animationName: [
|
||||
{
|
||||
from: {
|
||||
transform: `scaleX(0)`,
|
||||
},
|
||||
to: {
|
||||
transform: `scaleX(1)`,
|
||||
},
|
||||
},
|
||||
to: {
|
||||
transform: `scaleX(1)`
|
||||
}
|
||||
}],
|
||||
animationTimingFunction: "cubic-bezier(0, 1, 0, 1)",
|
||||
animationDuration: "30s"
|
||||
],
|
||||
animationTimingFunction: 'cubic-bezier(0, 1, 0, 1)',
|
||||
animationDuration: '30s',
|
||||
},
|
||||
root: {
|
||||
width: '100vw',
|
||||
@ -435,27 +511,27 @@ const style = StyleSheet.create({
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
fontFamily: FontFamily.MONOSPACE,
|
||||
lineHeight: '20px'
|
||||
lineHeight: '20px',
|
||||
},
|
||||
landingContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flex: 1
|
||||
flex: 1,
|
||||
},
|
||||
landingMessage: {
|
||||
maxWidth: 600
|
||||
maxWidth: 600,
|
||||
},
|
||||
landingP: {
|
||||
marginBottom: 16
|
||||
marginBottom: 16,
|
||||
},
|
||||
hide: {
|
||||
display: 'none'
|
||||
display: 'none',
|
||||
},
|
||||
browseButtonContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
},
|
||||
browseButton: {
|
||||
marginBottom: 16,
|
||||
@ -467,12 +543,12 @@ const style = StyleSheet.create({
|
||||
lineHeight: '72px',
|
||||
background: Colors.DARK_BLUE,
|
||||
color: 'white',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
},
|
||||
link: {
|
||||
color: Colors.LIGHT_BLUE,
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none'
|
||||
textDecoration: 'none',
|
||||
},
|
||||
toolbar: {
|
||||
height: 18,
|
||||
@ -482,7 +558,7 @@ const style = StyleSheet.create({
|
||||
fontFamily: FontFamily.MONOSPACE,
|
||||
fontSize: FontSize.TITLE,
|
||||
lineHeight: '18px',
|
||||
userSelect: 'none'
|
||||
userSelect: 'none',
|
||||
},
|
||||
toolbarLeft: {
|
||||
position: 'absolute',
|
||||
@ -513,24 +589,23 @@ const style = StyleSheet.create({
|
||||
marginLeft: 2,
|
||||
':hover': {
|
||||
background: Colors.GRAY,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
toolbarTabActive: {
|
||||
background: Colors.LIGHT_BLUE,
|
||||
':hover': {
|
||||
background: Colors.LIGHT_BLUE
|
||||
}
|
||||
background: Colors.LIGHT_BLUE,
|
||||
},
|
||||
},
|
||||
noLinkStyle: {
|
||||
textDecoration: 'none',
|
||||
color: 'inherit'
|
||||
color: 'inherit',
|
||||
},
|
||||
emoji: {
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'middle',
|
||||
paddingTop: '0px',
|
||||
marginRight: '0.3em'
|
||||
}
|
||||
marginRight: '0.3em',
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -1,11 +1,25 @@
|
||||
import regl from 'regl'
|
||||
import { RectangleBatchRenderer, RectangleBatch, RectangleBatchRendererProps } from './rectangle-batch-renderer';
|
||||
import { ViewportRectangleRenderer, ViewportRectangleRendererProps } from './overlay-rectangle-renderer';
|
||||
import { TextureCachedRenderer, TextureRenderer, TextureRendererProps } from './texture-catched-renderer'
|
||||
import {
|
||||
RectangleBatchRenderer,
|
||||
RectangleBatch,
|
||||
RectangleBatchRendererProps,
|
||||
} from './rectangle-batch-renderer'
|
||||
import {
|
||||
ViewportRectangleRenderer,
|
||||
ViewportRectangleRendererProps,
|
||||
} from './overlay-rectangle-renderer'
|
||||
import {
|
||||
TextureCachedRenderer,
|
||||
TextureRenderer,
|
||||
TextureRendererProps,
|
||||
} from './texture-catched-renderer'
|
||||
import { StatsPanel } from './stats'
|
||||
|
||||
import { Vec2, Rect } from './math';
|
||||
import { FlamechartColorPassRenderer, FlamechartColorPassRenderProps } from './flamechart-color-pass-renderer';
|
||||
import { Vec2, Rect } from './math'
|
||||
import {
|
||||
FlamechartColorPassRenderer,
|
||||
FlamechartColorPassRenderProps,
|
||||
} from './flamechart-color-pass-renderer'
|
||||
|
||||
type FrameCallback = () => void
|
||||
|
||||
@ -26,11 +40,11 @@ export class CanvasContext {
|
||||
this.gl = regl({
|
||||
canvas: canvas,
|
||||
attributes: {
|
||||
antialias: false
|
||||
antialias: false,
|
||||
},
|
||||
extensions: ['ANGLE_instanced_arrays', 'WEBGL_depth_texture'],
|
||||
optionalExtensions: ['EXT_disjoint_timer_query'],
|
||||
profile: true
|
||||
profile: true,
|
||||
})
|
||||
;(window as any)['CanvasContext'] = this
|
||||
this.rectangleBatchRenderer = new RectangleBatchRenderer(this.gl)
|
||||
@ -45,15 +59,18 @@ export class CanvasContext {
|
||||
},
|
||||
viewportY: (context: regl.Context, props: SetViewportScopeProps) => {
|
||||
return props.physicalBounds.top()
|
||||
}
|
||||
},
|
||||
},
|
||||
viewport: (context, props) => {
|
||||
const { physicalBounds } = props
|
||||
return {
|
||||
x: physicalBounds.left(),
|
||||
y: window.devicePixelRatio * window.innerHeight - physicalBounds.top() - physicalBounds.height(),
|
||||
y:
|
||||
window.devicePixelRatio * window.innerHeight -
|
||||
physicalBounds.top() -
|
||||
physicalBounds.height(),
|
||||
width: physicalBounds.width(),
|
||||
height: physicalBounds.height()
|
||||
height: physicalBounds.height(),
|
||||
}
|
||||
},
|
||||
scissor: (context, props) => {
|
||||
@ -62,12 +79,15 @@ export class CanvasContext {
|
||||
enable: true,
|
||||
box: {
|
||||
x: physicalBounds.left(),
|
||||
y: window.devicePixelRatio * window.innerHeight - physicalBounds.top() - physicalBounds.height(),
|
||||
y:
|
||||
window.devicePixelRatio * window.innerHeight -
|
||||
physicalBounds.top() -
|
||||
physicalBounds.height(),
|
||||
width: physicalBounds.width(),
|
||||
height: physicalBounds.height()
|
||||
}
|
||||
height: physicalBounds.height(),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -139,11 +159,11 @@ export class CanvasContext {
|
||||
}): TextureCachedRenderer<T> {
|
||||
return new TextureCachedRenderer(this.gl, {
|
||||
...options,
|
||||
textureRenderer: this.textureRenderer
|
||||
textureRenderer: this.textureRenderer,
|
||||
})
|
||||
}
|
||||
|
||||
drawViewportRectangle(props: ViewportRectangleRendererProps){
|
||||
drawViewportRectangle(props: ViewportRectangleRendererProps) {
|
||||
this.viewportRectangleRenderer.render(props)
|
||||
}
|
||||
|
||||
@ -151,7 +171,7 @@ export class CanvasContext {
|
||||
const bounds = el.getBoundingClientRect()
|
||||
const physicalBounds = new Rect(
|
||||
new Vec2(bounds.left * window.devicePixelRatio, bounds.top * window.devicePixelRatio),
|
||||
new Vec2(bounds.width * window.devicePixelRatio, bounds.height * window.devicePixelRatio)
|
||||
new Vec2(bounds.width * window.devicePixelRatio, bounds.height * window.devicePixelRatio),
|
||||
)
|
||||
this.setViewportScope({ physicalBounds }, cb)
|
||||
}
|
||||
@ -163,4 +183,4 @@ export class CanvasContext {
|
||||
getMaxTextureSize() {
|
||||
return this.gl.limits.maxTextureSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
color.ts
39
color.ts
@ -1,23 +1,32 @@
|
||||
import {Frame} from './profile'
|
||||
import { Frame } from './profile'
|
||||
|
||||
export class Color {
|
||||
constructor(readonly r: number = 0, readonly g: number = 0, readonly b: number = 0, readonly a: number = 1) {}
|
||||
constructor(
|
||||
readonly r: number = 0,
|
||||
readonly g: number = 0,
|
||||
readonly b: number = 0,
|
||||
readonly a: number = 1,
|
||||
) {}
|
||||
|
||||
static fromLumaChromaHue(L: number, C: number, H: number) {
|
||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_luma/chroma/hue
|
||||
|
||||
const hPrime = H / 60
|
||||
const X = C * (1 - Math.abs(hPrime % 2 - 1))
|
||||
const [R1, G1, B1] = (
|
||||
hPrime < 1 ? [C, X, 0] :
|
||||
hPrime < 2 ? [X, C, 0] :
|
||||
hPrime < 3 ? [0, C, X] :
|
||||
hPrime < 4 ? [0, X, C] :
|
||||
hPrime < 5 ? [X, 0, C] :
|
||||
[C, 0, X]
|
||||
)
|
||||
const [R1, G1, B1] =
|
||||
hPrime < 1
|
||||
? [C, X, 0]
|
||||
: hPrime < 2
|
||||
? [X, C, 0]
|
||||
: hPrime < 3
|
||||
? [0, C, X]
|
||||
: hPrime < 4
|
||||
? [0, X, C]
|
||||
: hPrime < 5
|
||||
? [X, 0, C]
|
||||
: [C, 0, X]
|
||||
|
||||
const m = L - (0.30 * R1 + 0.59 * G1 + 0.11 * B1)
|
||||
const m = L - (0.3 * R1 + 0.59 * G1 + 0.11 * B1)
|
||||
|
||||
return new Color(R1 + m, G1 + m, B1 + m, 1.0)
|
||||
}
|
||||
@ -64,11 +73,13 @@ export class FrameColorGenerator {
|
||||
const x = 2 * fract(100.0 * ratio) - 1
|
||||
|
||||
const L = 0.85 - 0.1 * x
|
||||
const C = 0.20 + 0.1 * x
|
||||
const C = 0.2 + 0.1 * x
|
||||
const H = 360 * ratio
|
||||
this.frameToColor.set(frames[i], Color.fromLumaChromaHue(L, C, H))
|
||||
}
|
||||
}
|
||||
|
||||
getColorForFrame(f: Frame) { return this.frameToColor.get(f) || new Color() }
|
||||
}
|
||||
getColorForFrame(f: Frame) {
|
||||
return this.frameToColor.get(f) || new Color()
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -102,7 +102,7 @@ export class FlamechartColorPassRenderer {
|
||||
`,
|
||||
|
||||
depth: {
|
||||
enable: false
|
||||
enable: false,
|
||||
},
|
||||
|
||||
attributes: {
|
||||
@ -114,18 +114,8 @@ export class FlamechartColorPassRenderer {
|
||||
// | /|
|
||||
// |/ |
|
||||
// 2 +--+ 3
|
||||
position: gl.buffer([
|
||||
[-1, 1],
|
||||
[1, 1],
|
||||
[-1, -1],
|
||||
[1, -1]
|
||||
]),
|
||||
uv: gl.buffer([
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
[0, 0],
|
||||
[1, 0]
|
||||
])
|
||||
position: gl.buffer([[-1, 1], [1, 1], [-1, -1], [1, -1]]),
|
||||
uv: gl.buffer([[0, 1], [1, 1], [0, 0], [1, 0]]),
|
||||
},
|
||||
|
||||
count: 4,
|
||||
@ -138,34 +128,33 @@ export class FlamechartColorPassRenderer {
|
||||
const { srcRect, rectInfoTexture } = props
|
||||
const physicalToUV = AffineTransform.withTranslation(new Vec2(0, 1))
|
||||
.times(AffineTransform.withScale(new Vec2(1, -1)))
|
||||
.times(AffineTransform.betweenRects(
|
||||
.times(
|
||||
AffineTransform.betweenRects(
|
||||
new Rect(Vec2.zero, new Vec2(rectInfoTexture.width, rectInfoTexture.height)),
|
||||
Rect.unit
|
||||
))
|
||||
Rect.unit,
|
||||
),
|
||||
)
|
||||
const uvRect = physicalToUV.transformRect(srcRect)
|
||||
return AffineTransform.betweenRects(
|
||||
Rect.unit,
|
||||
uvRect,
|
||||
).flatten()
|
||||
return AffineTransform.betweenRects(Rect.unit, uvRect).flatten()
|
||||
},
|
||||
renderOutlines: (context, props) => {
|
||||
return props.renderOutlines ? 1.0 : 0.0
|
||||
},
|
||||
uvSpacePixelSize: (context, props) => {
|
||||
return Vec2.unit.dividedByPointwise(new Vec2(props.rectInfoTexture.width, props.rectInfoTexture.height)).flatten()
|
||||
return Vec2.unit
|
||||
.dividedByPointwise(new Vec2(props.rectInfoTexture.width, props.rectInfoTexture.height))
|
||||
.flatten()
|
||||
},
|
||||
positionTransform: (context, props) => {
|
||||
const { dstRect } = props
|
||||
const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight)
|
||||
|
||||
const physicalToNDC = AffineTransform.withScale(new Vec2(1, -1))
|
||||
.times(AffineTransform.betweenRects(
|
||||
new Rect(Vec2.zero, viewportSize),
|
||||
Rect.NDC)
|
||||
)
|
||||
const physicalToNDC = AffineTransform.withScale(new Vec2(1, -1)).times(
|
||||
AffineTransform.betweenRects(new Rect(Vec2.zero, viewportSize), Rect.NDC),
|
||||
)
|
||||
const ndcRect = physicalToNDC.transformRect(dstRect)
|
||||
return AffineTransform.betweenRects(Rect.NDC, ndcRect).flatten()
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import { h, Component } from 'preact'
|
||||
import { css } from 'aphrodite'
|
||||
import { Flamechart } from './flamechart'
|
||||
import { Rect, Vec2, AffineTransform, clamp } from './math'
|
||||
import { FlamechartRenderer } from "./flamechart-renderer"
|
||||
import { cachedMeasureTextWidth } from "./utils";
|
||||
import { style, Sizes } from "./flamechart-style";
|
||||
import { FontFamily, FontSize, Colors } from "./style"
|
||||
import { FlamechartRenderer } from './flamechart-renderer'
|
||||
import { cachedMeasureTextWidth } from './utils'
|
||||
import { style, Sizes } from './flamechart-style'
|
||||
import { FontFamily, FontSize, Colors } from './style'
|
||||
import { CanvasContext } from './canvas-context'
|
||||
import { TextureCachedRenderer } from './texture-catched-renderer'
|
||||
|
||||
@ -24,7 +24,7 @@ interface FlamechartMinimapViewProps {
|
||||
|
||||
enum DraggingMode {
|
||||
DRAW_NEW_VIEWPORT,
|
||||
TRANSLATE_VIEWPORT
|
||||
TRANSLATE_VIEWPORT,
|
||||
}
|
||||
|
||||
export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps, {}> {
|
||||
@ -39,7 +39,7 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
private physicalViewSize() {
|
||||
return new Vec2(
|
||||
this.overlayCanvas ? this.overlayCanvas.width : 0,
|
||||
this.overlayCanvas ? this.overlayCanvas.height : 0
|
||||
this.overlayCanvas ? this.overlayCanvas.height : 0,
|
||||
)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
private configSpaceSize() {
|
||||
return new Vec2(
|
||||
this.props.flamechart.getTotalWeight(),
|
||||
this.props.flamechart.getLayers().length
|
||||
this.props.flamechart.getLayers().length,
|
||||
)
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
|
||||
return AffineTransform.betweenRects(
|
||||
new Rect(new Vec2(0, 0), this.configSpaceSize()),
|
||||
new Rect(minimapOrigin, this.physicalViewSize().minus(minimapOrigin))
|
||||
new Rect(minimapOrigin, this.physicalViewSize().minus(minimapOrigin)),
|
||||
)
|
||||
}
|
||||
|
||||
@ -90,20 +90,20 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
}
|
||||
return false
|
||||
},
|
||||
render: (props) => {
|
||||
render: props => {
|
||||
this.props.flamechartRenderer.render({
|
||||
physicalSpaceDstRect: new Rect(
|
||||
this.minimapOrigin(),
|
||||
this.physicalViewSize().minus(this.minimapOrigin())
|
||||
this.physicalViewSize().minus(this.minimapOrigin()),
|
||||
),
|
||||
configSpaceSrcRect: new Rect(new Vec2(0, 0), this.configSpaceSize()),
|
||||
renderOutlines: false
|
||||
renderOutlines: false,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
this.props.canvasContext.renderInto(this.container, (context) => {
|
||||
this.props.canvasContext.renderInto(this.container, context => {
|
||||
// TODO(jlfwong): Switch back to the texture cached renderer once I figure out
|
||||
// how to resize a framebuffer while another framebuffer is active. It seems
|
||||
// to crash regl. I should submit a reduced repro case and hopefully get it fixed?
|
||||
@ -116,9 +116,9 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
configSpaceSrcRect: new Rect(new Vec2(0, 0), this.configSpaceSize()),
|
||||
physicalSpaceDstRect: new Rect(
|
||||
this.minimapOrigin(),
|
||||
this.physicalViewSize().minus(this.minimapOrigin())
|
||||
this.physicalViewSize().minus(this.minimapOrigin()),
|
||||
),
|
||||
renderOutlines: false
|
||||
renderOutlines: false,
|
||||
})
|
||||
this.props.canvasContext.drawViewportRectangle({
|
||||
configSpaceViewportRect: this.props.configSpaceViewportRect,
|
||||
@ -147,14 +147,18 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
// 1eN, 2eN, or 5eN for some N
|
||||
|
||||
// Ideally, we want an interval every 100 logical screen pixels
|
||||
const logicalToConfig = (this.configSpaceToPhysicalViewSpace().inverted() || new AffineTransform()).times(this.logicalToPhysicalViewSpace())
|
||||
const logicalToConfig = (
|
||||
this.configSpaceToPhysicalViewSpace().inverted() || new AffineTransform()
|
||||
).times(this.logicalToPhysicalViewSpace())
|
||||
const targetInterval = logicalToConfig.transformVector(new Vec2(200, 1)).x
|
||||
|
||||
const physicalViewSpaceFrameHeight = Sizes.FRAME_HEIGHT * DEVICE_PIXEL_RATIO
|
||||
const physicalViewSpaceFontSize = FontSize.LABEL * DEVICE_PIXEL_RATIO
|
||||
const LABEL_PADDING_PX = (physicalViewSpaceFrameHeight - physicalViewSpaceFontSize) / 2
|
||||
|
||||
ctx.font = `${physicalViewSpaceFontSize}px/${physicalViewSpaceFrameHeight}px ${FontFamily.MONOSPACE}`
|
||||
ctx.font = `${physicalViewSpaceFontSize}px/${physicalViewSpaceFrameHeight}px ${
|
||||
FontFamily.MONOSPACE
|
||||
}`
|
||||
ctx.textBaseline = 'top'
|
||||
|
||||
const minInterval = Math.pow(10, Math.floor(Math.log10(targetInterval)))
|
||||
@ -204,12 +208,14 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
|
||||
private resizeOverlayCanvasIfNeeded() {
|
||||
if (!this.overlayCanvas) return
|
||||
let {width, height} = this.overlayCanvas.getBoundingClientRect()
|
||||
{/*
|
||||
let { width, height } = this.overlayCanvas.getBoundingClientRect()
|
||||
{
|
||||
/*
|
||||
We render text at a higher resolution then scale down to
|
||||
ensure we're rendering at 1:1 device pixel ratio.
|
||||
This ensures our text is rendered crisply.
|
||||
*/}
|
||||
*/
|
||||
}
|
||||
width = Math.floor(width)
|
||||
height = Math.floor(height)
|
||||
|
||||
@ -219,8 +225,8 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
const scaledWidth = width * DEVICE_PIXEL_RATIO
|
||||
const scaledHeight = height * DEVICE_PIXEL_RATIO
|
||||
|
||||
if (scaledWidth === this.overlayCanvas.width &&
|
||||
scaledHeight === this.overlayCanvas.height) return
|
||||
if (scaledWidth === this.overlayCanvas.width && scaledHeight === this.overlayCanvas.height)
|
||||
return
|
||||
|
||||
this.overlayCanvas.width = scaledWidth
|
||||
this.overlayCanvas.height = scaledHeight
|
||||
@ -252,7 +258,7 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
private maybeClearInteractionLock = () => {
|
||||
if (this.interactionLock) {
|
||||
if (!this.frameHadWheelEvent) {
|
||||
this.framesWithoutWheelEvents++;
|
||||
this.framesWithoutWheelEvents++
|
||||
if (this.framesWithoutWheelEvents >= 2) {
|
||||
this.interactionLock = null
|
||||
this.framesWithoutWheelEvents = 0
|
||||
@ -275,11 +281,10 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
private zoom(multiplier: number) {
|
||||
this.interactionLock = 'zoom'
|
||||
const configSpaceViewport = this.props.configSpaceViewportRect
|
||||
const configSpaceCenter = configSpaceViewport.origin.plus(configSpaceViewport.size.times(1/2))
|
||||
const configSpaceCenter = configSpaceViewport.origin.plus(configSpaceViewport.size.times(1 / 2))
|
||||
if (!configSpaceCenter) return
|
||||
|
||||
const zoomTransform = AffineTransform
|
||||
.withTranslation(configSpaceCenter.times(-1))
|
||||
const zoomTransform = AffineTransform.withTranslation(configSpaceCenter.times(-1))
|
||||
.scaledBy(new Vec2(multiplier, 1))
|
||||
.translatedBy(configSpaceCenter)
|
||||
|
||||
@ -294,13 +299,13 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
const isZoom = ev.metaKey || ev.ctrlKey
|
||||
|
||||
if (isZoom && this.interactionLock !== 'pan') {
|
||||
let multiplier = 1 + (ev.deltaY / 100)
|
||||
let multiplier = 1 + ev.deltaY / 100
|
||||
|
||||
// On Chrome & Firefox, pinch-to-zoom maps to
|
||||
// WheelEvent + Ctrl Key. We'll accelerate it in
|
||||
// this case, since it feels a bit sluggish otherwise.
|
||||
if (ev.ctrlKey) {
|
||||
multiplier = 1 + (ev.deltaY / 40)
|
||||
multiplier = 1 + ev.deltaY / 40
|
||||
}
|
||||
|
||||
multiplier = clamp(multiplier, 0.1, 10.0)
|
||||
@ -310,13 +315,16 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
this.pan(new Vec2(ev.deltaX, ev.deltaY))
|
||||
}
|
||||
|
||||
|
||||
this.renderCanvas()
|
||||
}
|
||||
|
||||
private configSpaceMouse(ev: MouseEvent): Vec2 | null {
|
||||
const logicalSpaceMouse = this.windowToLogicalViewSpace().transformPosition(new Vec2(ev.clientX, ev.clientY))
|
||||
const physicalSpaceMouse = this.logicalToPhysicalViewSpace().transformPosition(logicalSpaceMouse)
|
||||
const logicalSpaceMouse = this.windowToLogicalViewSpace().transformPosition(
|
||||
new Vec2(ev.clientX, ev.clientY),
|
||||
)
|
||||
const physicalSpaceMouse = this.logicalToPhysicalViewSpace().transformPosition(
|
||||
logicalSpaceMouse,
|
||||
)
|
||||
return this.configSpaceToPhysicalViewSpace().inverseTransformPosition(physicalSpaceMouse)
|
||||
}
|
||||
|
||||
@ -331,7 +339,9 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
// If dragging starting inside the viewport rectangle,
|
||||
// we'll move the existing viewport
|
||||
this.draggingMode = DraggingMode.TRANSLATE_VIEWPORT
|
||||
this.dragConfigSpaceViewportOffset = configSpaceMouse.minus(this.props.configSpaceViewportRect.origin)
|
||||
this.dragConfigSpaceViewportOffset = configSpaceMouse.minus(
|
||||
this.props.configSpaceViewportRect.origin,
|
||||
)
|
||||
} else {
|
||||
// If dragging starts outside the the viewport rectangle,
|
||||
// we'll start drawing a new viewport
|
||||
@ -353,7 +363,9 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
this.updateCursor(configSpaceMouse)
|
||||
|
||||
// Clamp the mouse position to avoid weird behavior when outside the canvas bounds
|
||||
configSpaceMouse = new Rect(new Vec2(0, 0), this.configSpaceSize()).closestPointTo(configSpaceMouse)
|
||||
configSpaceMouse = new Rect(new Vec2(0, 0), this.configSpaceSize()).closestPointTo(
|
||||
configSpaceMouse,
|
||||
)
|
||||
|
||||
if (this.draggingMode === DraggingMode.DRAW_NEW_VIEWPORT) {
|
||||
const configStart = this.dragStartConfigSpaceMouse
|
||||
@ -366,16 +378,15 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
const width = right - left
|
||||
const height = this.props.configSpaceViewportRect.height()
|
||||
|
||||
this.props.setConfigSpaceViewportRect(new Rect(
|
||||
new Vec2(left, configEnd.y - height / 2),
|
||||
new Vec2(width, height)
|
||||
))
|
||||
this.props.setConfigSpaceViewportRect(
|
||||
new Rect(new Vec2(left, configEnd.y - height / 2), new Vec2(width, height)),
|
||||
)
|
||||
} else if (this.draggingMode === DraggingMode.TRANSLATE_VIEWPORT) {
|
||||
if (!this.dragConfigSpaceViewportOffset) return
|
||||
|
||||
const newOrigin = configSpaceMouse.minus(this.dragConfigSpaceViewportOffset)
|
||||
this.props.setConfigSpaceViewportRect(
|
||||
this.props.configSpaceViewportRect.withOrigin(newOrigin)
|
||||
this.props.configSpaceViewportRect.withOrigin(newOrigin),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -428,11 +439,9 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
onWheel={this.onWheel}
|
||||
onMouseDown={this.onMouseDown}
|
||||
onMouseMove={this.onMouseMove}
|
||||
className={css(style.minimap, style.vbox)} >
|
||||
<canvas
|
||||
width={1} height={1}
|
||||
ref={this.overlayCanvasRef}
|
||||
className={css(style.fill)} />
|
||||
className={css(style.minimap, style.vbox)}
|
||||
>
|
||||
<canvas width={1} height={1} ref={this.overlayCanvasRef} className={css(style.fill)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import regl from 'regl'
|
||||
import { Flamechart } from './flamechart'
|
||||
import { RectangleBatch } from './rectangle-batch-renderer'
|
||||
import { CanvasContext } from './canvas-context';
|
||||
import { CanvasContext } from './canvas-context'
|
||||
import { Vec2, Rect, AffineTransform } from './math'
|
||||
import { LRUCache } from './lru-cache'
|
||||
import { Color } from './color'
|
||||
import { getOrInsert } from './utils';
|
||||
import { getOrInsert } from './utils'
|
||||
|
||||
const MAX_BATCH_SIZE = 10000
|
||||
|
||||
@ -26,15 +26,21 @@ class RowAtlas<K> {
|
||||
this.framebuffer = canvasContext.gl.framebuffer({ color: [this.texture] })
|
||||
this.rowCache = new LRUCache(this.texture.height)
|
||||
this.renderToFramebuffer = canvasContext.gl({
|
||||
framebuffer: this.framebuffer
|
||||
framebuffer: this.framebuffer,
|
||||
})
|
||||
this.clearLineBatch = canvasContext.createRectangleBatch()
|
||||
this.clearLineBatch.addRect(Rect.unit, new Color(0, 0, 0, 0))
|
||||
}
|
||||
|
||||
has(key: K) { return this.rowCache.has(key) }
|
||||
getResolution() { return this.texture.width }
|
||||
getCapacity() { return this.texture.height }
|
||||
has(key: K) {
|
||||
return this.rowCache.has(key)
|
||||
}
|
||||
getResolution() {
|
||||
return this.texture.width
|
||||
}
|
||||
getCapacity() {
|
||||
return this.texture.height
|
||||
}
|
||||
|
||||
private allocateLine(key: K): number {
|
||||
if (this.rowCache.getSize() < this.rowCache.getCapacity()) {
|
||||
@ -50,10 +56,7 @@ class RowAtlas<K> {
|
||||
}
|
||||
}
|
||||
|
||||
writeToAtlasIfNeeded(
|
||||
keys: K[],
|
||||
render: (textureDstRect: Rect, key: K) => void
|
||||
) {
|
||||
writeToAtlasIfNeeded(keys: K[], render: (textureDstRect: Rect, key: K) => void) {
|
||||
this.renderToFramebuffer((context: regl.Context) => {
|
||||
for (let key of keys) {
|
||||
let row = this.rowCache.get(key)
|
||||
@ -64,14 +67,11 @@ class RowAtlas<K> {
|
||||
// Not cached -- we'll have to actually render
|
||||
row = this.allocateLine(key)
|
||||
|
||||
const textureRect = new Rect(
|
||||
new Vec2(0, row),
|
||||
new Vec2(this.texture.width, 1)
|
||||
)
|
||||
const textureRect = new Rect(new Vec2(0, row), new Vec2(this.texture.width, 1))
|
||||
this.canvasContext.drawRectangleBatch({
|
||||
batch: this.clearLineBatch,
|
||||
configSpaceSrcRect: Rect.unit,
|
||||
physicalSpaceDstRect: textureRect
|
||||
physicalSpaceDstRect: textureRect,
|
||||
})
|
||||
render(textureRect, key)
|
||||
}
|
||||
@ -84,17 +84,14 @@ class RowAtlas<K> {
|
||||
return false
|
||||
}
|
||||
|
||||
const textureRect = new Rect(
|
||||
new Vec2(0, row),
|
||||
new Vec2(this.texture.width, 1)
|
||||
)
|
||||
const textureRect = new Rect(new Vec2(0, row), new Vec2(this.texture.width, 1))
|
||||
|
||||
// At this point, we have the row in cache, and we can
|
||||
// paint directly from it into the framebuffer.
|
||||
this.canvasContext.drawTexture({
|
||||
texture: this.texture,
|
||||
srcRect: textureRect,
|
||||
dstRect: dstRect
|
||||
dstRect: dstRect,
|
||||
})
|
||||
return true
|
||||
}
|
||||
@ -113,16 +110,26 @@ class RangeTreeLeafNode implements RangeTreeNode {
|
||||
constructor(
|
||||
private batch: RectangleBatch,
|
||||
private bounds: Rect,
|
||||
private numPrecedingRectanglesInRow: number
|
||||
private numPrecedingRectanglesInRow: number,
|
||||
) {
|
||||
batch.uploadToGPU()
|
||||
}
|
||||
|
||||
getBatch() { return this.batch }
|
||||
getBounds() { return this.bounds }
|
||||
getRectCount() { return this.batch.getRectCount() }
|
||||
getChildren() { return this.children }
|
||||
getParity() { return this.numPrecedingRectanglesInRow % 2 }
|
||||
getBatch() {
|
||||
return this.batch
|
||||
}
|
||||
getBounds() {
|
||||
return this.bounds
|
||||
}
|
||||
getRectCount() {
|
||||
return this.batch.getRectCount()
|
||||
}
|
||||
getChildren() {
|
||||
return this.children
|
||||
}
|
||||
getParity() {
|
||||
return this.numPrecedingRectanglesInRow % 2
|
||||
}
|
||||
forEachLeafNodeWithinBounds(configSpaceBounds: Rect, cb: (leaf: RangeTreeLeafNode) => void) {
|
||||
if (!this.bounds.hasIntersectionWith(configSpaceBounds)) return
|
||||
cb(this)
|
||||
@ -134,7 +141,7 @@ class RangeTreeInteriorNode implements RangeTreeNode {
|
||||
private bounds: Rect
|
||||
constructor(private children: RangeTreeNode[]) {
|
||||
if (children.length === 0) {
|
||||
throw new Error("Empty interior node")
|
||||
throw new Error('Empty interior node')
|
||||
}
|
||||
let minLeft = Infinity
|
||||
let maxRight = -Infinity
|
||||
@ -150,13 +157,19 @@ class RangeTreeInteriorNode implements RangeTreeNode {
|
||||
}
|
||||
this.bounds = new Rect(
|
||||
new Vec2(minLeft, minTop),
|
||||
new Vec2(maxRight - minLeft, maxBottom - minTop)
|
||||
new Vec2(maxRight - minLeft, maxBottom - minTop),
|
||||
)
|
||||
}
|
||||
|
||||
getBounds() { return this.bounds }
|
||||
getRectCount() { return this.rectCount }
|
||||
getChildren() { return this.children }
|
||||
getBounds() {
|
||||
return this.bounds
|
||||
}
|
||||
getRectCount() {
|
||||
return this.rectCount
|
||||
}
|
||||
getChildren() {
|
||||
return this.children
|
||||
}
|
||||
|
||||
forEachLeafNodeWithinBounds(configSpaceBounds: Rect, cb: (leaf: RangeTreeLeafNode) => void) {
|
||||
if (!this.bounds.hasIntersectionWith(configSpaceBounds)) return
|
||||
@ -202,17 +215,20 @@ export class FlamechartRenderer {
|
||||
for (let i = 0; i < layer.length; i++) {
|
||||
const frame = layer[i]
|
||||
if (batch.getRectCount() >= MAX_BATCH_SIZE) {
|
||||
leafNodes.push(new RangeTreeLeafNode(batch, new Rect(
|
||||
new Vec2(minLeft, stackDepth),
|
||||
new Vec2(maxRight - minLeft, 1)
|
||||
), rectCount))
|
||||
leafNodes.push(
|
||||
new RangeTreeLeafNode(
|
||||
batch,
|
||||
new Rect(new Vec2(minLeft, stackDepth), new Vec2(maxRight - minLeft, 1)),
|
||||
rectCount,
|
||||
),
|
||||
)
|
||||
minLeft = Infinity
|
||||
maxRight = -Infinity
|
||||
batch = canvasContext.createRectangleBatch()
|
||||
}
|
||||
const configSpaceBounds = new Rect(
|
||||
new Vec2(frame.start, y),
|
||||
new Vec2(frame.end - frame.start, 1)
|
||||
new Vec2(frame.end - frame.start, 1),
|
||||
)
|
||||
minLeft = Math.min(minLeft, configSpaceBounds.left())
|
||||
maxRight = Math.max(maxRight, configSpaceBounds.right())
|
||||
@ -225,17 +241,20 @@ export class FlamechartRenderer {
|
||||
const color = new Color(
|
||||
(1 + i % 255) / 256,
|
||||
(1 + stackDepth % 255) / 256,
|
||||
(1 + this.flamechart.getColorBucketForFrame(frame.node.frame)) / 256
|
||||
(1 + this.flamechart.getColorBucketForFrame(frame.node.frame)) / 256,
|
||||
)
|
||||
batch.addRect(configSpaceBounds, color)
|
||||
rectCount++
|
||||
}
|
||||
|
||||
if (batch.getRectCount() > 0) {
|
||||
leafNodes.push(new RangeTreeLeafNode(batch, new Rect(
|
||||
new Vec2(minLeft, stackDepth),
|
||||
new Vec2(maxRight - minLeft, 1)
|
||||
), rectCount))
|
||||
leafNodes.push(
|
||||
new RangeTreeLeafNode(
|
||||
batch,
|
||||
new Rect(new Vec2(minLeft, stackDepth), new Vec2(maxRight - minLeft, 1)),
|
||||
rectCount,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO(jlfwong): Making this into a binary tree
|
||||
@ -260,16 +279,13 @@ export class FlamechartRenderer {
|
||||
|
||||
const width = configSpaceContentWidth / Math.pow(2, zoomLevel)
|
||||
|
||||
return new Rect(
|
||||
new Vec2(width * index, stackDepth),
|
||||
new Vec2(width, 1)
|
||||
)
|
||||
return new Rect(new Vec2(width * index, stackDepth), new Vec2(width, 1))
|
||||
}
|
||||
|
||||
render(props: FlamechartRendererProps) {
|
||||
const { configSpaceSrcRect, physicalSpaceDstRect } = props
|
||||
|
||||
const atlasKeysToRender: { stackDepth: number, zoomLevel: number, index: number }[] = []
|
||||
const atlasKeysToRender: { stackDepth: number; zoomLevel: number; index: number }[] = []
|
||||
|
||||
// We want to render the lowest resolution we can while still guaranteeing that the
|
||||
// atlas line is higher resolution than its corresponding destination rectangle on
|
||||
@ -295,8 +311,12 @@ export class FlamechartRenderer {
|
||||
|
||||
const configSpaceContentWidth = this.flamechart.getTotalWeight()
|
||||
const numAtlasEntriesPerLayer = Math.pow(2, zoomLevel)
|
||||
const left = Math.floor(numAtlasEntriesPerLayer * configSpaceSrcRect.left() / configSpaceContentWidth)
|
||||
const right = Math.ceil(numAtlasEntriesPerLayer * configSpaceSrcRect.right() / configSpaceContentWidth)
|
||||
const left = Math.floor(
|
||||
numAtlasEntriesPerLayer * configSpaceSrcRect.left() / configSpaceContentWidth,
|
||||
)
|
||||
const right = Math.ceil(
|
||||
numAtlasEntriesPerLayer * configSpaceSrcRect.right() / configSpaceContentWidth,
|
||||
)
|
||||
|
||||
for (let stackDepth = top; stackDepth < bottom; stackDepth++) {
|
||||
for (let index = left; index <= right; index++) {
|
||||
@ -314,21 +334,24 @@ export class FlamechartRenderer {
|
||||
// Fill the cache
|
||||
this.rowAtlas.writeToAtlasIfNeeded(keysToRenderCached, (textureDstRect, key) => {
|
||||
const configSpaceBounds = this.configSpaceBoundsForKey(key)
|
||||
this.layers[key.stackDepth].forEachLeafNodeWithinBounds(configSpaceBounds, (leaf) => {
|
||||
this.layers[key.stackDepth].forEachLeafNodeWithinBounds(configSpaceBounds, leaf => {
|
||||
this.canvasContext.drawRectangleBatch({
|
||||
batch: leaf.getBatch(),
|
||||
configSpaceSrcRect: configSpaceBounds,
|
||||
physicalSpaceDstRect: textureDstRect,
|
||||
parityMin: key.stackDepth % 2 == 0 ? 2 : 0,
|
||||
parityOffset: leaf.getParity()
|
||||
parityOffset: leaf.getParity(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.framebuffer.resize(physicalSpaceDstRect.width(), physicalSpaceDstRect.height())
|
||||
this.framebuffer.use(context => {
|
||||
this.canvasContext.gl.clear({color: [0, 0, 0, 0]})
|
||||
const viewportRect = new Rect(Vec2.zero, new Vec2(context.viewportWidth, context.viewportHeight))
|
||||
this.canvasContext.gl.clear({ color: [0, 0, 0, 0] })
|
||||
const viewportRect = new Rect(
|
||||
Vec2.zero,
|
||||
new Vec2(context.viewportWidth, context.viewportHeight),
|
||||
)
|
||||
|
||||
const configToViewport = AffineTransform.betweenRects(configSpaceSrcRect, viewportRect)
|
||||
|
||||
@ -342,13 +365,13 @@ export class FlamechartRenderer {
|
||||
for (let key of keysToRenderUncached) {
|
||||
const configSpaceBounds = this.configSpaceBoundsForKey(key)
|
||||
const physicalBounds = configToViewport.transformRect(configSpaceBounds)
|
||||
this.layers[key.stackDepth].forEachLeafNodeWithinBounds(configSpaceBounds, (leaf) => {
|
||||
this.layers[key.stackDepth].forEachLeafNodeWithinBounds(configSpaceBounds, leaf => {
|
||||
this.canvasContext.drawRectangleBatch({
|
||||
batch: leaf.getBatch(),
|
||||
configSpaceSrcRect,
|
||||
physicalSpaceDstRect: physicalBounds,
|
||||
parityMin: key.stackDepth % 2 == 0 ? 2 : 0,
|
||||
parityOffset: leaf.getParity()
|
||||
parityOffset: leaf.getParity(),
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -356,9 +379,12 @@ export class FlamechartRenderer {
|
||||
|
||||
this.canvasContext.drawFlamechartColorPass({
|
||||
rectInfoTexture: this.rectInfoTexture,
|
||||
srcRect: new Rect(Vec2.zero, new Vec2(this.rectInfoTexture.width, this.rectInfoTexture.height)),
|
||||
srcRect: new Rect(
|
||||
Vec2.zero,
|
||||
new Vec2(this.rectInfoTexture.width, this.rectInfoTexture.height),
|
||||
),
|
||||
dstRect: physicalSpaceDstRect,
|
||||
renderOutlines: props.renderOutlines
|
||||
renderOutlines: props.renderOutlines,
|
||||
})
|
||||
|
||||
// Overlay the atlas on top of the canvas for debugging
|
||||
@ -370,4 +396,4 @@ export class FlamechartRenderer {
|
||||
})
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {StyleSheet} from 'aphrodite'
|
||||
import { StyleSheet } from 'aphrodite'
|
||||
import { FontFamily, FontSize, Colors } from './style'
|
||||
|
||||
const HOVERTIP_PADDING = 2
|
||||
@ -22,7 +22,7 @@ export const style = StyleSheet.create({
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
fontSize: FontSize.LABEL,
|
||||
fontFamily: FontFamily.MONOSPACE
|
||||
fontFamily: FontFamily.MONOSPACE,
|
||||
},
|
||||
hoverTipRow: {
|
||||
textOverflow: 'ellipsis',
|
||||
@ -33,10 +33,10 @@ export const style = StyleSheet.create({
|
||||
maxWidth: Sizes.TOOLTIP_WIDTH_MAX,
|
||||
},
|
||||
hoverCount: {
|
||||
color: '#6FCF97'
|
||||
color: '#6FCF97',
|
||||
},
|
||||
clip: {
|
||||
overflow: 'hidden'
|
||||
overflow: 'hidden',
|
||||
},
|
||||
vbox: {
|
||||
display: 'flex',
|
||||
@ -48,13 +48,13 @@ export const style = StyleSheet.create({
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0
|
||||
top: 0,
|
||||
},
|
||||
minimap: {
|
||||
height: Sizes.MINIMAP_HEIGHT,
|
||||
borderBottom: `${Sizes.SEPARATOR_HEIGHT}px solid ${Colors.MEDIUM_GRAY}`
|
||||
borderBottom: `${Sizes.SEPARATOR_HEIGHT}px solid ${Colors.MEDIUM_GRAY}`,
|
||||
},
|
||||
panZoomView: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {h} from 'preact'
|
||||
import {css} from 'aphrodite'
|
||||
import {ReloadableComponent} from './reloadable'
|
||||
import { h } from 'preact'
|
||||
import { css } from 'aphrodite'
|
||||
import { ReloadableComponent } from './reloadable'
|
||||
|
||||
import { CallTreeNode } from './profile'
|
||||
import { Flamechart, FlamechartFrame } from './flamechart'
|
||||
|
||||
import { Rect, Vec2, AffineTransform, clamp } from './math'
|
||||
import { cachedMeasureTextWidth } from "./utils";
|
||||
import { FlamechartMinimapView } from "./flamechart-minimap-view"
|
||||
import { cachedMeasureTextWidth } from './utils'
|
||||
import { FlamechartMinimapView } from './flamechart-minimap-view'
|
||||
|
||||
import { style, Sizes } from './flamechart-style'
|
||||
import { FontSize, FontFamily, Colors } from './style'
|
||||
@ -19,7 +19,13 @@ interface FlamechartFrameLabel {
|
||||
node: CallTreeNode
|
||||
}
|
||||
|
||||
function binarySearch(lo: number, hi: number, f: (val: number) => number, target: number, targetRangeSize = 1): [number, number] {
|
||||
function binarySearch(
|
||||
lo: number,
|
||||
hi: number,
|
||||
f: (val: number) => number,
|
||||
target: number,
|
||||
targetRangeSize = 1,
|
||||
): [number, number] {
|
||||
console.assert(!isNaN(targetRangeSize) && !isNaN(target))
|
||||
while (true) {
|
||||
if (hi - lo <= targetRangeSize) return [lo, hi]
|
||||
@ -41,9 +47,14 @@ function buildTrimmedText(text: string, length: number) {
|
||||
|
||||
function trimTextMid(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
|
||||
if (cachedMeasureTextWidth(ctx, text) <= maxWidth) return text
|
||||
const [lo,] = binarySearch(0, text.length, (n) => {
|
||||
return cachedMeasureTextWidth(ctx, buildTrimmedText(text, n))
|
||||
}, maxWidth)
|
||||
const [lo] = binarySearch(
|
||||
0,
|
||||
text.length,
|
||||
n => {
|
||||
return cachedMeasureTextWidth(ctx, buildTrimmedText(text, n))
|
||||
},
|
||||
maxWidth,
|
||||
)
|
||||
return buildTrimmedText(text, lo)
|
||||
}
|
||||
|
||||
@ -108,14 +119,14 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
private configSpaceSize() {
|
||||
return new Vec2(
|
||||
this.props.flamechart.getTotalWeight(),
|
||||
this.props.flamechart.getLayers().length
|
||||
this.props.flamechart.getLayers().length,
|
||||
)
|
||||
}
|
||||
|
||||
private physicalViewSize() {
|
||||
return new Vec2(
|
||||
this.overlayCanvas ? this.overlayCanvas.width : 0,
|
||||
this.overlayCanvas ? this.overlayCanvas.height : 0
|
||||
this.overlayCanvas ? this.overlayCanvas.height : 0,
|
||||
)
|
||||
}
|
||||
|
||||
@ -124,7 +135,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
private configSpaceToPhysicalViewSpace() {
|
||||
return AffineTransform.betweenRects(
|
||||
this.props.configSpaceViewportRect,
|
||||
new Rect(new Vec2(0, 0), this.physicalViewSize())
|
||||
new Rect(new Vec2(0, 0), this.physicalViewSize()),
|
||||
)
|
||||
}
|
||||
|
||||
@ -134,12 +145,14 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
|
||||
private resizeOverlayCanvasIfNeeded() {
|
||||
if (!this.overlayCanvas) return
|
||||
let {width, height} = this.overlayCanvas.getBoundingClientRect()
|
||||
{/*
|
||||
let { width, height } = this.overlayCanvas.getBoundingClientRect()
|
||||
{
|
||||
/*
|
||||
We render text at a higher resolution then scale down to
|
||||
ensure we're rendering at 1:1 device pixel ratio.
|
||||
This ensures our text is rendered crisply.
|
||||
*/}
|
||||
*/
|
||||
}
|
||||
width = Math.floor(width)
|
||||
height = Math.floor(height)
|
||||
|
||||
@ -149,8 +162,8 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
const scaledWidth = width * DEVICE_PIXEL_RATIO
|
||||
const scaledHeight = height * DEVICE_PIXEL_RATIO
|
||||
|
||||
if (scaledWidth === this.overlayCanvas.width &&
|
||||
scaledHeight === this.overlayCanvas.height) return
|
||||
if (scaledWidth === this.overlayCanvas.width && scaledHeight === this.overlayCanvas.height)
|
||||
return
|
||||
|
||||
this.overlayCanvas.width = scaledWidth
|
||||
this.overlayCanvas.height = scaledHeight
|
||||
@ -177,27 +190,30 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
if (this.hoveredLabel) {
|
||||
const physicalViewBounds = configToPhysical.transformRect(this.hoveredLabel.configSpaceBounds)
|
||||
ctx.strokeRect(
|
||||
Math.floor(physicalViewBounds.left()), Math.floor(physicalViewBounds.top()),
|
||||
Math.floor(physicalViewBounds.width()), Math.floor(physicalViewBounds.height())
|
||||
Math.floor(physicalViewBounds.left()),
|
||||
Math.floor(physicalViewBounds.top()),
|
||||
Math.floor(physicalViewBounds.width()),
|
||||
Math.floor(physicalViewBounds.height()),
|
||||
)
|
||||
}
|
||||
|
||||
ctx.font = `${physicalViewSpaceFontSize}px/${physicalViewSpaceFrameHeight}px ${FontFamily.MONOSPACE}`
|
||||
ctx.font = `${physicalViewSpaceFontSize}px/${physicalViewSpaceFrameHeight}px ${
|
||||
FontFamily.MONOSPACE
|
||||
}`
|
||||
ctx.fillStyle = Colors.DARK_GRAY
|
||||
ctx.textBaseline = 'top'
|
||||
|
||||
const minWidthToRender = cachedMeasureTextWidth(ctx, 'M' + ELLIPSIS + 'M')
|
||||
const minConfigSpaceWidthToRender = (configToPhysical.inverseTransformVector(new Vec2(minWidthToRender, 0)) || new Vec2(0, 0)).x
|
||||
const minConfigSpaceWidthToRender = (
|
||||
configToPhysical.inverseTransformVector(new Vec2(minWidthToRender, 0)) || new Vec2(0, 0)
|
||||
).x
|
||||
const LABEL_PADDING_PX = (physicalViewSpaceFrameHeight - physicalViewSpaceFontSize) / 2
|
||||
const PADDING_OFFSET = new Vec2(LABEL_PADDING_PX, LABEL_PADDING_PX)
|
||||
const SIZE_OFFSET = new Vec2(2 * LABEL_PADDING_PX, 2 * LABEL_PADDING_PX)
|
||||
|
||||
const renderFrameLabelAndChildren = (frame: FlamechartFrame, depth = 0) => {
|
||||
const width = frame.end - frame.start
|
||||
const configSpaceBounds = new Rect(
|
||||
new Vec2(frame.start, depth),
|
||||
new Vec2(width, 1)
|
||||
)
|
||||
const configSpaceBounds = new Rect(new Vec2(frame.start, depth), new Vec2(width, 1))
|
||||
|
||||
if (width < minConfigSpaceWidthToRender) return
|
||||
if (configSpaceBounds.left() > this.props.configSpaceViewportRect.right()) return
|
||||
@ -210,11 +226,16 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
if (physicalLabelBounds.left() < 0) {
|
||||
physicalLabelBounds = physicalLabelBounds
|
||||
.withOrigin(physicalLabelBounds.origin.withX(0))
|
||||
.withSize(physicalLabelBounds.size.withX(physicalLabelBounds.size.x + physicalLabelBounds.left()))
|
||||
.withSize(
|
||||
physicalLabelBounds.size.withX(
|
||||
physicalLabelBounds.size.x + physicalLabelBounds.left(),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (physicalLabelBounds.right() > physicalViewSize.x) {
|
||||
physicalLabelBounds = physicalLabelBounds
|
||||
.withSize(physicalLabelBounds.size.withX(physicalViewSize.x - physicalLabelBounds.left()))
|
||||
physicalLabelBounds = physicalLabelBounds.withSize(
|
||||
physicalLabelBounds.size.withX(physicalViewSize.x - physicalLabelBounds.left()),
|
||||
)
|
||||
}
|
||||
|
||||
physicalLabelBounds = physicalLabelBounds
|
||||
@ -230,7 +251,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
}
|
||||
}
|
||||
|
||||
for (let frame of (this.props.flamechart.getLayers()[0] || [])) {
|
||||
for (let frame of this.props.flamechart.getLayers()[0] || []) {
|
||||
renderFrameLabelAndChildren(frame)
|
||||
}
|
||||
|
||||
@ -241,7 +262,9 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
// 1eN, 2eN, or 5eN for some N
|
||||
|
||||
// Ideally, we want an interval every 100 logical screen pixels
|
||||
const logicalToConfig = (this.configSpaceToPhysicalViewSpace().inverted() || new AffineTransform()).times(this.logicalToPhysicalViewSpace())
|
||||
const logicalToConfig = (
|
||||
this.configSpaceToPhysicalViewSpace().inverted() || new AffineTransform()
|
||||
).times(this.logicalToPhysicalViewSpace())
|
||||
const targetInterval = logicalToConfig.transformVector(new Vec2(200, 1)).x
|
||||
|
||||
const minInterval = Math.pow(10, Math.floor(Math.log10(targetInterval)))
|
||||
@ -280,19 +303,22 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
if (width < 2 || height < 2) return
|
||||
|
||||
if (this.lastBounds == null) {
|
||||
this.setConfigSpaceViewportRect(new Rect(
|
||||
new Vec2(0, -1),
|
||||
new Vec2(this.configSpaceSize().x, height / this.LOGICAL_VIEW_SPACE_FRAME_HEIGHT)
|
||||
))
|
||||
this.setConfigSpaceViewportRect(
|
||||
new Rect(
|
||||
new Vec2(0, -1),
|
||||
new Vec2(this.configSpaceSize().x, height / this.LOGICAL_VIEW_SPACE_FRAME_HEIGHT),
|
||||
),
|
||||
)
|
||||
} else if (windowResized) {
|
||||
// Resize the viewport rectangle to match the window size aspect
|
||||
// ratio.
|
||||
this.setConfigSpaceViewportRect(this.props.configSpaceViewportRect.withSize(
|
||||
this.props.configSpaceViewportRect.size.timesPointwise(new Vec2(
|
||||
width / this.lastBounds.width,
|
||||
height / this.lastBounds.height
|
||||
))
|
||||
))
|
||||
this.setConfigSpaceViewportRect(
|
||||
this.props.configSpaceViewportRect.withSize(
|
||||
this.props.configSpaceViewportRect.size.timesPointwise(
|
||||
new Vec2(width / this.lastBounds.width, height / this.lastBounds.height),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
this.lastBounds = bounds
|
||||
}
|
||||
@ -308,11 +334,11 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
|
||||
if (this.props.configSpaceViewportRect.isEmpty()) return
|
||||
|
||||
this.props.canvasContext.renderInto(this.container, (context) => {
|
||||
this.props.canvasContext.renderInto(this.container, context => {
|
||||
this.props.flamechartRenderer.render({
|
||||
physicalSpaceDstRect: new Rect(Vec2.zero, this.physicalViewSize()),
|
||||
configSpaceSrcRect: this.props.configSpaceViewportRect,
|
||||
renderOutlines: true
|
||||
renderOutlines: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -333,7 +359,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
private maybeClearInteractionLock = () => {
|
||||
if (this.interactionLock) {
|
||||
if (!this.frameHadWheelEvent) {
|
||||
this.framesWithoutWheelEvents++;
|
||||
this.framesWithoutWheelEvents++
|
||||
if (this.framesWithoutWheelEvents >= 2) {
|
||||
this.interactionLock = null
|
||||
this.framesWithoutWheelEvents = 0
|
||||
@ -367,12 +393,15 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
private zoom(logicalViewSpaceCenter: Vec2, multiplier: number) {
|
||||
this.interactionLock = 'zoom'
|
||||
|
||||
const physicalCenter = this.logicalToPhysicalViewSpace().transformPosition(logicalViewSpaceCenter)
|
||||
const configSpaceCenter = this.configSpaceToPhysicalViewSpace().inverseTransformPosition(physicalCenter)
|
||||
const physicalCenter = this.logicalToPhysicalViewSpace().transformPosition(
|
||||
logicalViewSpaceCenter,
|
||||
)
|
||||
const configSpaceCenter = this.configSpaceToPhysicalViewSpace().inverseTransformPosition(
|
||||
physicalCenter,
|
||||
)
|
||||
if (!configSpaceCenter) return
|
||||
|
||||
const zoomTransform = AffineTransform
|
||||
.withTranslation(configSpaceCenter.times(-1))
|
||||
const zoomTransform = AffineTransform.withTranslation(configSpaceCenter.times(-1))
|
||||
.scaledBy(new Vec2(multiplier, 1))
|
||||
.translatedBy(configSpaceCenter)
|
||||
|
||||
@ -404,7 +433,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
const hoveredBounds = this.hoveredLabel.configSpaceBounds
|
||||
const viewportRect = new Rect(
|
||||
hoveredBounds.origin.minus(new Vec2(0, 1)),
|
||||
hoveredBounds.size.withY(this.props.configSpaceViewportRect.height())
|
||||
hoveredBounds.size.withY(this.props.configSpaceViewportRect.height()),
|
||||
)
|
||||
this.props.setConfigSpaceViewportRect(viewportRect)
|
||||
}
|
||||
@ -434,24 +463,25 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
}
|
||||
this.hoveredLabel = null
|
||||
const logicalViewSpaceMouse = new Vec2(ev.offsetX, ev.offsetY)
|
||||
const physicalViewSpaceMouse = this.logicalToPhysicalViewSpace().transformPosition(logicalViewSpaceMouse)
|
||||
const configSpaceMouse = this.configSpaceToPhysicalViewSpace().inverseTransformPosition(physicalViewSpaceMouse)
|
||||
const physicalViewSpaceMouse = this.logicalToPhysicalViewSpace().transformPosition(
|
||||
logicalViewSpaceMouse,
|
||||
)
|
||||
const configSpaceMouse = this.configSpaceToPhysicalViewSpace().inverseTransformPosition(
|
||||
physicalViewSpaceMouse,
|
||||
)
|
||||
|
||||
if (!configSpaceMouse) return
|
||||
|
||||
const setHoveredLabel = (frame: FlamechartFrame, depth = 0) => {
|
||||
const width = frame.end - frame.start
|
||||
const configSpaceBounds = new Rect(
|
||||
new Vec2(frame.start, depth),
|
||||
new Vec2(width, 1)
|
||||
)
|
||||
const configSpaceBounds = new Rect(new Vec2(frame.start, depth), new Vec2(width, 1))
|
||||
if (configSpaceMouse.x < configSpaceBounds.left()) return null
|
||||
if (configSpaceMouse.x > configSpaceBounds.right()) return null
|
||||
|
||||
if (configSpaceBounds.contains(configSpaceMouse)) {
|
||||
this.hoveredLabel = {
|
||||
configSpaceBounds,
|
||||
node: frame.node
|
||||
node: frame.node,
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,7 +490,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
}
|
||||
}
|
||||
|
||||
for (let frame of (this.props.flamechart.getLayers()[0] || [])) {
|
||||
for (let frame of this.props.flamechart.getLayers()[0] || []) {
|
||||
setHoveredLabel(frame)
|
||||
}
|
||||
|
||||
@ -486,13 +516,13 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
const isZoom = ev.metaKey || ev.ctrlKey
|
||||
|
||||
if (isZoom && this.interactionLock !== 'pan') {
|
||||
let multiplier = 1 + (ev.deltaY / 100)
|
||||
let multiplier = 1 + ev.deltaY / 100
|
||||
|
||||
// On Chrome & Firefox, pinch-to-zoom maps to
|
||||
// WheelEvent + Ctrl Key. We'll accelerate it in
|
||||
// this case, since it feels a bit sluggish otherwise.
|
||||
if (ev.ctrlKey) {
|
||||
multiplier = 1 + (ev.deltaY / 40)
|
||||
multiplier = 1 + ev.deltaY / 40
|
||||
}
|
||||
|
||||
multiplier = clamp(multiplier, 0.1, 10.0)
|
||||
@ -507,7 +537,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
|
||||
onWindowKeyPress = (ev: KeyboardEvent) => {
|
||||
if (!this.container) return
|
||||
const {width, height} = this.container.getBoundingClientRect()
|
||||
const { width, height } = this.container.getBoundingClientRect()
|
||||
|
||||
if (ev.key === '=' || ev.key === '+') {
|
||||
this.zoom(new Vec2(width / 2, height / 2), 0.5)
|
||||
@ -528,8 +558,9 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
shouldComponentUpdate() { return false }
|
||||
shouldComponentUpdate() {
|
||||
return false
|
||||
}
|
||||
componentWillReceiveProps(nextProps: FlamechartPanZoomViewProps) {
|
||||
if (this.props.flamechart !== nextProps.flamechart) {
|
||||
this.renderCanvas()
|
||||
@ -557,11 +588,9 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
onDblClick={this.onDblClick}
|
||||
onWheel={this.onWheel}
|
||||
ref={this.containerRef}>
|
||||
<canvas
|
||||
width={1} height={1}
|
||||
ref={this.overlayCanvasRef}
|
||||
className={css(style.fill)} />
|
||||
ref={this.containerRef}
|
||||
>
|
||||
<canvas width={1} height={1} ref={this.overlayCanvasRef} className={css(style.fill)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -587,37 +616,40 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
this.state = {
|
||||
hoveredNode: null,
|
||||
configSpaceViewportRect: Rect.empty,
|
||||
logicalSpaceMouse: Vec2.zero
|
||||
logicalSpaceMouse: Vec2.zero,
|
||||
}
|
||||
}
|
||||
|
||||
private configSpaceSize() {
|
||||
return new Vec2(
|
||||
this.props.flamechart.getTotalWeight(),
|
||||
this.props.flamechart.getLayers().length
|
||||
this.props.flamechart.getLayers().length,
|
||||
)
|
||||
}
|
||||
|
||||
private minConfigSpaceViewportRectWidth() {
|
||||
return Math.min(this.props.flamechart.getTotalWeight(), 3 * this.props.flamechart.getMinFrameWidth());
|
||||
return Math.min(
|
||||
this.props.flamechart.getTotalWeight(),
|
||||
3 * this.props.flamechart.getMinFrameWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
private setConfigSpaceViewportRect = (viewportRect: Rect): void => {
|
||||
const configSpaceOriginBounds = new Rect(
|
||||
new Vec2(0, -1),
|
||||
Vec2.max(new Vec2(0, 0), this.configSpaceSize().minus(viewportRect.size))
|
||||
Vec2.max(new Vec2(0, 0), this.configSpaceSize().minus(viewportRect.size)),
|
||||
)
|
||||
|
||||
const configSpaceSizeBounds = new Rect(
|
||||
new Vec2(this.minConfigSpaceViewportRectWidth(), viewportRect.height()),
|
||||
new Vec2(this.configSpaceSize().x, viewportRect.height())
|
||||
new Vec2(this.configSpaceSize().x, viewportRect.height()),
|
||||
)
|
||||
|
||||
this.setState({
|
||||
configSpaceViewportRect: new Rect(
|
||||
configSpaceOriginBounds.closestPointTo(viewportRect.origin),
|
||||
configSpaceSizeBounds.closestPointTo(viewportRect.size)
|
||||
)
|
||||
configSpaceSizeBounds.closestPointTo(viewportRect.size),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@ -629,8 +661,8 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
onNodeHover = (hoveredNode: CallTreeNode | null, logicalSpaceMouse: Vec2) => {
|
||||
this.setState({
|
||||
hoveredNode,
|
||||
logicalSpaceMouse: logicalSpaceMouse.plus(new Vec2(0, Sizes.MINIMAP_HEIGHT))
|
||||
});
|
||||
logicalSpaceMouse: logicalSpaceMouse.plus(new Vec2(0, Sizes.MINIMAP_HEIGHT)),
|
||||
})
|
||||
}
|
||||
|
||||
formatValue(weight: number) {
|
||||
@ -652,7 +684,7 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
const { hoveredNode, logicalSpaceMouse } = this.state
|
||||
if (!hoveredNode) return null
|
||||
|
||||
const {width, height} = this.container.getBoundingClientRect()
|
||||
const { width, height } = this.container.getBoundingClientRect()
|
||||
|
||||
const positionStyle: {
|
||||
left?: number
|
||||
@ -665,26 +697,30 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
if (logicalSpaceMouse.x + OFFSET_FROM_MOUSE + Sizes.TOOLTIP_WIDTH_MAX < width) {
|
||||
positionStyle.left = logicalSpaceMouse.x + OFFSET_FROM_MOUSE
|
||||
} else {
|
||||
positionStyle.right = (width - logicalSpaceMouse.x) + 1
|
||||
positionStyle.right = width - logicalSpaceMouse.x + 1
|
||||
}
|
||||
|
||||
if (logicalSpaceMouse.y + OFFSET_FROM_MOUSE + Sizes.TOOLTIP_HEIGHT_MAX < height) {
|
||||
positionStyle.top = logicalSpaceMouse.y + OFFSET_FROM_MOUSE
|
||||
} else {
|
||||
positionStyle.bottom = (height - logicalSpaceMouse.y) + 1
|
||||
positionStyle.bottom = height - logicalSpaceMouse.y + 1
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={css(style.hoverTip)} style={positionStyle}>
|
||||
<div className={css(style.hoverTipRow)}>
|
||||
<span className={css(style.hoverCount)}>{this.formatValue(hoveredNode.getTotalWeight())}</span>{' '}
|
||||
<span className={css(style.hoverCount)}>
|
||||
{this.formatValue(hoveredNode.getTotalWeight())}
|
||||
</span>{' '}
|
||||
{hoveredNode.frame.name}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
containerRef = (container?: Element) => { this.container = container as HTMLDivElement || null }
|
||||
containerRef = (container?: Element) => {
|
||||
this.container = (container as HTMLDivElement) || null
|
||||
}
|
||||
|
||||
panZoomView: FlamechartPanZoomView | null = null
|
||||
panZoomRef = (view: FlamechartPanZoomView | null) => {
|
||||
@ -692,7 +728,7 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
}
|
||||
subcomponents() {
|
||||
return {
|
||||
panZoom: this.panZoomView
|
||||
panZoom: this.panZoomView,
|
||||
}
|
||||
}
|
||||
|
||||
@ -705,7 +741,8 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
flamechart={this.props.flamechart}
|
||||
flamechartRenderer={this.props.flamechartRenderer}
|
||||
canvasContext={this.props.canvasContext}
|
||||
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect} />
|
||||
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect}
|
||||
/>
|
||||
<FlamechartPanZoomView
|
||||
ref={this.panZoomRef}
|
||||
canvasContext={this.props.canvasContext}
|
||||
@ -718,6 +755,6 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
/>
|
||||
{this.renderTooltip()}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Frame, CallTreeNode} from './profile'
|
||||
import { Frame, CallTreeNode } from './profile'
|
||||
|
||||
import { lastOf } from './utils'
|
||||
|
||||
@ -19,7 +19,7 @@ interface FlamechartDataSource {
|
||||
|
||||
forEachCall(
|
||||
openFrame: (node: CallTreeNode, value: number) => void,
|
||||
closeFrame: (value: number) => void
|
||||
closeFrame: (value: number) => void,
|
||||
): void
|
||||
|
||||
getColorBucketForFrame(f: Frame): number
|
||||
@ -31,11 +31,21 @@ export class Flamechart {
|
||||
private totalWeight: number = 0
|
||||
private minFrameWidth: number = 1
|
||||
|
||||
getTotalWeight() { return this.totalWeight }
|
||||
getLayers() { return this.layers }
|
||||
getColorBucketForFrame(frame: Frame) { return this.source.getColorBucketForFrame(frame) }
|
||||
getMinFrameWidth() { return this.minFrameWidth }
|
||||
formatValue(v: number) { return this.source.formatValue(v) }
|
||||
getTotalWeight() {
|
||||
return this.totalWeight
|
||||
}
|
||||
getLayers() {
|
||||
return this.layers
|
||||
}
|
||||
getColorBucketForFrame(frame: Frame) {
|
||||
return this.source.getColorBucketForFrame(frame)
|
||||
}
|
||||
getMinFrameWidth() {
|
||||
return this.minFrameWidth
|
||||
}
|
||||
formatValue(v: number) {
|
||||
return this.source.formatValue(v)
|
||||
}
|
||||
|
||||
constructor(private source: FlamechartDataSource) {
|
||||
const stack: FlamechartFrame[] = []
|
||||
@ -71,4 +81,4 @@ export class Flamechart {
|
||||
|
||||
if (!isFinite(this.minFrameWidth)) this.minFrameWidth = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// https://github.com/brendangregg/FlameGraph#2-fold-stacks
|
||||
|
||||
import {Profile, FrameInfo} from '../profile'
|
||||
import { Profile, FrameInfo } from '../profile'
|
||||
|
||||
interface BGSample {
|
||||
stack: FrameInfo[]
|
||||
@ -9,10 +9,10 @@ interface BGSample {
|
||||
|
||||
function parseBGFoldedStacks(contents: string): BGSample[] {
|
||||
const samples: BGSample[] = []
|
||||
contents.replace(/^(.*) (\d+)$/mg, (match: string, stack: string, n: string) => {
|
||||
contents.replace(/^(.*) (\d+)$/gm, (match: string, stack: string, n: string) => {
|
||||
samples.push({
|
||||
stack: stack.split(';').map(name => ({key: name, name: name})),
|
||||
duration: parseInt(n, 10)
|
||||
stack: stack.split(';').map(name => ({ key: name, name: name })),
|
||||
duration: parseInt(n, 10),
|
||||
})
|
||||
return match
|
||||
})
|
||||
@ -27,4 +27,4 @@ export function importFromBGFlameGraph(contents: string): Profile {
|
||||
profile.appendSample(sample.stack, sample.duration)
|
||||
}
|
||||
return profile
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,29 @@
|
||||
import {Profile, TimeFormatter, FrameInfo} from '../profile'
|
||||
import {getOrInsert, lastOf} from '../utils'
|
||||
import { Profile, TimeFormatter, FrameInfo } from '../profile'
|
||||
import { getOrInsert, lastOf } from '../utils'
|
||||
|
||||
interface TimelineEvent {
|
||||
pid: number,
|
||||
tid: number,
|
||||
ts: number,
|
||||
ph: string,
|
||||
cat: string,
|
||||
name: string,
|
||||
dur: number,
|
||||
tdur: number,
|
||||
tts: number,
|
||||
pid: number
|
||||
tid: number
|
||||
ts: number
|
||||
ph: string
|
||||
cat: string
|
||||
name: string
|
||||
dur: number
|
||||
tdur: number
|
||||
tts: number
|
||||
args: { [key: string]: any }
|
||||
}
|
||||
|
||||
interface PositionTickInfo {
|
||||
line: number,
|
||||
line: number
|
||||
ticks: number
|
||||
}
|
||||
|
||||
interface CPUProfileCallFrame {
|
||||
columnNumber: number,
|
||||
functionName: string,
|
||||
lineNumber: number,
|
||||
scriptId: string,
|
||||
columnNumber: number
|
||||
functionName: string
|
||||
lineNumber: number
|
||||
scriptId: string
|
||||
url: string
|
||||
}
|
||||
|
||||
@ -37,10 +37,10 @@ interface CPUProfileNode {
|
||||
}
|
||||
|
||||
interface CPUProfile {
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
nodes: CPUProfileNode[],
|
||||
samples: number[],
|
||||
startTime: number
|
||||
endTime: number
|
||||
nodes: CPUProfileNode[]
|
||||
samples: number[]
|
||||
timeDeltas: number[]
|
||||
}
|
||||
|
||||
@ -48,18 +48,18 @@ export function importFromChromeTimeline(events: TimelineEvent[]) {
|
||||
// It seems like sometimes Chrome timeline files contain multiple CpuProfiles?
|
||||
// For now, choose the first one in the list.
|
||||
for (let event of events) {
|
||||
if (event.name == "CpuProfile") {
|
||||
if (event.name == 'CpuProfile') {
|
||||
const chromeProfile = event.args.data.cpuProfile as CPUProfile
|
||||
return importFromChromeCPUProfile(chromeProfile)
|
||||
}
|
||||
}
|
||||
throw new Error("Could not find CPU profile in Timeline")
|
||||
throw new Error('Could not find CPU profile in Timeline')
|
||||
}
|
||||
|
||||
const callFrameToFrameInfo = new Map<CPUProfileCallFrame, FrameInfo>()
|
||||
function frameInfoForCallFrame(callFrame: CPUProfileCallFrame) {
|
||||
return getOrInsert(callFrameToFrameInfo, callFrame, (callFrame) => {
|
||||
const name = callFrame.functionName || "(anonymous)"
|
||||
return getOrInsert(callFrameToFrameInfo, callFrame, callFrame => {
|
||||
const name = callFrame.functionName || '(anonymous)'
|
||||
const file = callFrame.url
|
||||
const line = callFrame.lineNumber
|
||||
const col = callFrame.columnNumber
|
||||
@ -68,7 +68,7 @@ function frameInfoForCallFrame(callFrame: CPUProfileCallFrame) {
|
||||
name,
|
||||
file,
|
||||
line,
|
||||
col
|
||||
col,
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -117,7 +117,7 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile) {
|
||||
|
||||
let value = 0
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
const timeDelta = timeDeltas[i+1] || 0
|
||||
const timeDelta = timeDeltas[i + 1] || 0
|
||||
const nodeId = samples[i]
|
||||
let stackTop = nodeById.get(nodeId)
|
||||
if (!stackTop) continue
|
||||
@ -130,7 +130,10 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile) {
|
||||
for (
|
||||
lca = stackTop;
|
||||
lca && prevStack.indexOf(lca) === -1;
|
||||
lca = lca.callFrame.functionName === "(garbage collector)" ? lastOf(prevStack) : lca.parent || null
|
||||
lca =
|
||||
lca.callFrame.functionName === '(garbage collector)'
|
||||
? lastOf(prevStack)
|
||||
: lca.parent || null
|
||||
) {}
|
||||
|
||||
// Close frames that are no longer open
|
||||
@ -146,7 +149,10 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile) {
|
||||
let node: CPUProfileNode | null = stackTop;
|
||||
node && node != lca;
|
||||
// Place GC calls on top of the previous call stack
|
||||
node = node.callFrame.functionName === "(garbage collector)" ? lastOf(prevStack) : node.parent || null
|
||||
node =
|
||||
node.callFrame.functionName === '(garbage collector)'
|
||||
? lastOf(prevStack)
|
||||
: node.parent || null
|
||||
) {
|
||||
toOpen.push(node)
|
||||
}
|
||||
@ -167,4 +173,4 @@ export function importFromChromeCPUProfile(chromeProfile: CPUProfile) {
|
||||
|
||||
profile.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
return profile
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// https://github.com/tmm1/stackprof
|
||||
|
||||
import {Profile, TimeFormatter, FrameInfo} from '../profile'
|
||||
import { Profile, TimeFormatter, FrameInfo } from '../profile'
|
||||
|
||||
interface StackprofFrame {
|
||||
name: string
|
||||
@ -9,7 +9,7 @@ interface StackprofFrame {
|
||||
}
|
||||
|
||||
export interface StackprofProfile {
|
||||
frames: {[number: string]: StackprofFrame}
|
||||
frames: { [number: string]: StackprofFrame }
|
||||
raw: number[]
|
||||
raw_timestamp_deltas: number[]
|
||||
}
|
||||
@ -18,9 +18,9 @@ export function importFromStackprof(stackprofProfile: StackprofProfile): Profile
|
||||
const duration = stackprofProfile.raw_timestamp_deltas.reduce((a, b) => a + b, 0)
|
||||
const profile = new Profile(duration)
|
||||
|
||||
const {frames, raw, raw_timestamp_deltas} = stackprofProfile
|
||||
const { frames, raw, raw_timestamp_deltas } = stackprofProfile
|
||||
let sampleIndex = 0
|
||||
for (let i = 0; i < raw.length;) {
|
||||
for (let i = 0; i < raw.length; ) {
|
||||
const stackHeight = raw[i++]
|
||||
|
||||
const stack: FrameInfo[] = []
|
||||
@ -28,7 +28,7 @@ export function importFromStackprof(stackprofProfile: StackprofProfile): Profile
|
||||
const id = raw[i++]
|
||||
stack.push({
|
||||
key: id,
|
||||
...frames[id]
|
||||
...frames[id],
|
||||
})
|
||||
}
|
||||
const nSamples = raw[i++]
|
||||
@ -43,4 +43,4 @@ export function importFromStackprof(stackprofProfile: StackprofProfile): Profile
|
||||
|
||||
profile.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
return profile
|
||||
}
|
||||
}
|
||||
|
28
lru-cache.ts
28
lru-cache.ts
@ -1,18 +1,24 @@
|
||||
class ListNode<V> {
|
||||
prev: ListNode<V> | null = null
|
||||
next: ListNode<V> | null = null
|
||||
constructor(readonly data: V) { }
|
||||
constructor(readonly data: V) {}
|
||||
}
|
||||
|
||||
export class List<V> {
|
||||
private head: ListNode<V> | null = null
|
||||
private tail: ListNode<V> | null = null
|
||||
private size: number = 0
|
||||
constructor() { }
|
||||
constructor() {}
|
||||
|
||||
getHead(): ListNode<V> | null { return this.head }
|
||||
getTail(): ListNode<V> | null { return this.tail }
|
||||
getSize(): number { return this.size }
|
||||
getHead(): ListNode<V> | null {
|
||||
return this.head
|
||||
}
|
||||
getTail(): ListNode<V> | null {
|
||||
return this.tail
|
||||
}
|
||||
getSize(): number {
|
||||
return this.size
|
||||
}
|
||||
|
||||
append(node: ListNode<V>): void {
|
||||
if (!this.tail) {
|
||||
@ -97,7 +103,7 @@ export class LRUCache<K, V> {
|
||||
private list = new List<K>()
|
||||
private map = new Map<K, LRUCacheNode<K, V>>()
|
||||
|
||||
constructor(private capacity: number) { }
|
||||
constructor(private capacity: number) {}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this.map.has(key)
|
||||
@ -115,9 +121,13 @@ export class LRUCache<K, V> {
|
||||
return node ? node.value : null
|
||||
}
|
||||
|
||||
getSize() { return this.list.getSize() }
|
||||
getSize() {
|
||||
return this.list.getSize()
|
||||
}
|
||||
|
||||
getCapacity() { return this.capacity }
|
||||
getCapacity() {
|
||||
return this.capacity
|
||||
}
|
||||
|
||||
insert(key: K, value: V) {
|
||||
const node = this.map.get(key)
|
||||
@ -149,4 +159,4 @@ export class LRUCache<K, V> {
|
||||
this.map.delete(key)
|
||||
return [key, value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
203
math.ts
203
math.ts
@ -6,19 +6,43 @@ export function clamp(x: number, minVal: number, maxVal: number) {
|
||||
|
||||
export class Vec2 {
|
||||
constructor(readonly x: number, readonly y: number) {}
|
||||
withX(x: number) { return new Vec2(x, this.y) }
|
||||
withY(y: number) { return new Vec2(this.x, y) }
|
||||
withX(x: number) {
|
||||
return new Vec2(x, this.y)
|
||||
}
|
||||
withY(y: number) {
|
||||
return new Vec2(this.x, y)
|
||||
}
|
||||
|
||||
plus(other: Vec2) { return new Vec2(this.x + other.x, this.y + other.y) }
|
||||
minus(other: Vec2) { return new Vec2(this.x - other.x, this.y - other.y) }
|
||||
times(scalar: number) { return new Vec2(this.x * scalar, this.y * scalar) }
|
||||
timesPointwise(other: Vec2) { return new Vec2(this.x * other.x, this.y * other.y) }
|
||||
dividedByPointwise(other: Vec2) { return new Vec2(this.x / other.x, this.y / other.y) }
|
||||
dot(other: Vec2) { return this.x * other.x + this.y * other.y }
|
||||
equals(other: Vec2) { return this.x === other.x && this.y === other.y }
|
||||
length2() { return this.dot(this) }
|
||||
length() { return Math.sqrt(this.length2()) }
|
||||
abs() { return new Vec2(Math.abs(this.x), Math.abs(this.y)) }
|
||||
plus(other: Vec2) {
|
||||
return new Vec2(this.x + other.x, this.y + other.y)
|
||||
}
|
||||
minus(other: Vec2) {
|
||||
return new Vec2(this.x - other.x, this.y - other.y)
|
||||
}
|
||||
times(scalar: number) {
|
||||
return new Vec2(this.x * scalar, this.y * scalar)
|
||||
}
|
||||
timesPointwise(other: Vec2) {
|
||||
return new Vec2(this.x * other.x, this.y * other.y)
|
||||
}
|
||||
dividedByPointwise(other: Vec2) {
|
||||
return new Vec2(this.x / other.x, this.y / other.y)
|
||||
}
|
||||
dot(other: Vec2) {
|
||||
return this.x * other.x + this.y * other.y
|
||||
}
|
||||
equals(other: Vec2) {
|
||||
return this.x === other.x && this.y === other.y
|
||||
}
|
||||
length2() {
|
||||
return this.dot(this)
|
||||
}
|
||||
length() {
|
||||
return Math.sqrt(this.length2())
|
||||
}
|
||||
abs() {
|
||||
return new Vec2(Math.abs(this.x), Math.abs(this.y))
|
||||
}
|
||||
|
||||
static min(a: Vec2, b: Vec2) {
|
||||
return new Vec2(Math.min(a.x, b.x), Math.min(a.y, b.y))
|
||||
@ -31,52 +55,56 @@ export class Vec2 {
|
||||
static zero = new Vec2(0, 0)
|
||||
static unit = new Vec2(1, 1)
|
||||
|
||||
flatten(): [number, number] { return [this.x, this.y] }
|
||||
flatten(): [number, number] {
|
||||
return [this.x, this.y]
|
||||
}
|
||||
}
|
||||
|
||||
export class AffineTransform {
|
||||
constructor(
|
||||
readonly m00 = 1, readonly m01 = 0, readonly m02 = 0,
|
||||
readonly m10 = 0, readonly m11 = 1, readonly m12 = 0
|
||||
readonly m00 = 1,
|
||||
readonly m01 = 0,
|
||||
readonly m02 = 0,
|
||||
readonly m10 = 0,
|
||||
readonly m11 = 1,
|
||||
readonly m12 = 0,
|
||||
) {}
|
||||
|
||||
withScale(s: Vec2) {
|
||||
let {
|
||||
m00, m01, m02,
|
||||
m10, m11, m12
|
||||
} = this
|
||||
let { m00, m01, m02, m10, m11, m12 } = this
|
||||
m00 = s.x
|
||||
m11 = s.y
|
||||
return new AffineTransform(m00, m01, m02, m10, m11, m12)
|
||||
}
|
||||
static withScale(s: Vec2) {
|
||||
return (new AffineTransform).withScale(s)
|
||||
return new AffineTransform().withScale(s)
|
||||
}
|
||||
scaledBy(s: Vec2) {
|
||||
return AffineTransform.withScale(s).times(this)
|
||||
}
|
||||
getScale() {
|
||||
return new Vec2(this.m00, this.m11)
|
||||
}
|
||||
scaledBy(s: Vec2) { return AffineTransform.withScale(s).times(this) }
|
||||
getScale() { return new Vec2(this.m00, this.m11) }
|
||||
|
||||
withTranslation(t: Vec2) {
|
||||
let {
|
||||
m00, m01, m02,
|
||||
m10, m11, m12
|
||||
} = this
|
||||
let { m00, m01, m02, m10, m11, m12 } = this
|
||||
m02 = t.x
|
||||
m12 = t.y
|
||||
return new AffineTransform(m00, m01, m02, m10, m11, m12)
|
||||
}
|
||||
static withTranslation(t: Vec2) {
|
||||
return (new AffineTransform).withTranslation(t)
|
||||
return new AffineTransform().withTranslation(t)
|
||||
}
|
||||
getTranslation() {
|
||||
return new Vec2(this.m02, this.m12)
|
||||
}
|
||||
translatedBy(t: Vec2) {
|
||||
return AffineTransform.withTranslation(t).times(this)
|
||||
}
|
||||
getTranslation() { return new Vec2(this.m02, this.m12) }
|
||||
translatedBy(t: Vec2) { return AffineTransform.withTranslation(t).times(this) }
|
||||
|
||||
static betweenRects(from: Rect, to: Rect) {
|
||||
return AffineTransform
|
||||
.withTranslation(from.origin.times(-1))
|
||||
.scaledBy(new Vec2(
|
||||
to.size.x / from.size.x,
|
||||
to.size.y / from.size.y
|
||||
))
|
||||
return AffineTransform.withTranslation(from.origin.times(-1))
|
||||
.scaledBy(new Vec2(to.size.x / from.size.x, to.size.y / from.size.y))
|
||||
.translatedBy(to.origin)
|
||||
}
|
||||
|
||||
@ -92,34 +120,34 @@ export class AffineTransform {
|
||||
}
|
||||
|
||||
equals(other: AffineTransform) {
|
||||
return this.m00 == other.m00 &&
|
||||
this.m01 == other.m01 &&
|
||||
this.m02 == other.m02 &&
|
||||
this.m10 == other.m10 &&
|
||||
this.m11 == other.m11 &&
|
||||
this.m12 == other.m12;
|
||||
return (
|
||||
this.m00 == other.m00 &&
|
||||
this.m01 == other.m01 &&
|
||||
this.m02 == other.m02 &&
|
||||
this.m10 == other.m10 &&
|
||||
this.m11 == other.m11 &&
|
||||
this.m12 == other.m12
|
||||
)
|
||||
}
|
||||
|
||||
timesScalar(s: number) {
|
||||
const {m00, m01, m02, m10, m11, m12} = this
|
||||
return new AffineTransform(s*m00, s*m01, s*m02, s*m10, s*m11, s*m12)
|
||||
const { m00, m01, m02, m10, m11, m12 } = this
|
||||
return new AffineTransform(s * m00, s * m01, s * m02, s * m10, s * m11, s * m12)
|
||||
}
|
||||
|
||||
det() {
|
||||
const {m00, m01, m02, m10, m11, m12} = this
|
||||
const { m00, m01, m02, m10, m11, m12 } = this
|
||||
const m20 = 0
|
||||
const m21 = 0
|
||||
const m22 = 1
|
||||
|
||||
return (
|
||||
m00 * (m11 * m22 - m12 * m21) -
|
||||
m01 * (m10 * m22 - m12 * m20) +
|
||||
m02 * (m10 * m21 - m11 * m20)
|
||||
m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20)
|
||||
)
|
||||
}
|
||||
|
||||
adj() {
|
||||
const {m00, m01, m02, m10, m11, m12} = this
|
||||
const { m00, m01, m02, m10, m11, m12 } = this
|
||||
const m20 = 0
|
||||
const m21 = 0
|
||||
const m22 = 1
|
||||
@ -149,10 +177,7 @@ export class AffineTransform {
|
||||
}
|
||||
|
||||
transformVector(v: Vec2) {
|
||||
return new Vec2(
|
||||
v.x * this.m00 + v.y * this.m01,
|
||||
v.x * this.m10 + v.y * this.m11
|
||||
)
|
||||
return new Vec2(v.x * this.m00 + v.y * this.m01, v.x * this.m10 + v.y * this.m11)
|
||||
}
|
||||
|
||||
inverseTransformVector(v: Vec2): Vec2 | null {
|
||||
@ -164,7 +189,7 @@ export class AffineTransform {
|
||||
transformPosition(v: Vec2) {
|
||||
return new Vec2(
|
||||
v.x * this.m00 + v.y * this.m01 + this.m02,
|
||||
v.x * this.m10 + v.y * this.m11 + this.m12
|
||||
v.x * this.m10 + v.y * this.m11 + this.m12,
|
||||
)
|
||||
}
|
||||
|
||||
@ -189,7 +214,7 @@ export class AffineTransform {
|
||||
return new Rect(origin, size)
|
||||
}
|
||||
|
||||
inverseTransformRect(r: Rect): Rect | null{
|
||||
inverseTransformRect(r: Rect): Rect | null {
|
||||
const inv = this.inverted()
|
||||
if (!inv) return null
|
||||
return inv.transformRect(r)
|
||||
@ -197,44 +222,60 @@ export class AffineTransform {
|
||||
|
||||
flatten(): [number, number, number, number, number, number, number, number, number] {
|
||||
// Flatten into GLSL format
|
||||
return [
|
||||
this.m00, this.m10, 0,
|
||||
this.m01, this.m11, 0,
|
||||
this.m02, this.m12, 1,
|
||||
]
|
||||
return [this.m00, this.m10, 0, this.m01, this.m11, 0, this.m02, this.m12, 1]
|
||||
}
|
||||
}
|
||||
|
||||
export class Rect {
|
||||
constructor(
|
||||
readonly origin: Vec2,
|
||||
readonly size: Vec2
|
||||
) {}
|
||||
constructor(readonly origin: Vec2, readonly size: Vec2) {}
|
||||
|
||||
isEmpty() { return this.width() == 0 || this.height() == 0 }
|
||||
isEmpty() {
|
||||
return this.width() == 0 || this.height() == 0
|
||||
}
|
||||
|
||||
width() { return this.size.x }
|
||||
height() { return this.size.y }
|
||||
width() {
|
||||
return this.size.x
|
||||
}
|
||||
height() {
|
||||
return this.size.y
|
||||
}
|
||||
|
||||
left() { return this.origin.x }
|
||||
right() { return this.left() + this.width() }
|
||||
top() { return this.origin.y }
|
||||
bottom() { return this.top() + this.height() }
|
||||
left() {
|
||||
return this.origin.x
|
||||
}
|
||||
right() {
|
||||
return this.left() + this.width()
|
||||
}
|
||||
top() {
|
||||
return this.origin.y
|
||||
}
|
||||
bottom() {
|
||||
return this.top() + this.height()
|
||||
}
|
||||
|
||||
topLeft() { return this.origin }
|
||||
topRight() { return this.origin.plus(new Vec2(this.width(), 0)) }
|
||||
topLeft() {
|
||||
return this.origin
|
||||
}
|
||||
topRight() {
|
||||
return this.origin.plus(new Vec2(this.width(), 0))
|
||||
}
|
||||
|
||||
bottomRight() { return this.origin.plus(this.size) }
|
||||
bottomLeft() { return this.origin.plus(new Vec2(0, this.height())) }
|
||||
bottomRight() {
|
||||
return this.origin.plus(this.size)
|
||||
}
|
||||
bottomLeft() {
|
||||
return this.origin.plus(new Vec2(0, this.height()))
|
||||
}
|
||||
|
||||
withOrigin(origin: Vec2) { return new Rect(origin, this.size) }
|
||||
withSize(size: Vec2) { return new Rect(this.origin, size) }
|
||||
withOrigin(origin: Vec2) {
|
||||
return new Rect(origin, this.size)
|
||||
}
|
||||
withSize(size: Vec2) {
|
||||
return new Rect(this.origin, size)
|
||||
}
|
||||
|
||||
closestPointTo(p: Vec2) {
|
||||
return new Vec2(
|
||||
clamp(p.x, this.left(), this.right()),
|
||||
clamp(p.y, this.top(), this.bottom())
|
||||
)
|
||||
return new Vec2(clamp(p.x, this.left(), this.right()), clamp(p.y, this.top(), this.bottom()))
|
||||
}
|
||||
|
||||
distanceFrom(p: Vec2) {
|
||||
@ -270,4 +311,4 @@ export class Rect {
|
||||
static empty = new Rect(Vec2.zero, Vec2.zero)
|
||||
static unit = new Rect(Vec2.zero, Vec2.unit)
|
||||
static NDC = new Rect(new Vec2(-1, -1), new Vec2(2, 2))
|
||||
}
|
||||
}
|
||||
|
@ -66,12 +66,12 @@ export class ViewportRectangleRenderer {
|
||||
srcRGB: 'src alpha',
|
||||
srcAlpha: 'one',
|
||||
dstRGB: 'one minus src alpha',
|
||||
dstAlpha: 'one'
|
||||
}
|
||||
dstAlpha: 'one',
|
||||
},
|
||||
},
|
||||
|
||||
depth: {
|
||||
enable: false
|
||||
enable: false,
|
||||
},
|
||||
|
||||
attributes: {
|
||||
@ -83,12 +83,7 @@ export class ViewportRectangleRenderer {
|
||||
// | /|
|
||||
// |/ |
|
||||
// 2 +--+ 3
|
||||
position: [
|
||||
[-1, 1],
|
||||
[1, 1],
|
||||
[-1, -1],
|
||||
[1, -1]
|
||||
]
|
||||
position: [[-1, 1], [1, 1], [-1, -1], [1, -1]],
|
||||
},
|
||||
|
||||
uniforms: {
|
||||
@ -109,12 +104,12 @@ export class ViewportRectangleRenderer {
|
||||
},
|
||||
framebufferHeight: (context, props) => {
|
||||
return context.framebufferHeight
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
primitive: 'triangle strip',
|
||||
|
||||
count: 4
|
||||
count: 4,
|
||||
})
|
||||
}
|
||||
|
||||
@ -122,6 +117,10 @@ export class ViewportRectangleRenderer {
|
||||
this.command(props)
|
||||
}
|
||||
|
||||
resetStats() { return Object.assign(this.command.stats, { cpuTime: 0, gpuTime: 0, count: 0 }) }
|
||||
stats() { return this.command.stats }
|
||||
}
|
||||
resetStats() {
|
||||
return Object.assign(this.command.stats, { cpuTime: 0, gpuTime: 0, count: 0 })
|
||||
}
|
||||
stats() {
|
||||
return this.command.stats
|
||||
}
|
||||
}
|
||||
|
703
package-lock.json
generated
703
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -4,8 +4,11 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"serve": "parcel index.html --open --no-autoinstall",
|
||||
"deploy": "./deploy.sh"
|
||||
"deploy": "./deploy.sh",
|
||||
"prettier": "prettier --write './**/*.ts' './**/*.tsx'",
|
||||
"lint": "eslint './**/*.ts' './**/*.tsx'",
|
||||
"test": "npm run lint",
|
||||
"serve": "parcel index.html --open --no-autoinstall"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions",
|
||||
@ -15,10 +18,14 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aphrodite": "2.1.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"parcel-bundler": "1.7.0",
|
||||
"preact": "8.2.7",
|
||||
"prettier": "^1.12.0",
|
||||
"regl": "1.3.1",
|
||||
"typescript": "2.8.1",
|
||||
"typescript-eslint-parser": "^14.0.0",
|
||||
"uglify-es": "3.2.2"
|
||||
}
|
||||
}
|
||||
|
6
prettier.config.js
Normal file
6
prettier.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
56
profile.ts
56
profile.ts
@ -25,10 +25,18 @@ export interface FrameInfo {
|
||||
export class HasWeights {
|
||||
private selfWeight = 0
|
||||
private totalWeight = 0
|
||||
getSelfWeight() { return this.selfWeight }
|
||||
getTotalWeight() { return this.totalWeight }
|
||||
addToTotalWeight(delta: number) { this.totalWeight += delta }
|
||||
addToSelfWeight(delta: number) { this.selfWeight += delta }
|
||||
getSelfWeight() {
|
||||
return this.selfWeight
|
||||
}
|
||||
getTotalWeight() {
|
||||
return this.totalWeight
|
||||
}
|
||||
addToTotalWeight(delta: number) {
|
||||
this.totalWeight += delta
|
||||
}
|
||||
addToSelfWeight(delta: number) {
|
||||
this.selfWeight += delta
|
||||
}
|
||||
}
|
||||
|
||||
export class Frame extends HasWeights {
|
||||
@ -81,7 +89,7 @@ export class RawValueFormatter implements ValueFormatter {
|
||||
}
|
||||
|
||||
export class TimeFormatter implements ValueFormatter {
|
||||
private multiplier : number
|
||||
private multiplier: number
|
||||
|
||||
constructor(unit: 'nanoseconds' | 'microseconds' | 'milliseconds' | 'seconds') {
|
||||
if (unit === 'nanoseconds') this.multiplier = 1e-9
|
||||
@ -93,7 +101,7 @@ export class TimeFormatter implements ValueFormatter {
|
||||
format(v: number) {
|
||||
const s = v * this.multiplier
|
||||
|
||||
if (s / 1e0 >= 1) return `${s.toFixed(2)}s`
|
||||
if (s / 1 >= 1) return `${s.toFixed(2)}s`
|
||||
if (s / 1e-3 >= 1) return `${(s / 1e-3).toFixed(2)}ms`
|
||||
if (s / 1e-6 >= 1) return `${(s / 1e-6).toFixed(2)}µs`
|
||||
else return `${(s / 1e-9).toFixed(2)}ms`
|
||||
@ -120,20 +128,30 @@ export class Profile {
|
||||
this.totalWeight = totalWeight
|
||||
}
|
||||
|
||||
formatValue(v: number) { return this.valueFormatter.format(v) }
|
||||
setValueFormatter(f: ValueFormatter) { this.valueFormatter = f }
|
||||
formatValue(v: number) {
|
||||
return this.valueFormatter.format(v)
|
||||
}
|
||||
setValueFormatter(f: ValueFormatter) {
|
||||
this.valueFormatter = f
|
||||
}
|
||||
|
||||
getName() { return this.name }
|
||||
setName(name: string) { this.name = name }
|
||||
getName() {
|
||||
return this.name
|
||||
}
|
||||
setName(name: string) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
getTotalWeight() { return this.totalWeight }
|
||||
getTotalWeight() {
|
||||
return this.totalWeight
|
||||
}
|
||||
getTotalNonIdleWeight() {
|
||||
return this.groupedCalltreeRoot.children.reduce((n, c) => n + c.getTotalWeight(), 0)
|
||||
}
|
||||
|
||||
forEachCallGrouped(
|
||||
openFrame: (node: CallTreeNode, value: number) => void,
|
||||
closeFrame: (value: number) => void
|
||||
closeFrame: (value: number) => void,
|
||||
) {
|
||||
function visit(node: CallTreeNode, start: number) {
|
||||
if (node.frame !== rootFrame) {
|
||||
@ -143,9 +161,9 @@ export class Profile {
|
||||
let childTime = 0
|
||||
|
||||
const children = [...node.children]
|
||||
children.sort((a, b) => a.getTotalWeight() > b.getTotalWeight() ? -1 : 1)
|
||||
children.sort((a, b) => (a.getTotalWeight() > b.getTotalWeight() ? -1 : 1))
|
||||
|
||||
children.forEach(function (child) {
|
||||
children.forEach(function(child) {
|
||||
visit(child, start + childTime)
|
||||
childTime += child.getTotalWeight()
|
||||
})
|
||||
@ -159,7 +177,7 @@ export class Profile {
|
||||
|
||||
forEachCall(
|
||||
openFrame: (node: CallTreeNode, value: number) => void,
|
||||
closeFrame: (value: number) => void
|
||||
closeFrame: (value: number) => void,
|
||||
) {
|
||||
let prevStack: CallTreeNode[] = []
|
||||
let value = 0
|
||||
@ -220,7 +238,9 @@ export class Profile {
|
||||
|
||||
for (let frameInfo of stack) {
|
||||
const frame = getOrInsert(this.frames, frameInfo.key, () => new Frame(frameInfo))
|
||||
const last = useAppendOrder ? lastOf(node.children) : node.children.find(c => c.frame === frame)
|
||||
const last = useAppendOrder
|
||||
? lastOf(node.children)
|
||||
: node.children.find(c => c.frame === frame)
|
||||
if (last && last.frame == frame) {
|
||||
node = last
|
||||
} else {
|
||||
@ -303,7 +323,9 @@ export class Profile {
|
||||
}
|
||||
}
|
||||
|
||||
const last = useAppendOrder ? lastOf(prevTop.children) : prevTop.children.find(c => c.frame === frame)
|
||||
const last = useAppendOrder
|
||||
? lastOf(prevTop.children)
|
||||
: prevTop.children.find(c => c.frame === frame)
|
||||
let node: CallTreeNode
|
||||
if (last && last.frame == frame) {
|
||||
node = last
|
||||
|
@ -10,19 +10,23 @@ export class RectangleBatch {
|
||||
private configSpaceSizes = new Float32Array(this.rectCapacity * 2)
|
||||
private colors = new Float32Array(this.rectCapacity * 3)
|
||||
|
||||
constructor(private gl: regl.Instance) { }
|
||||
constructor(private gl: regl.Instance) {}
|
||||
|
||||
getRectCount() { return this.rectCount }
|
||||
getRectCount() {
|
||||
return this.rectCount
|
||||
}
|
||||
|
||||
private configSpaceOffsetBuffer: regl.Buffer | null = null
|
||||
getConfigSpaceOffsetBuffer() {
|
||||
if (!this.configSpaceOffsetBuffer) this.configSpaceOffsetBuffer = this.gl.buffer(this.configSpaceOffsets)
|
||||
if (!this.configSpaceOffsetBuffer)
|
||||
this.configSpaceOffsetBuffer = this.gl.buffer(this.configSpaceOffsets)
|
||||
return this.configSpaceOffsetBuffer
|
||||
}
|
||||
|
||||
private configSpaceSizeBuffer: regl.Buffer | null = null
|
||||
getConfigSpaceSizeBuffer() {
|
||||
if (!this.configSpaceSizeBuffer) this.configSpaceSizeBuffer = this.gl.buffer(this.configSpaceSizes)
|
||||
if (!this.configSpaceSizeBuffer)
|
||||
this.configSpaceSizeBuffer = this.gl.buffer(this.configSpaceSizes)
|
||||
return this.configSpaceSizeBuffer
|
||||
}
|
||||
|
||||
@ -105,11 +109,11 @@ export class RectangleBatchRenderer {
|
||||
}
|
||||
`,
|
||||
|
||||
depth: {
|
||||
enable: false
|
||||
},
|
||||
depth: {
|
||||
enable: false,
|
||||
},
|
||||
|
||||
frag: `
|
||||
frag: `
|
||||
precision mediump float;
|
||||
varying vec3 vColor;
|
||||
varying float vParity;
|
||||
@ -121,12 +125,7 @@ export class RectangleBatchRenderer {
|
||||
|
||||
attributes: {
|
||||
// Non-instanced attributes
|
||||
corner: gl.buffer([
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
]),
|
||||
corner: gl.buffer([[0, 0], [1, 0], [0, 1], [1, 1]]),
|
||||
|
||||
// Instanced attributes
|
||||
configSpaceOffset: (context, props) => {
|
||||
@ -135,7 +134,7 @@ export class RectangleBatchRenderer {
|
||||
offset: 0,
|
||||
stride: 2 * 4,
|
||||
size: 2,
|
||||
divisor: 1
|
||||
divisor: 1,
|
||||
}
|
||||
},
|
||||
configSpaceSize: (context, props) => {
|
||||
@ -144,7 +143,7 @@ export class RectangleBatchRenderer {
|
||||
offset: 0,
|
||||
stride: 2 * 4,
|
||||
size: 2,
|
||||
divisor: 1
|
||||
divisor: 1,
|
||||
}
|
||||
},
|
||||
color: (context, props) => {
|
||||
@ -153,22 +152,23 @@ export class RectangleBatchRenderer {
|
||||
offset: 0,
|
||||
stride: 3 * 4,
|
||||
size: 3,
|
||||
divisor: 1
|
||||
divisor: 1,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
uniforms: {
|
||||
configSpaceToNDC: (context, props) => {
|
||||
const configToPhysical = AffineTransform.betweenRects(
|
||||
props.configSpaceSrcRect,
|
||||
props.physicalSpaceDstRect
|
||||
props.physicalSpaceDstRect,
|
||||
)
|
||||
|
||||
const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight)
|
||||
|
||||
const physicalToNDC = AffineTransform.withTranslation(new Vec2(-1, 1))
|
||||
.times(AffineTransform.withScale(new Vec2(2, -2).dividedByPointwise(viewportSize)))
|
||||
const physicalToNDC = AffineTransform.withTranslation(new Vec2(-1, 1)).times(
|
||||
AffineTransform.withScale(new Vec2(2, -2).dividedByPointwise(viewportSize)),
|
||||
)
|
||||
|
||||
return physicalToNDC.times(configToPhysical).flatten()
|
||||
},
|
||||
@ -179,7 +179,7 @@ export class RectangleBatchRenderer {
|
||||
|
||||
parityMin: (context, props) => {
|
||||
return props.parityMin == null ? 0 : 1 + props.parityMin
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
instances: (context, props) => {
|
||||
@ -196,6 +196,10 @@ export class RectangleBatchRenderer {
|
||||
this.command(props)
|
||||
}
|
||||
|
||||
resetStats() { return Object.assign(this.command.stats, { cpuTime: 0, gpuTime: 0, count: 0 }) }
|
||||
stats() { return this.command.stats }
|
||||
}
|
||||
resetStats() {
|
||||
return Object.assign(this.command.stats, { cpuTime: 0, gpuTime: 0, count: 0 })
|
||||
}
|
||||
stats() {
|
||||
return this.command.stats
|
||||
}
|
||||
}
|
||||
|
315
regl.d.ts
vendored
315
regl.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
declare module "regl" {
|
||||
declare module 'regl' {
|
||||
interface InitializationOptions {
|
||||
/** A reference to a WebGL rendering context. (Default created from canvas) */
|
||||
gl?: WebGLRenderingContext
|
||||
@ -57,7 +57,24 @@ declare module "regl" {
|
||||
export type vec3 = [number, number, number]
|
||||
export type vec4 = [number, number, number, number]
|
||||
export type mat3 = [number, number, number, number, number, number, number, number, number]
|
||||
export type mat4 = [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]
|
||||
export type mat4 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number
|
||||
]
|
||||
type GlslPrimitive = number | vec2 | vec3 | vec4 | mat3 | mat4
|
||||
|
||||
interface Tick {
|
||||
@ -68,9 +85,9 @@ declare module "regl" {
|
||||
<P>(params: CommandOptions<P>): Command<P>
|
||||
|
||||
clear(args: {
|
||||
color?: [number, number, number, number],
|
||||
depth?: number,
|
||||
stencil?: number,
|
||||
color?: [number, number, number, number]
|
||||
depth?: number
|
||||
stencil?: number
|
||||
}): void
|
||||
|
||||
// TODO(jlfwong): read()
|
||||
@ -127,10 +144,25 @@ declare module "regl" {
|
||||
frame(callback: (context: Context) => void): Tick
|
||||
}
|
||||
|
||||
type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array |
|
||||
Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array
|
||||
type TypedArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
|
||||
type DrawMode = 'points' | 'lines' | 'line strip' | 'line loop' | 'triangles' | 'triangle strip' | 'triangle fan'
|
||||
type DrawMode =
|
||||
| 'points'
|
||||
| 'lines'
|
||||
| 'line strip'
|
||||
| 'line loop'
|
||||
| 'triangles'
|
||||
| 'triangle strip'
|
||||
| 'triangle fan'
|
||||
|
||||
interface Context {
|
||||
tick: number
|
||||
@ -177,14 +209,49 @@ declare module "regl" {
|
||||
}
|
||||
|
||||
type MagFilter = 'nearest' | 'linear'
|
||||
type MinFilter = 'nearest' | 'linear' | 'mipmap' | 'linear mipmap linear' | 'nearest mipmap linear' | 'nearest mipmap nearest'
|
||||
type MinFilter =
|
||||
| 'nearest'
|
||||
| 'linear'
|
||||
| 'mipmap'
|
||||
| 'linear mipmap linear'
|
||||
| 'nearest mipmap linear'
|
||||
| 'nearest mipmap nearest'
|
||||
type WrapMode = 'repeat' | 'clamp' | 'mirror'
|
||||
type TextureFormat = 'alpha' | 'luminance' | 'luminance alpha' | 'rgb' | 'rgba' | 'rgba4' | 'rgb5 a1' | 'rgb565' | 'srgb' | 'srgba' | 'depth' | 'depth stencil' | 'rgb s3tc dxt1' | 'rgb s3tc dxt5' | 'rgb atc' | 'rgba atc explicit alpha' | 'rgba atc interpolated alpha' | 'rgb pvrtc 4bppv1' | 'rgb pvrtc 2bppv1' | 'rgba pvrtc 4bppv1' | 'rgba pvrtc 2bppv1' | 'rgb etc1'
|
||||
type TextureFormat =
|
||||
| 'alpha'
|
||||
| 'luminance'
|
||||
| 'luminance alpha'
|
||||
| 'rgb'
|
||||
| 'rgba'
|
||||
| 'rgba4'
|
||||
| 'rgb5 a1'
|
||||
| 'rgb565'
|
||||
| 'srgb'
|
||||
| 'srgba'
|
||||
| 'depth'
|
||||
| 'depth stencil'
|
||||
| 'rgb s3tc dxt1'
|
||||
| 'rgb s3tc dxt5'
|
||||
| 'rgb atc'
|
||||
| 'rgba atc explicit alpha'
|
||||
| 'rgba atc interpolated alpha'
|
||||
| 'rgb pvrtc 4bppv1'
|
||||
| 'rgb pvrtc 2bppv1'
|
||||
| 'rgba pvrtc 4bppv1'
|
||||
| 'rgba pvrtc 2bppv1'
|
||||
| 'rgb etc1'
|
||||
type TextureType = 'uint8' | 'uint16' | 'float' | 'float32' | 'half float' | 'float16'
|
||||
type ColorSpace = 'none' | 'browser'
|
||||
type MipmapHint = "don't care" | 'dont care' | 'nice' | 'fast'
|
||||
|
||||
type TextureData = number[] | number[][] | TypedArray | HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | CanvasRenderingContext2D
|
||||
type TextureData =
|
||||
| number[]
|
||||
| number[][]
|
||||
| TypedArray
|
||||
| HTMLImageElement
|
||||
| HTMLVideoElement
|
||||
| HTMLCanvasElement
|
||||
| CanvasRenderingContext2D
|
||||
interface TextureOptions {
|
||||
width?: number
|
||||
height?: number
|
||||
@ -233,7 +300,17 @@ declare module "regl" {
|
||||
// TODO(jlfwong): Cubic frame buffers
|
||||
|
||||
interface RenderBufferOptions {
|
||||
format?: 'rgba4' | 'rgb565' | 'rgb5 a1' | 'depth' | 'stencil' | 'depth stencil' | 'srgba' | 'rgba16f' | 'rgb16f' | 'rgba32f'
|
||||
format?:
|
||||
| 'rgba4'
|
||||
| 'rgb565'
|
||||
| 'rgb5 a1'
|
||||
| 'depth'
|
||||
| 'stencil'
|
||||
| 'depth stencil'
|
||||
| 'srgba'
|
||||
| 'rgba16f'
|
||||
| 'rgb16f'
|
||||
| 'rgba32f'
|
||||
width?: number
|
||||
height?: number
|
||||
shape?: [number, number]
|
||||
@ -256,7 +333,15 @@ declare module "regl" {
|
||||
depth?: boolean | RenderBuffer | Texture
|
||||
stencil?: boolean | RenderBuffer | Texture
|
||||
depthStencil?: boolean | RenderBuffer | Texture
|
||||
colorFormat?: 'rgba' | 'rgba4' | 'rgb565' | 'rgb5 a1' | 'rgb16f' | 'rgba16f' | 'rgba32f' | 'srgba'
|
||||
colorFormat?:
|
||||
| 'rgba'
|
||||
| 'rgba4'
|
||||
| 'rgb565'
|
||||
| 'rgb5 a1'
|
||||
| 'rgb16f'
|
||||
| 'rgba16f'
|
||||
| 'rgba32f'
|
||||
| 'srgba'
|
||||
colorType?: 'uint8' | 'half float' | 'float'
|
||||
}
|
||||
interface Framebuffer {
|
||||
@ -281,18 +366,60 @@ declare module "regl" {
|
||||
size?: number
|
||||
divisor?: number
|
||||
}
|
||||
type Attribute = AttributeOptions | Buffer | BufferArgs | { constant: number | vec2 | vec3 | vec4 | mat3 | mat4 }
|
||||
type Attribute =
|
||||
| AttributeOptions
|
||||
| Buffer
|
||||
| BufferArgs
|
||||
| { constant: number | vec2 | vec3 | vec4 | mat3 | mat4 }
|
||||
|
||||
interface Computed<P, T> {
|
||||
(context: Context, props: P, batchId: number): T
|
||||
}
|
||||
type MaybeComputed<P, T> = Computed<P, T> | T
|
||||
|
||||
type DepthFunction = 'never' | 'always' | '<' | 'less' | '<=' | 'lequal' | '>' | 'greater' | '>=' | 'gequal' | '=' | 'equal' | '!=' | 'notequal'
|
||||
type BlendFunction = 0 | 'zero' | 1 | 'one' | 'src color' | 'one minus src color' | 'src alpha' | 'one minus src alpha' | 'dst color' | 'one minus dst color' | 'dst alpha' | 'one minus dst alpha' | 'constant color' | 'one minus constant color' | 'one minus constant alpha' | 'src alpha saturate'
|
||||
type DepthFunction =
|
||||
| 'never'
|
||||
| 'always'
|
||||
| '<'
|
||||
| 'less'
|
||||
| '<='
|
||||
| 'lequal'
|
||||
| '>'
|
||||
| 'greater'
|
||||
| '>='
|
||||
| 'gequal'
|
||||
| '='
|
||||
| 'equal'
|
||||
| '!='
|
||||
| 'notequal'
|
||||
type BlendFunction =
|
||||
| 0
|
||||
| 'zero'
|
||||
| 1
|
||||
| 'one'
|
||||
| 'src color'
|
||||
| 'one minus src color'
|
||||
| 'src alpha'
|
||||
| 'one minus src alpha'
|
||||
| 'dst color'
|
||||
| 'one minus dst color'
|
||||
| 'dst alpha'
|
||||
| 'one minus dst alpha'
|
||||
| 'constant color'
|
||||
| 'one minus constant color'
|
||||
| 'one minus constant alpha'
|
||||
| 'src alpha saturate'
|
||||
type BlendEquation = 'add' | 'subtract' | 'reverse subtract' | 'min' | 'max'
|
||||
type StencilFunction = DepthFunction
|
||||
type StencilOp = 'zero' | 'keep' | 'replace' | 'invert' | 'increment' | 'decrement' | 'increment wrap' | 'decrement wrap'
|
||||
type StencilOp =
|
||||
| 'zero'
|
||||
| 'keep'
|
||||
| 'replace'
|
||||
| 'invert'
|
||||
| 'increment'
|
||||
| 'decrement'
|
||||
| 'increment wrap'
|
||||
| 'decrement wrap'
|
||||
interface CommandOptions<P> {
|
||||
/** Source code of vertex shader */
|
||||
vert?: string
|
||||
@ -324,51 +451,70 @@ declare module "regl" {
|
||||
|
||||
profile?: MaybeComputed<P, boolean>
|
||||
|
||||
depth?: MaybeComputed<P, {
|
||||
enable?: boolean,
|
||||
mask?: boolean,
|
||||
func?: DepthFunction,
|
||||
range?: [number, number]
|
||||
}>
|
||||
|
||||
blend?: MaybeComputed<P, {
|
||||
enable?: boolean,
|
||||
func?: {
|
||||
src: BlendFunction
|
||||
dst: BlendFunction
|
||||
} | {
|
||||
srcRGB: BlendFunction
|
||||
srcAlpha: BlendFunction
|
||||
dstRGB: BlendFunction
|
||||
dstAlpha: BlendFunction
|
||||
},
|
||||
equation?: BlendEquation | {
|
||||
rgb: BlendEquation
|
||||
alpha: BlendEquation
|
||||
},
|
||||
color?: vec4
|
||||
}>
|
||||
|
||||
stencil?: MaybeComputed<P, {
|
||||
enable?: boolean
|
||||
mask?: number
|
||||
func?: StencilFunction
|
||||
opFront?: { fail: StencilOp, zfail: StencilOp, pass: StencilOp },
|
||||
opBack?: { fail: StencilOp, zfail: StencilOp, pass: StencilOp },
|
||||
}>
|
||||
|
||||
polygonOffset?: MaybeComputed<P, {
|
||||
enable?: boolean
|
||||
offset?: {
|
||||
factor: number
|
||||
units: number
|
||||
depth?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
mask?: boolean
|
||||
func?: DepthFunction
|
||||
range?: [number, number]
|
||||
}
|
||||
}>
|
||||
>
|
||||
|
||||
cull?: MaybeComputed<P, {
|
||||
enable?: boolean
|
||||
face?: 'front' | 'back'
|
||||
}>
|
||||
blend?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
func?:
|
||||
| {
|
||||
src: BlendFunction
|
||||
dst: BlendFunction
|
||||
}
|
||||
| {
|
||||
srcRGB: BlendFunction
|
||||
srcAlpha: BlendFunction
|
||||
dstRGB: BlendFunction
|
||||
dstAlpha: BlendFunction
|
||||
}
|
||||
equation?:
|
||||
| BlendEquation
|
||||
| {
|
||||
rgb: BlendEquation
|
||||
alpha: BlendEquation
|
||||
}
|
||||
color?: vec4
|
||||
}
|
||||
>
|
||||
|
||||
stencil?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
mask?: number
|
||||
func?: StencilFunction
|
||||
opFront?: { fail: StencilOp; zfail: StencilOp; pass: StencilOp }
|
||||
opBack?: { fail: StencilOp; zfail: StencilOp; pass: StencilOp }
|
||||
}
|
||||
>
|
||||
|
||||
polygonOffset?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
offset?: {
|
||||
factor: number
|
||||
units: number
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
cull?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
face?: 'front' | 'back'
|
||||
}
|
||||
>
|
||||
|
||||
frontFace?: MaybeComputed<P, 'cw' | 'ccw'>
|
||||
|
||||
@ -378,35 +524,46 @@ declare module "regl" {
|
||||
|
||||
colorMask?: MaybeComputed<P, [boolean, boolean, boolean, boolean]>
|
||||
|
||||
sample?: MaybeComputed<P, {
|
||||
enable?: boolean
|
||||
alpha?: boolean
|
||||
coverage?: {
|
||||
value: number
|
||||
invert: boolean
|
||||
sample?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
alpha?: boolean
|
||||
coverage?: {
|
||||
value: number
|
||||
invert: boolean
|
||||
}
|
||||
}
|
||||
}>
|
||||
>
|
||||
|
||||
scissor?: MaybeComputed<P, {
|
||||
enable?: boolean
|
||||
box?: {
|
||||
scissor?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
enable?: boolean
|
||||
box?: {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
}
|
||||
>
|
||||
|
||||
viewport?: MaybeComputed<
|
||||
P,
|
||||
{
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
}>
|
||||
|
||||
viewport?: MaybeComputed<P, {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
}>
|
||||
>
|
||||
}
|
||||
|
||||
function prop<P>(name: keyof P): (context: Context, props: P, batchId: number) => P[keyof P]
|
||||
function context<P>(name: keyof Context): (context: Context, props: P, batchId: number) => Context[keyof Context]
|
||||
function context<P>(
|
||||
name: keyof Context,
|
||||
): (context: Context, props: P, batchId: number) => Context[keyof Context]
|
||||
|
||||
interface Command<P> {
|
||||
/** One shot rendering */
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {Component} from 'preact'
|
||||
import { Component } from 'preact'
|
||||
|
||||
export interface SerializedComponent<S> {
|
||||
state: S
|
||||
serializedSubcomponents: {[key: string]: any}
|
||||
serializedSubcomponents: { [key: string]: any }
|
||||
}
|
||||
|
||||
export abstract class ReloadableComponent<P, S> extends Component<P, S> {
|
||||
serialize(): SerializedComponent<S> {
|
||||
const serializedSubcomponents: {[key: string]: any} = Object.create(null)
|
||||
const serializedSubcomponents: { [key: string]: any } = Object.create(null)
|
||||
|
||||
const subcomponents = this.subcomponents()
|
||||
for (const key in subcomponents) {
|
||||
@ -34,9 +34,7 @@ export abstract class ReloadableComponent<P, S> extends Component<P, S> {
|
||||
}
|
||||
})
|
||||
}
|
||||
subcomponents(): {[key: string]: any} {
|
||||
subcomponents(): { [key: string]: any } {
|
||||
return Object.create(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {h, render} from 'preact'
|
||||
import {Application} from'./application'
|
||||
import { h, render } from 'preact'
|
||||
import { Application } from './application'
|
||||
|
||||
let app: Application | null = null
|
||||
const retained = (window as any)['__retained__'] as any
|
||||
@ -7,7 +7,7 @@ declare const module: any
|
||||
if (module.hot) {
|
||||
module.hot.dispose(() => {
|
||||
if (app) {
|
||||
(window as any)['__retained__'] = app.serialize()
|
||||
;(window as any)['__retained__'] = app.serialize()
|
||||
}
|
||||
})
|
||||
module.hot.accept()
|
||||
@ -21,4 +21,4 @@ function ref(instance: Application | null) {
|
||||
}
|
||||
}
|
||||
|
||||
render(<Application ref={ref}/>, document.body, document.body.lastElementChild || undefined)
|
||||
render(<Application ref={ref} />, document.body, document.body.lastElementChild || undefined)
|
||||
|
101
stats.ts
101
stats.ts
@ -35,33 +35,32 @@ export class StatsPanel {
|
||||
|
||||
showPanel(id: number) {
|
||||
for (var i = 0; i < this.container.children.length; i++) {
|
||||
(this.container.children[i] as HTMLElement).style.display = i === id ? 'block' : 'none';
|
||||
;(this.container.children[i] as HTMLElement).style.display = i === id ? 'block' : 'none'
|
||||
}
|
||||
this.shown = id;
|
||||
this.shown = id
|
||||
}
|
||||
|
||||
|
||||
private beginTime: number = 0
|
||||
begin() {
|
||||
this.beginTime = ( performance || Date ).now();
|
||||
this.beginTime = (performance || Date).now()
|
||||
}
|
||||
|
||||
private frames = 0
|
||||
private prevTime = 0
|
||||
end() {
|
||||
this.frames++;
|
||||
var time = ( performance || Date ).now();
|
||||
this.msPanel.update(time - this.beginTime, 200);
|
||||
this.frames++
|
||||
var time = (performance || Date).now()
|
||||
this.msPanel.update(time - this.beginTime, 200)
|
||||
|
||||
if ( time >= this.prevTime + 1000 ) {
|
||||
this.fpsPanel.update(( this.frames * 1000 ) / ( time - this.prevTime ), 100);
|
||||
this.prevTime = time;
|
||||
this.frames = 0;
|
||||
if (time >= this.prevTime + 1000) {
|
||||
this.fpsPanel.update(this.frames * 1000 / (time - this.prevTime), 100)
|
||||
this.prevTime = time
|
||||
this.frames = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PR = Math.round( window.devicePixelRatio || 1 );
|
||||
const PR = Math.round(window.devicePixelRatio || 1)
|
||||
|
||||
class Panel {
|
||||
private min: number = Infinity
|
||||
@ -75,23 +74,23 @@ class Panel {
|
||||
private GRAPH_X = 3 * PR
|
||||
private GRAPH_Y = 15 * PR
|
||||
private GRAPH_WIDTH = 74 * PR
|
||||
private GRAPH_HEIGHT = 30 * PR;
|
||||
private GRAPH_HEIGHT = 30 * PR
|
||||
|
||||
constructor(private name: string, private fg: string, private bg: string) {
|
||||
this.canvas.width = this.WIDTH;
|
||||
this.canvas.height = this.HEIGHT;
|
||||
this.canvas.style.cssText = 'width:80px;height:48px';
|
||||
this.canvas.width = this.WIDTH
|
||||
this.canvas.height = this.HEIGHT
|
||||
this.canvas.style.cssText = 'width:80px;height:48px'
|
||||
|
||||
this.context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
|
||||
this.context.textBaseline = 'top';
|
||||
this.context.fillStyle = bg;
|
||||
this.context.fillRect( 0, 0, this.WIDTH, this.HEIGHT );
|
||||
this.context.fillStyle = fg;
|
||||
this.context.font = 'bold ' + 9 * PR + 'px Helvetica,Arial,sans-serif'
|
||||
this.context.textBaseline = 'top'
|
||||
this.context.fillStyle = bg
|
||||
this.context.fillRect(0, 0, this.WIDTH, this.HEIGHT)
|
||||
this.context.fillStyle = fg
|
||||
this.context.fillText(this.name, this.TEXT_X, this.TEXT_Y)
|
||||
this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT);
|
||||
this.context.fillStyle = bg;
|
||||
this.context.globalAlpha = 0.9;
|
||||
this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT);
|
||||
this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT)
|
||||
this.context.fillStyle = bg
|
||||
this.context.globalAlpha = 0.9
|
||||
this.context.fillRect(this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH, this.GRAPH_HEIGHT)
|
||||
}
|
||||
|
||||
appendTo(el: HTMLElement) {
|
||||
@ -99,18 +98,44 @@ class Panel {
|
||||
}
|
||||
|
||||
update(value: number, maxValue: number) {
|
||||
this.min = Math.min(this.min, value );
|
||||
this.max = Math.max(this.max, value );
|
||||
this.min = Math.min(this.min, value)
|
||||
this.max = Math.max(this.max, value)
|
||||
|
||||
this.context.fillStyle = this.bg;
|
||||
this.context.globalAlpha = 1;
|
||||
this.context.fillRect( 0, 0, this.WIDTH, this.GRAPH_Y );
|
||||
this.context.fillStyle = this.fg;
|
||||
this.context.fillText( Math.round( value ) + ' ' + name + ' (' + Math.round( this.min ) + '-' + Math.round( this.max ) + ')', this.TEXT_X, this.TEXT_Y );
|
||||
this.context.drawImage( this.canvas, this.GRAPH_X + PR, this.GRAPH_Y, this.GRAPH_WIDTH - PR, this.GRAPH_HEIGHT, this.GRAPH_X, this.GRAPH_Y, this.GRAPH_WIDTH - PR, this.GRAPH_HEIGHT );
|
||||
this.context.fillRect( this.GRAPH_X + this.GRAPH_WIDTH - PR, this.GRAPH_Y, PR, this.GRAPH_HEIGHT );
|
||||
this.context.fillStyle = this.bg;
|
||||
this.context.globalAlpha = 0.9;
|
||||
this.context.fillRect( this.GRAPH_X + this.GRAPH_WIDTH - PR, this.GRAPH_Y, PR, Math.round( ( 1 - ( value / maxValue ) ) * this.GRAPH_HEIGHT ) );
|
||||
this.context.fillStyle = this.bg
|
||||
this.context.globalAlpha = 1
|
||||
this.context.fillRect(0, 0, this.WIDTH, this.GRAPH_Y)
|
||||
this.context.fillStyle = this.fg
|
||||
this.context.fillText(
|
||||
Math.round(value) +
|
||||
' ' +
|
||||
name +
|
||||
' (' +
|
||||
Math.round(this.min) +
|
||||
'-' +
|
||||
Math.round(this.max) +
|
||||
')',
|
||||
this.TEXT_X,
|
||||
this.TEXT_Y,
|
||||
)
|
||||
this.context.drawImage(
|
||||
this.canvas,
|
||||
this.GRAPH_X + PR,
|
||||
this.GRAPH_Y,
|
||||
this.GRAPH_WIDTH - PR,
|
||||
this.GRAPH_HEIGHT,
|
||||
this.GRAPH_X,
|
||||
this.GRAPH_Y,
|
||||
this.GRAPH_WIDTH - PR,
|
||||
this.GRAPH_HEIGHT,
|
||||
)
|
||||
this.context.fillRect(this.GRAPH_X + this.GRAPH_WIDTH - PR, this.GRAPH_Y, PR, this.GRAPH_HEIGHT)
|
||||
this.context.fillStyle = this.bg
|
||||
this.context.globalAlpha = 0.9
|
||||
this.context.fillRect(
|
||||
this.GRAPH_X + this.GRAPH_WIDTH - PR,
|
||||
this.GRAPH_Y,
|
||||
PR,
|
||||
Math.round((1 - value / maxValue) * this.GRAPH_HEIGHT),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
style.ts
10
style.ts
@ -1,18 +1,18 @@
|
||||
export enum FontFamily {
|
||||
MONOSPACE = "Courier, monospace"
|
||||
MONOSPACE = 'Courier, monospace',
|
||||
}
|
||||
|
||||
export enum FontSize {
|
||||
LABEL = 10,
|
||||
TITLE = 12,
|
||||
BIG_BUTTON = 36
|
||||
BIG_BUTTON = 36,
|
||||
}
|
||||
|
||||
export enum Colors {
|
||||
LIGHT_GRAY = "#C4C4C4",
|
||||
MEDIUM_GRAY = "#BDBDBD",
|
||||
LIGHT_GRAY = '#C4C4C4',
|
||||
MEDIUM_GRAY = '#BDBDBD',
|
||||
GRAY = '#666666',
|
||||
DARK_GRAY = '#222222',
|
||||
LIGHT_BLUE = '#56CCF2',
|
||||
DARK_BLUE = '#2F80ED'
|
||||
DARK_BLUE = '#2F80ED',
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export class TextureRenderer {
|
||||
`,
|
||||
|
||||
depth: {
|
||||
enable: false
|
||||
enable: false,
|
||||
},
|
||||
|
||||
attributes: {
|
||||
@ -48,18 +48,8 @@ export class TextureRenderer {
|
||||
// | /|
|
||||
// |/ |
|
||||
// 2 +--+ 3
|
||||
position: gl.buffer([
|
||||
[-1, 1],
|
||||
[1, 1],
|
||||
[-1, -1],
|
||||
[1, -1]
|
||||
]),
|
||||
uv: gl.buffer([
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
[0, 0],
|
||||
[1, 0]
|
||||
])
|
||||
position: gl.buffer([[-1, 1], [1, 1], [-1, -1], [1, -1]]),
|
||||
uv: gl.buffer([[0, 1], [1, 1], [0, 0], [1, 0]]),
|
||||
},
|
||||
|
||||
uniforms: {
|
||||
@ -68,34 +58,31 @@ export class TextureRenderer {
|
||||
const { srcRect, texture } = props
|
||||
const physicalToUV = AffineTransform.withTranslation(new Vec2(0, 1))
|
||||
.times(AffineTransform.withScale(new Vec2(1, -1)))
|
||||
.times(AffineTransform.betweenRects(
|
||||
.times(
|
||||
AffineTransform.betweenRects(
|
||||
new Rect(Vec2.zero, new Vec2(texture.width, texture.height)),
|
||||
Rect.unit
|
||||
))
|
||||
Rect.unit,
|
||||
),
|
||||
)
|
||||
const uvRect = physicalToUV.transformRect(srcRect)
|
||||
return AffineTransform.betweenRects(
|
||||
Rect.unit,
|
||||
uvRect,
|
||||
).flatten()
|
||||
return AffineTransform.betweenRects(Rect.unit, uvRect).flatten()
|
||||
},
|
||||
positionTransform: (context, props) => {
|
||||
const { dstRect } = props
|
||||
|
||||
const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight)
|
||||
|
||||
const physicalToNDC = AffineTransform.withScale(new Vec2(1, -1))
|
||||
.times(AffineTransform.betweenRects(
|
||||
new Rect(Vec2.zero, viewportSize),
|
||||
Rect.NDC)
|
||||
)
|
||||
const physicalToNDC = AffineTransform.withScale(new Vec2(1, -1)).times(
|
||||
AffineTransform.betweenRects(new Rect(Vec2.zero, viewportSize), Rect.NDC),
|
||||
)
|
||||
const ndcRect = physicalToNDC.transformRect(dstRect)
|
||||
return AffineTransform.betweenRects(Rect.NDC, ndcRect).flatten()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
primitive: 'triangle strip',
|
||||
|
||||
count: 4
|
||||
count: 4,
|
||||
})
|
||||
}
|
||||
|
||||
@ -103,8 +90,12 @@ export class TextureRenderer {
|
||||
this.command(props)
|
||||
}
|
||||
|
||||
resetStats() { return Object.assign(this.command.stats, { cpuTime: 0, gpuTime: 0, count: 0 }) }
|
||||
stats() { return this.command.stats }
|
||||
resetStats() {
|
||||
return Object.assign(this.command.stats, { cpuTime: 0, gpuTime: 0, count: 0 })
|
||||
}
|
||||
stats() {
|
||||
return this.command.stats
|
||||
}
|
||||
}
|
||||
|
||||
export interface TextureCachedRendererOptions<T> {
|
||||
@ -127,7 +118,7 @@ export class TextureCachedRenderer<T> {
|
||||
this.textureRenderer = options.textureRenderer
|
||||
|
||||
this.texture = gl.texture(1, 1)
|
||||
this.framebuffer = gl.framebuffer({color: [this.texture]})
|
||||
this.framebuffer = gl.framebuffer({ color: [this.texture] })
|
||||
this.withContext = gl({})
|
||||
}
|
||||
|
||||
@ -141,7 +132,10 @@ export class TextureCachedRenderer<T> {
|
||||
render(props: T) {
|
||||
this.withContext((context: regl.Context) => {
|
||||
let needsRender = false
|
||||
if (this.texture.width !== context.viewportWidth || this.texture.height !== context.viewportHeight) {
|
||||
if (
|
||||
this.texture.width !== context.viewportWidth ||
|
||||
this.texture.height !== context.viewportHeight
|
||||
) {
|
||||
// TODO(jlfwong): Can probably just use this.framebuffer.resize
|
||||
this.texture({ width: context.viewportWidth, height: context.viewportHeight })
|
||||
this.framebuffer({ color: [this.texture] })
|
||||
@ -161,26 +155,29 @@ export class TextureCachedRenderer<T> {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: context.viewportWidth,
|
||||
height: context.viewportHeight
|
||||
height: context.viewportHeight,
|
||||
}
|
||||
},
|
||||
framebuffer: this.framebuffer
|
||||
framebuffer: this.framebuffer,
|
||||
})(() => {
|
||||
this.gl.clear({color: [0, 0, 0, 0]})
|
||||
this.gl.clear({ color: [0, 0, 0, 0] })
|
||||
this.renderUncached(props)
|
||||
})
|
||||
}
|
||||
|
||||
const glViewportRect = new Rect(Vec2.zero, new Vec2(context.viewportWidth, context.viewportHeight))
|
||||
const glViewportRect = new Rect(
|
||||
Vec2.zero,
|
||||
new Vec2(context.viewportWidth, context.viewportHeight),
|
||||
)
|
||||
|
||||
// Render from texture
|
||||
this.textureRenderer.render({
|
||||
texture: this.texture,
|
||||
srcRect: glViewportRect,
|
||||
dstRect: glViewportRect
|
||||
dstRect: glViewportRect,
|
||||
})
|
||||
this.lastRenderProps = props
|
||||
this.dirty = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
utils.ts
8
utils.ts
@ -1,5 +1,5 @@
|
||||
export function lastOf<T>(ts: T[]): T | null {
|
||||
return ts[ts.length-1] || null
|
||||
return ts[ts.length - 1] || null
|
||||
}
|
||||
|
||||
export function sortBy<T>(ts: T[], key: (t: T) => number | string): void {
|
||||
@ -26,7 +26,9 @@ export function* itMap<T, U>(it: Iterable<T>, f: (t: T) => U): Iterable<U> {
|
||||
}
|
||||
|
||||
export function itForEach<T>(it: Iterable<T>, f: (t: T) => void): void {
|
||||
for (let t of it) { f(t) }
|
||||
for (let t of it) {
|
||||
f(t)
|
||||
}
|
||||
}
|
||||
|
||||
export function itReduce<T, U>(it: Iterable<T>, f: (a: U, b: T) => U, init: U): U {
|
||||
@ -44,4 +46,4 @@ export function cachedMeasureTextWidth(ctx: CanvasRenderingContext2D, text: stri
|
||||
measureTextCache.set(text, ctx.measureText(text).width)
|
||||
}
|
||||
return measureTextCache.get(text)!
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user