Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance is irregular on iOS8/iPad3 #246

Closed
samreid opened this issue Sep 18, 2014 · 19 comments
Closed

Performance is irregular on iOS8/iPad3 #246

samreid opened this issue Sep 18, 2014 · 19 comments
Assignees

Comments

@samreid
Copy link
Member

samreid commented Sep 18, 2014

Energy skate park basics suffers from a significant problem on iOS8/iPad3. The motion speeds up and slows down dramatically at different parts in the track.
espb

@samreid
Copy link
Member Author

samreid commented Sep 18, 2014

In order to isolate the performance issue, I added this code throughout scenery's SVG update handling code:

      if (Date.now()-this.start>20000){
        return;
      }

After setting up the behavior above and waiting for the remainder of the 20 seconds to elapse, the simulation runs at a consistent 60fps (while updating the model but not the graphics).

@samreid samreid self-assigned this Sep 18, 2014
@samreid
Copy link
Member Author

samreid commented Sep 18, 2014

I tried replacing the entire model step function with this to see if it could be caused by the physics:

      this.skater.position = new Vector2( 10 * Math.sin( Date.now() / 1000 / 3 ), Math.abs( 10 * Math.sin( Date.now() / 1000 / 7 / 3 ) ) );
      this.skater.trigger( 'updated' );

This moves the skater all around the screen slowly. Normally it gives about 20fps on iPad3 but there are regions (such as the bottom right corner) where it gets noticeably smoother and reports 30fps.

@samreid
Copy link
Member Author

samreid commented Sep 18, 2014

Remember how the behavior got chunky when an object was partially off the screen? phetsims/scenery#270 This may be a similar phenomenon

@samreid
Copy link
Member Author

samreid commented Sep 18, 2014

I don't think the poor iOS8 performance is isolated to Energy Skate Park: Basics. I tested blast, and it is very nonsmooth and sluggish (even when removing css transforms).

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

Same problem on iPad2/iOS8

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

When removing the skater graphic, the sim runs a smooth 60fps on iPad3/iOS8.

When removing all graphics except the skater, the sim still runs sluggishly and getting faster/slower. Still true when replacing the skater image with a small circle.

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

When removing all graphics except the skater, the sim still runs sluggishly and getting faster/slower. Still true when replacing the skater image with a small circle.

For this case, I'm seeing 55fps or so on average, but lots of stutters.

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

Removing the red skateboard dot, removing the skaternode's parent node and using rendererOptions: {cssTransform: true} don't help at all.

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

Rewriting SkaterNode using CanvasNode runs smoothly but 30fps (pretty slow compared to our normal 60fps). This is still with everything hidden except the skater like so:

image

// Copyright 2002-2013, University of Colorado Boulder

