草庐IT

iOS Safari 和 chrome 上的 Javascript Canvas 缓冲区/性能低下

coder 2024-01-10 原文

我正在尝试通过将像素直接绘制到 imageData 缓冲区来更新 javascript Canvas 。基本上我会在每次 mousemove/touchmove 事件后更新 imageData 缓冲区上的所有像素,并尝试获得最佳性能。

背景: 我正在开发一个基于 emscripten 的应用程序,其中 Canvas 上的绘图完全由“ native ”代码逐像素绘制。我在这个问题中给出的例子是我重现我的问题的一个更简单的例子。

我目前遇到了两个性能问题:

  • 在 iOS safari 上(在 iPad air 上测试):绘图函数以 31 fps 的速度调用,但屏幕上的 Canvas 渲染延迟(视觉上,我会说它以最大 10fps 的速度更新,加上一些间隔0.5 秒,完全不更新)
  • 在 iOS Chrome 上:性能很糟糕,因为我得到 2.9 fps

在桌面 mac 上,我获得稳定的性能:使用 firefox 时为 55 fps,使用 chrome 时为 45 fps

所以,我有两个问题

  • 如何强制 Canvas 在 iOs safari 上更快地刷新(以便获得真正的 30 fps 渲染,或者可能更低一点)?
  • 您将如何优化性能?我错过了可能的优化吗?

请引用下面的代码:它是一个重现我的问题的 html 文件。

我知道我可以使用 webworker,但由于我使用的是 emscripten,所以这不是最佳选择(每个 webworker 都以新鲜内存开始,我需要保留状态记录)。

请参阅此处的代码(这是一个单独的 html 文件,js 是独立的)。请在 Canvas 内移动鼠标以查看计算的 fps。

<canvas width=800 height=600 id="canvas"> </canvas>

<script>


//Disable scroll : usefull for tablets where touch events
//will scroll the page
function DisableScroll()
{
  window.addEventListener("touchmove", function(event) {
    if (!event.target.classList.contains('scrollable')) {
      // no more scrolling
      event.preventDefault();
    }
  }, false);
}

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();


window.countFPS = (function () 
{
  var nbSamples = 20; //number of samples before giving a fps
  var counter = 0;
  var fps = 0;
  var timeStart = new Date().getTime();
  return function()
  {
    counter++;
    if (counter == nbSamples)
    {
      var timeEnd = new Date().getTime();
      var delaySeconds = (timeEnd - timeStart) / 1000;
      fps = 1 / delaySeconds * nbSamples;

      counter = 0;
      timeStart = timeEnd;
    }
    return fps.toFixed(2);
  }
}());


function getMousePos(canvas, evt) {
  var rect = canvas.getBoundingClientRect();
  return {
    x: evt.clientX - rect.left,
    y: evt.clientY - rect.top
  };
}
function getTouchPos(canvas, evt) {
  var rect = canvas.getBoundingClientRect();
  return {
    x: evt.targetTouches[0].clientX - rect.left,
    y: evt.targetTouches[0].clientY - rect.top
  };
}


DisableScroll();

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
var canvasData = "empty";

function myDraw(pos)
{
  canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var binaryData = canvasData.data;

  var idx = 0;
  for (y = 0; y < canvas.height; y++)
  {
    for (x = 0; x < canvas.width; x++)
    {
      //Red
      binaryData[idx ++] = x % 255;
      //Green : add a little animation on the green channel
      //var dist = Math.sqrt( (pos.x - x) * (pos.x - x) + (pos.y - y) * (pos.y - y));
      var dist = Math.abs(pos.x - x) + Math.abs(pos.y - y);
      var g = 255 - dist;
      if ( g < 0 )
        g = 0;
      binaryData[idx++] = g;
      //Blue
      binaryData[idx ++] = y % 255;
      //Alpha
      binaryData[idx ++] =  255;
    }
  }

  ctx.putImageData(canvasData, 0, 0);
}



var OnLoad = function()
{
  myDraw({x:0, y:0});
}


//
// Mouse & touch callbacks
//
function CanvasMouseMove(pos)
{
  myDraw(pos);
  var elem = document.getElementById("fps");
  elem.value = window.countFPS();

}
canvas.addEventListener("touchmove", function(e){ CanvasMouseMove( getTouchPos(canvas, e)); } , false);
canvas.addEventListener("mousemove", function(e){ CanvasMouseMove( getMousePos(canvas, e) ); });


