kano-code icon indicating copy to clipboard operation
kano-code copied to clipboard

Lower than expected FPS on some devices

Open keithclark opened this issue 5 years ago β€’ 2 comments
trafficstars

While testing the editor on a Kindle Fire 8 I noticed that the player only seems to run very simple creations at a max of 30fps when using the Repeat every 1 frames block. The Kindle Fire is more than capable of running at 60fps for basic drawing operations.

Digging into the code I noticed that the rAF callback calculates the delta time between the current and previous frames to determine when to run the next update:

https://github.com/KanoComputing/kano-code/blob/6a544976f1729caffa5cac0d22a1ea5818e64fc4/src/app/lib/modules/control/time.ts#L49-L54

It appears that this code isn't working correctly if rAF callbacks happen at inconsistent intervals. I've done some reading on FPS limiting and it seems that timing errors can be compensated for by changing this:

https://github.com/KanoComputing/kano-code/blob/6a544976f1729caffa5cac0d22a1ea5818e64fc4/src/app/lib/modules/control/time.ts#L52

to this:

startTimestamp = timestamp - (diff % dt);

In my case this works and bumps the frame rate back to 60fps. I also noticed an FPS increase on the PI3 kit. I was going to put a PR together with that change but I'd like your thoughts on this first.

In addition to (or instead of) the above, should there be a special case for when the "Repeat every n frames" interval is set to 1, as this should happen every rAF, regardless of frame deltas?

if (interval === 1) {
  this.frames[loopId] = requestAnimationFrame(func);
  return;
}

Any thoughts / ideas?

keithclark avatar Jan 17 '20 19:01 keithclark

Hi Keith,

Thanks for raising this. We discussed this with the team, and the reason why we limit the FPS to 30 is to achieve relative consistency in rendering between different devices. When users share creations on our platform with the on every frame block, we're normalising the speed to 30 so the animations look the same when someone remixes it. It does come with a drawback of lower FPS.

Hope that makes sense.

pazdera avatar Feb 19 '20 11:02 pazdera

That makes sense. But, if you're capping creations to 30FPS, I think you still have a bug. If I load this creation into Kano Code in Chrome and Firefox I get completely different frame rates:

  • Chrome: Rock-solid 60FPS
  • Firefox: 35-40FPS

My machine is capable or running that creation at 60FPS in both browsers, so 30FPS should be pretty stable. However neither browser seems to be running creations at that speed.

This is really noticeable when you create something on a PI Kit and later open it in a browser on a higher spec device - creations run at completely different speeds.