/**
 * Scenery node for the skater, which is draggable.
 *
 * Converted to composition instead of inheritance for SkaterNode to work around updateSVGFragment problem, see #123
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Matrix3 = require( 'DOT/Matrix3' );
  var LinearFunction = require( 'DOT/LinearFunction' );
  var Node = require( 'SCENERY/nodes/Node' );
  var CanvasNode = require( 'SCENERY/nodes/CanvasNode' );
  var Bounds2 = require( 'DOT/Bounds2' );

  // images
  var skaterLeftImage = require( 'image!ENERGY_SKATE_PARK_BASICS/skater-left.png' );
  var skaterRightImage = require( 'image!ENERGY_SKATE_PARK_BASICS/skater-right.png' );

  //Map from mass(kg) to scale
  var massToScale = new LinearFunction( (100 + 25) / 2, 100, 0.34, 0.43 );

  var LEFT_STRING = 'left';

  /**
   * SkaterNode constructor
   *
   * @param {Skater} skater
   * @param {EnergySkateParkBasicsScreenView} view
   * @param {ModelViewTransform} modelViewTransform
   * @param {function} getClosestTrackAndPositionAndParameter function that gets the closest track properties, used when the skater is being dragged close to the track
   * @param {function} getPhysicalTracks function that returns the physical tracks in the model, so the skater can try to attach to them while dragging
   * @constructor
   */
  function SkaterNode( skater, view, modelViewTransform, getClosestTrackAndPositionAndParameter, getPhysicalTracks ) {
    this.skater = skater;
    var skaterNode = this;

    CanvasNode.call( this, {canvasBounds: new Bounds2( 0, 0, 1000, 1000 )} );
    var imageWidth = 100;
    var imageHeight = 100;

    //Update the position and angle.  Normally the angle would only change if the position has also changed, so no need for a duplicate callback there
    //Uses pooling to avoid allocations, see #50
    this.skater.on( 'updated', function() {
      var mass = skater.mass;
      var position = skater.position;
      var direction = skater.direction;
      var angle = skater.angle;

      var view = modelViewTransform.modelToViewPosition( position );

      //Translate to the desired location
      var matrix = Matrix3.translation( view.x, view.y );

      //Rotation and translation can happen in any order
      var rotationMatrix = Matrix3.rotation2( angle );
      matrix.multiplyMatrix( rotationMatrix );
      rotationMatrix.freeToPool();

      var scale = massToScale( mass );
      skaterNode.image = direction === LEFT_STRING ? skaterLeftImage : skaterRightImage;
      var scalingMatrix = Matrix3.scaling( scale, scale );
      matrix.multiplyMatrix( scalingMatrix );
      scalingMatrix.freeToPool();

      //Think of it as a multiplying the Vector2 to the right, so this step happens first actually.  Use it to center the registration point
      var translation = Matrix3.translation( -imageWidth / 2, -imageHeight );
      matrix.multiplyMatrix( translation );
      translation.freeToPool();

//      skaterNode.setMatrix( matrix );
      skaterNode.storedMatrix = matrix;
      skaterNode.invalidatePaint();
    } );

    //Show a red dot in the bottom center as the important particle model coordinate
//    this.addChild( new Circle( 8, {fill: 'red', x: imageWidth / 2, y: imageHeight } ) );

    var targetTrack = null;

    var targetU = null;
    this.addInputListener( new SimpleDragHandler(
      {
        start: function( event ) {
          skater.dragging = true;

          //Clear thermal energy whenever skater is grabbed, see https://github.com/phetsims/energy-skate-park-basics/issues/32
          skater.thermalEnergy = 0;

          //Jump to the input location when dragged
          this.drag( event );
        },

        drag: function( event ) {

          var globalPoint = skaterNode.globalToParentPoint( event.pointer.point );
          var position = modelViewTransform.viewToModelPosition( globalPoint );

          //make sure it is within the visible bounds
          position = view.availableModelBounds.getClosestPoint( position.x, position.y, position );

          //PERFORMANCE/ALLOCATION: lots of unnecessary allocations and computation here, biggest improvement could be to use binary search for position on the track
          var closestTrackAndPositionAndParameter = getClosestTrackAndPositionAndParameter( position, getPhysicalTracks() );
          var closeEnough = false;
          if ( closestTrackAndPositionAndParameter && closestTrackAndPositionAndParameter.track && closestTrackAndPositionAndParameter.track.isParameterInBounds( closestTrackAndPositionAndParameter.u ) ) {
            var closestPoint = closestTrackAndPositionAndParameter.point;
            var distance = closestPoint.distance( position );
            if ( distance < 0.5 ) {
              position = closestPoint;
              targetTrack = closestTrackAndPositionAndParameter.track;
              targetU = closestTrackAndPositionAndParameter.u;

              //Choose the right side of the track, i.e. the side of the track that would have the skater upside up
              var normal = targetTrack.getUnitNormalVector( targetU );
              skater.up = normal.y > 0;

              skater.angle = targetTrack.getViewAngleAt( targetU ) + (skater.up ? 0 : Math.PI);

              closeEnough = true;
            }
          }
          if ( !closeEnough ) {
            targetTrack = null;
            targetU = null;

            //make skater upright if not near the track
            skater.angle = 0;
            skater.up = true;

            skater.position = position;
          }

          else {
            skater.position = targetTrack.getPoint( targetU );
          }

          skater.updateEnergy();
          skater.trigger( 'updated' );
        },

        end: function() {

          //Record the state of the skater for "return skater"
          skater.released( targetTrack, targetU );
        }
      } ) );
  }

  return inherit( CanvasNode, SkaterNode, {


    paintCanvas: function( wrapper ) {
      var context = wrapper.context;

      //If the debug flag is enabled, it will show the bounds of the canvas
      context.fillStyle = this.color;

//      context.fillText( this.storedMatrix.toString(), 200, 200 );
      this.storedMatrix.canvasAppendTransform( context );
      context.fillRect( 0, 0, 100, 100 );
    }

  } );
} );

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

