vr-super-stats
v1.4.1
Published
Modern stats component for A-Frame WebXR projects that shows stats while in VR itself
Downloads
1
Maintainers
Readme
features
- get stats component data in VR, while you're actually using your app
- high performance, just one canvas/image draw-call texture for all stats (though each graph is its own canvas/image/draw-call, if you include those)
- uses existing stats component under the hood, so same numbers, no re-inventing the wheel
- pick which stats you want to track, reduce clutter
- throttle to as smooth or as performance sensitive as you want
- pick background color, including optional opacity
- include some, all, or no graphs
- attaches to camera by default, but can attach anywhere in-scene you want
- default behavior is to display when enter-vr, and hide and show
stats
when exit-vr, but behavior can be specified with options - set targets for maximum or minimum stats values, which will cause numbers to be red or green accordingly
- by default, shows all stats and graphs and has some opacity, and some default target values for the major stats
- if you prefer the lightest weight option instead, just set
performancemode='true;'
andshowlabels:fps,raf;
(or exactly whatever stats you want to track). - track live performance and view in-VR reports on caverage/high/low within sample period at a sample rate you determine
- helper components for activating on events (e.g.
buttondown
event fromtracked-controller
)
yet another necro component pulled into service
I've wanted this for a while, but I googled, found this, and then found a library that used to do what I wanted 5 years ago (and hadn't been touched since) in an older version of A-Frame. I've spent some time--arguably too much time--almost completely rewriting it, improving it, making it faster, lighter, and adding features.
You can access it through jsdelivr's cdn here: https://cdn.jsdelivr.net/gh/kylebakerio/[email protected]/vr-super-stats.js
Installation
Browser
Install and use by directly including the browser file:
<head>
<title>My A-Frame Scene</title>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/kylebakerio/[email protected]/vr-super-stats.js"></script>
</head>
<body>
<a-scene vr-super-statsr></a-scene>
</body>
Usage Examples
default behavior:
when you enter VR, full stats get attached to your face, about half a meter down and forward from you. When you are not in VR, you see the normal 2d stats.
<a-scene vr-super-stats></a-scene>
just want fps and triangles and raf, and graphs for only the first two
<a-scene vr-super-stats="showlabels:fps,raf,triangles; showgraphs:fps,raf"></a-scene>
no graphs, just numbers please
takes up less space and reduces overhead
<a-scene vr-super-stats="showgraphs:null;"></a-scene>
high performance mode defaults?
bare minimum makes for the lighest tick, producing the purest readings possible
<a-scene vr-super-stats="performancemode:true;"></a-scene>
no targets
improves performance
<a-scene vr-super-stats="targetmax:{};targetmin:{}"></a-scene>
custom targets
shoot high, or shoot low, based on your platform
<a-scene vr-super-stats='targetmin:{"fps":59};targetmax:{"raf":30}'></a-scene>
only fps graph, but all numbers
since all labels and graphs are enabled by default, this overrides this existing graph list
<a-scene vr-super-stats="showgraphs:fps;"></a-scene>
advanced examples
enable default auto-report (600 ticks after enter-vr, display for 30 seconds before disappearing)
runs and displays a report on stats collected from 600 ticks
<a-scene vr-super-stats='samplereport:{"autostart":true};'></a-scene>
start sampling manually
<a-scene vr-super-stats></a-scene>
<script>
// ... at the appropriate moment...
const samplesToTake = 200
const timeToShowResults = 10000
document.querySelector('[vr-super-stats]').components['vr-super-stats'].sample(samplesToTake).then(() => {
document.querySelector('[vr-super-stats]').components['vr-super-stats'].showSampleCanvas(timeToShowResults)
})
</script>
attach translucent stats to your left hand when you enter vr:
<a-scene vr-super-stats="anchorel:#left-hand; position:0 -.5 0; backgroundcolor:rgba(255, 255, 255, 0.8);">
<a-entity id="rig" position="0 0 0">
<a-camera camera position="0 1.6 0" look-controls></a-camera>
<a-entity hand-controls="hand: left" id="left-hand"></a-entity>
<a-entity hand-controls="hand: right"> </a-entity>
</a-entity>
</a-scene>
make stats appear on your right controller when you press button on your right controller, run sampling when you press button on left controller
<a-scene vr-super-stats="anchorel:#right-hand; position:0 -.5 0;" >
<a-entity id="rig" position="0 0 0">
<a-entity camera id="the-cam" position="0 1.6 0"></a-entity>
<a-entity sample-on-event id="left-hand" hand-controls="hand: left"></a-entity>
<a-entity stats-on-event id="right-hand" hand-controls="hand: right"></a-entity>
</a-entity>
make the stats panel a fixed item in your scene's space, remaining there whether in vr or not:
stick a VR panel somewhere you want in the scene, and make it stay there, whether you're in VR or not.
<a-scene vr-super-stats="anchorel:#the-box;position:0 .4 0; alwaysshow3dstats:true; show2dstats:false;" >
<a-circle id="floor" rotation="-90 0 0" radius="400" color="#7BC8A4"></a-circle>
<a-box id="the-box" position="-1 0.5 -6" rotation="0 45 0" color="red"></a-box>
</a-scene>
Demo
Glitch workspace (May show work-in-progress) Or, see the examples in this repo.
params
schema: {
enabled: { type: "boolean", default: true },
debug: { type: "boolean", default: false },
position: { type: "string", default: "0 -1.1 -.5" },
rotation: { type: "string", default: "-20 0 0" },
scale: { type: "string", default: "1 .8 1" },
performancemode: { type: "boolean", default: false }, // set of defaults to focus on making it as light of impact as possible
throttle: { type: "number", default: 50 }, // how many ms between recalc, has biggest effect on performance (which, here, you can easily see for yourself! hah.)
backgroundcolor: { type:"color", default: "orange"}, // you can specify semi-transparent colors using css style rgba(r,g,b,a) color declarations as well.
show2dstats: { type: "boolean", default: true }, // show the built-in 'stats' component when not in VR
alwaysshow3dstats: { type: "boolean", default: false }, // show this component even when not in VR
anchorel: { type: "selector", default: "[camera]" }, // anchor in-vr stats to something other than the camera
showlabels: {type: 'array', default:['raf','fps','geometries','programs','textures','calls','triangles','points','entities','load time']}, // please give all inputs in lowercase
showgraphs: {type: 'array', default:['raf','fps','geometries','programs','textures','calls','triangles','points','entities','load time']}, // this will be auto-filtered down to match above, but you can filter down further if you want, say, 4 values in text, but only 1 in graph form. you can also select `null` or `false` or `[]` to turn off all graphs.
//
// advanced options:
//
// targetmax
// targetmin
// samplereport
// ^ these three are defined below as custom schema options--basically, they take in JSON (if serializing or if defining in HTML, see examples) or straight up JS objects (if adding to scene programatically)
samplereport: {
// all numbers in ms
default: JSON.stringify({
autostart: false, // if false, can be programtically started by e.g. a button press by calling
// if true, will fire every time `enter-vr` event is triggered
/*
const ticksToSample = 600;
const durationToShowVRSampleReport = 20000 // 20 seconds
document.querySelector('[vr-super-stats]').components['vr-super-stats'].sample(ticksToSample).then(() => {
document.querySelector('[vr-super-stats]').components['vr-super-stats'].showSampleCanvas(durationToShowVRSampleReport)
})
*/
delay: 0, // if autostart true, how long after app launch to auto-start sampling
samples: 200, // if autostart true, how many samples to take
displayDuration: 30000, // how long to leave report up in VR before auto-closing
}),
parse: json => {
return typeof json === "string" ? JSON.parse(json) : json;
},
stringify: JSON.stringify
},
// thrown in are some sane defaults. This library is written/expects all stats to be given in lowercase everywhere, they will be uppercased as needed.
// note that you can only have one or the other defined for a given property; for performance, only one will be checked per property. to maximize performance, set no targets.
targetmax: {
default: JSON.stringify({
calls: 200, // too many draw calls kills responsiveness
raf: 15, // needed to keep responsiveness around 60fps
triangles: 100000, // rough limit for mobile experiences to be smooth
"load time": 3000, // subjective
points: 15000, // unsure, I've heard 20000 is a drag, but likely lower than that
entities: 200, // unsure, I'm more familiar with draw calls, suggested improved number here welcome
// you can specify your own targets for any stats props, and they'll turn red when they rise above target
// this does come with a small performance penalty
}),
capLabels: ['geometries','programs','textures','calls','triangles','points','entities','load'], // these props are auto-uppercased once for faster processing in tick handler
parse: json => {
const output = typeof json === "string" ? JSON.parse(json) : json;
const capitalizeWord = function(word) {
return word[0].toUpperCase() + word.slice(1,word.length)
}
Object.keys(output).forEach(label => {
output[capitalizeWord(label)] = output[label]
})
output['Load Time'] = output['Load Time'] || output['load time'] || output['load']; // || output['loadtime'] || output['load_time'] || output['Load'] || output['Load time'] || output['load-time'] ||
return output;
},
stringify: JSON.stringify
},
targetmin: {// inverse of targetmax, for values where lower is better
default: JSON.stringify({
fps: 75, // phones cap at 60, quest 1 aimed for 75.
// you can specify targets for any stats props, and they'll turn red when they fall below target
// this does come with a small performance penalty
}),
capLabels: ['geometries','programs','textures','calls','triangles','points','entities','load'], // these props are auto-uppercased once for faster processing in tick handler
parse: json => {
const output = typeof json === "string" ? JSON.parse(json) : json;
const capitalizeWord = function(word) {
return word[0].toUpperCase() + word.slice(1,word.length)
}
Object.keys(output).forEach(label => {
output[capitalizeWord(label)] = output[label]
})
output['Load Time'] = output['Load Time'] || output['load time'] || output['load']; // || output['loadtime'] || output['load_time'] || output['Load'] || output['Load time'] || output['load-time'] ||
return output;
},
stringify: JSON.stringify
}
},