{
  "source": "<xml xmlns=\"http://www.w3.org/1999/xhtml\"><variables><variable type=\"\" id=\"%!A68BvlUj}jLF8@h_Lg\">ticks</variable><variable type=\"\" id=\"PG2z^~,|Q^A^XFf;/6RL\">done</variable></variables><block type=\"app_onStart\" id=\"default_app_onStart\" x=\"118\" y=\"91\"><field name=\"FLASH\"></field><statement name=\"CALLBACK\"><block type=\"variables_set\" id=\"Fr$GO`Lh{cW)vEJw_Slh\"><field name=\"VAR\" id=\"%!A68BvlUj}jLF8@h_Lg\" variabletype=\"\">ticks</field><value name=\"VALUE\"><block type=\"math_number\" id=\"ST]XY,c-JF`bI(Yx?!}e\"><field name=\"NUM\">0</field></block></value><next><block type=\"in_x_time\" id=\"9LnK@b](Avdjf7R%47x;\"><field name=\"UNIT\">seconds</field><value name=\"DELAY\"><shadow type=\"math_number\" id=\"ewzF=qFJ3nAPku=^7M7(\"><field name=\"NUM\">1</field></shadow></value><statement name=\"DO\"><block type=\"variables_set\" id=\"XS7[B;4j|ouQ8j^[B(TT\"><field name=\"VAR\" id=\"PG2z^~,|Q^A^XFf;/6RL\" variabletype=\"\">done</field><value name=\"VALUE\"><block type=\"logic_boolean\" id=\"*w}kz4o!VV%i],m;XVVY\"><field name=\"BOOL\">TRUE</field></block></value></block></statement></block></next></block></statement></block><block type=\"every_x_seconds\" id=\"9i+i8FNYI|+!BWjXlkNs\" x=\"117\" y=\"340\"><field name=\"UNIT\">frames</field><value name=\"INTERVAL\"><shadow type=\"math_number\" id=\"Iu/IU=L=R26KXQs_3i8b\"><field name=\"NUM\">1</field></shadow></value><statement name=\"DO\"><block type=\"controls_if\" id=\"z41lvg]I1b99Aiz/Mf)P\"><field name=\"CONFIG\">{\"elseIfs\":0,\"else\":false}</field><value name=\"IF0\"><block type=\"logic_negate\" id=\",@s@fISmfN/4-vcG^B;4\"><value name=\"BOOL\"><block type=\"variables_get\" id=\"~PT(T2|H%4[9rG/e@-kT\"><field name=\"VAR\" id=\"PG2z^~,|Q^A^XFf;/6RL\" variabletype=\"\">done</field></block></value></block></value><statement name=\"DO0\"><block type=\"sticker_moveTo\" id=\"Yg(75M%$w+NDFNBE}Fr]\"><value name=\"X\"><shadow type=\"math_number\" id=\"9r=%_jOQ/;;7C[]^R-Y_\"><field name=\"NUM\">0</field></shadow><block type=\"math_lerp\" id=\"Q=kkWsQW7F-n%`JOI5p^\"><value name=\"FROM\"><shadow type=\"math_number\" id=\"u0+q0$+s!3$J6NUQ{sqP\"><field name=\"NUM\">100</field></shadow></value><value name=\"TO\"><shadow type=\"math_number\" id=\"M*YVT2(c=K4R;XgW1O8F\"><field name=\"NUM\">700</field></shadow></value><value name=\"PERCENT\"><shadow type=\"math_number\" id=\"rLpGZfKp3creGK3FUS1=\"><field name=\"NUM\">50</field></shadow><block type=\"osc_value_get\" id=\"2-lD,-fa/;-o$|kL2[/(\"></block></value></block></value><value name=\"Y\"><shadow type=\"math_number\" id=\"znjN^(-t6VukrNk-Iur?\"><field name=\"NUM\">0</field></shadow></value><next><block type=\"text_value_set\" id=\")KuIM8UCp+{N-b_w|{k5\"><value name=\"VALUE\"><shadow type=\"text\" id=\"OP9}1cu]W/lVrU-wg}Ba\"><field name=\"TEXT\">Text</field></shadow><block type=\"variables_get\" id=\"O5)_l}=NF~,z(JP`=_lY\"><field name=\"VAR\" id=\"%!A68BvlUj}jLF8@h_Lg\" variabletype=\"\">ticks</field></block></value><next><block type=\"unary\" id=\"d3C8PMx0@~BYsvr(dwb1\"><field name=\"LEFT_HAND\" id=\"%!A68BvlUj}jLF8@h_Lg\" variabletype=\"\">ticks</field><field name=\"OPERATOR\">+=</field><value name=\"RIGHT_HAND\"><shadow type=\"math_number\" id=\"RJ]sE1?hZ-tR[X?YL/F1\"><field name=\"NUM\">1</field></shadow></value></block></next></block></next></block></statement></block></statement></block></xml>",
  "code": "var ticks, done;\n\n\napp.onStart(function() {\n  ticks = 0;\n  time.later(1, 'seconds', function () {\n    done = true;\n  });\n\n});\n\ntime.every(1, 'frames', function () {\n  if (!done) {\n    sticker.moveTo(math.lerp(100, 700, osc.value), 0);\n    text.value = ticks;\n    ticks += 1;\n  }\n});\n",
  "parts": [
    {
      "type": "sticker",
      "id": "sticker",
      "name": "Sticker"
    },
    {
      "type": "oscillator",
      "id": "osc",
      "name": "Osc"
    },
    {
      "type": "text",
      "id": "text",
      "name": "Text"
    }
  ],
  "profile": "default"
}

keithclark avatar Feb 24 '20 13:02 keithclark