Putting back in the rest of the graphics except for the CanvasNode for the skater, the FPS is still 30FPS (which is good!). However, the FPS drops to 20fps when turning on piechart/barchart/grid/speedometer, which looks pretty slow. Also, if using CanvasNode for something draggable, we would have to find/create a solution for making the skater pickable.

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

Simply putting renderer:canvas in SkaterNode's image seems to get the same behavior as the above comment: smooth at 30FPS (no irregularities), dropping down to 20fps when turning on the checkboxes.

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

When I ran the sin/sin test above, it revealed a region in the bottom right of the screen where the motion is very smooth and fast. You can see the same behavior by just dragging the skater. Dragging most of the places on the screen is very slow (30fps+stuttering) and dragging in the bottom right corner is very speedy (60fps and no hiccups). In this respect, the problem seems very similar to phetsims/scenery#270

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

After the above investigation, it seems like the best bets for continued development involve using webgl to render the skater (and possibly the charts), however this will delay publication of the sim significantly since this is a novel technology and doesn't integrate directly with our existing infrastructure (and since it should only apply to iOS8 devices).

EDIT: If we need to publish before the above work can be completed, then we can use canvas renderer on iPad which will reduce the frame rate but fix the "speeding up and slowing down randomly" problem.

@samreid
Copy link
Member Author

samreid commented Sep 19, 2014

Note: the same behavior as in the original issue comment occurs if you never create the skater node, but only show the pie chart.

@samreid
Copy link
Member Author

samreid commented Sep 22, 2014

This version uses Scenery's initial webgl support for images + rectangles, moves all of the dynamic content to the front webgl layer, uses a custom (hack) speedometer needle:

http://www.colorado.edu/physics/phet/dev/html/energy-skate-park-basics/1.0.0-dev.67/energy-skate-park-basics_en.html

Issues/Concessions/Caveats/etc:

  1. WebGL only runs on iOS when webgl is detected, otherwise svg is used throughout
  2. the pie chart node is omitted here (should be added back once we can do so with good performance)
  3. The skater is a little blurry
  4. The bar chart bars no longer render a border stroke
  5. The speedometer needle no longer goes behind the word "speed" or behind the black "pin" or behind the gauge ticks.
  6. There is a brief pause when the skater changes direction. Perhaps a problem in our WebGL image renderer? Could be worked around by flipping the image instead of supplying a different left/right image (which were drawn by the artist).
  7. The dynamic front layer is WebGL and the background + control panels are SVG
  8. The layout dimensions are a bit odd--would need to be addressed before the next version (perhaps I branched before layout bounds changed?).

Results:
A. The motion no longer speeds up and slows down irregularly
B. On my iPad3/iOS8, it gets about 50fps when animating on the 1st screen/1st track. I see minor garbage collection hitches, but they are much less severe than before. The performance remains 50fps when the bar chart, speedometer, and grid are turned on.

I haven't tested this on iPad2 with iOS8, it would be good to see what that performance is like. Also, I don't have an iOS7 device and it would be good to test the performance there since numerous changes to the layering have been changed to push the dynamic content to the front.

EDIT: reorganized notes above

@samreid
Copy link
Member Author

samreid commented Sep 24, 2014

I tested iPad Air running iOS 8 today (for the SVG-rendered sim), and it had the same problem of speeding up and slowing down. WebGL, on the other hand is performing nicely on iPad3/iOS8. I'm going to merge webgl into master branch and continue there.

@samreid
Copy link
Member Author

samreid commented Sep 24, 2014

Merge complete, and I deleted the webgl branch.

@samreid
Copy link
Member Author

samreid commented Sep 24, 2014

@jbphet reports the following for iPad2/iOS7:

58fps average with all checkboxes off
40fps average with all checkboxes on

No reports of speeding up or slowing down as we saw for SVG/iOS8. The iOS8 performance is excellent. Closing.

@samreid samreid closed this as completed Sep 24, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant