vr-super-stats

Modern stats component for A-Frame WebXR projects that shows stats while in VR itself

Usage no npm install needed!

<script type="module">
  import vrSuperStats from 'https://cdn.skypack.dev/vr-super-stats';
</script>

README

loading-bar sample-report-3

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;' and showlabels: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 from tracked-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/vr-super-stats@1.4.1/vr-super-stats.js

vr-super-stats orange some-graphs-only allgraphs-opacity sample-report-2

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/vr-super-stats@1.4.1/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
    }
  },