</script>

<body onload=OnLoad()>
<br/>
FPS<input type=text id="fps" />&nbsp;&nbsp;&nbsp;
</body>

最佳答案

要求:
- 避免泄漏全局并在 myDraw 中将 x、y 声明为变量。
建议:
- 缓存 canvas.width 和 canvas.height 以避免 DOM 访问,
- 缓存 pos.x 和 pos.y
- 用 (% 255) 换取 (& 0xFF)
- 缓存 Math.abs
- 只需创建一个您不断修改的图像数据(减轻 g.c.)。
- 在 requestAnimationFrame 上绘制(否则您可能需要等待绘制帧)。
- 缓存 Canvas 的边界矩形(及其顶部/左侧值)。

jsbin 在这里:

http://jsbin.com/saruzoqo/4/

您可以使用 2 个按钮切换旧/新。

看起来像

var staticCanvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);

function myDraw2(pos) {
    canvasData = staticCanvasData;
    var binaryData = canvasData.data;
    var cw = canvas.width,
        ch = canvas.height;
    var posX = pos.x,
        posY = pos.y;
    var idx = 0;
    var abs = Math.abs;
    for (var y = 0; y < ch; y++) {
        var yDiff = abs(posY - y) ;
        for (var x = 0; x < cw; x++) {
            //Red
            binaryData[idx++] = x & 0xFF;
            //Green : add a little animation on the green channel
            //var dist = Math.sqrt( (pos.x - x) * (pos.x - x) + (pos.y - y) * (pos.y - y));
            var dist = abs(posX - x) + yDiff;
            var g = 255 - dist;
     //       if (g < 0) g = 0; // useless array is clamped
            binaryData[idx++] = g;
            //Blue
            binaryData[idx++] = y & 0xFF;
            //Alpha
            binaryData[idx++] = 255;
        }
    }
    ctx.putImageData(canvasData, 0, 0);
}

结果相当不错,FF 只用了一半时间(10 对 20 毫秒),Chrome 少了 15 毫秒(116(!)到 100),而 safari 只用了 7 而不是 20 ! (苹果操作系统)

我没有做太多调查,但似乎仅在每次重绘时不创建/复制图像数据这一事实就占了 60% 以上的 yield 。

关于iOS Safari 和 chrome 上的 Javascript Canvas 缓冲区/性能低下,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23202555/

有关iOS Safari 和 chrome 上的 Javascript Canvas 缓冲区/性能低下的更多相关文章

  1. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  2. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  3. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  4. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

    我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

  5. 带有 attr_accessor 的类上的 Ruby instance_eval - 2

    我了解instance_eval和class_eval之间的基本区别。我在玩弄时发现的是一些涉及attr_accessor的奇怪东西。这是一个例子:A=Class.newA.class_eval{attr_accessor:x}a=A.newa.x="x"a.x=>"x"#...expectedA.instance_eval{attr_accessor:y}A.y="y"=>NoMethodError:undefinedmethod`y='forA:Classa.y="y"=>"y"#WHATTT?这是怎么回事:instance_eval没有访问我们的A类(对象)然后它实际上将它添加到

  6. ruby-on-rails - rails 上的 ruby : radio buttons for collection select - 2

    我有一个集合选择:此方法的单选按钮是什么?谢谢 最佳答案 Rails3中没有这样的助手。在Rails4中,它是collection_radio_buttons. 关于ruby-on-rails-rails上的ruby:radiobuttonsforcollectionselect,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/18525986/

  7. ruby - 下载位置 Selenium-webdriver Cucumber Chrome - 2

    我将Cucumber与Ruby结合使用。通过Selenium-Webdriver在Chrome中运行测试时,我想将下载位置更改为测试文件夹而不是用户下载文件夹。我当前的chrome驱动程序是这样设置的:Capybara.default_driver=:seleniumCapybara.register_driver:seleniumdo|app|Capybara::Selenium::Driver.new(app,:browser=>:chrome,desired_capabilities:{'chromeOptions'=>{'args'=>%w{window-size=1920,1

  8. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  9. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  10. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

随机推荐