【问题标题】:getImageData memory leak?getImageData 内存泄漏?
【发布时间】:2015-07-12 12:49:31
【问题描述】:

背景:上周我一直在使用 Canvas 和 JavaScript 开发一款本质上是多向 Tron 的游戏。我选择不清除每一帧的画布,这样我的小线段就会留下痕迹。对于碰撞检测,我使用这个函数:

// 8 sensors for collision testing, positioned evenly around the brush point
var detectionRadius = this.width / 2 + 1; //points just outside the circumference
var counter = 0;
var pixelData;
for (var i = 0; i < 16; i += 2) {
    //collisionPixels[] is an array of 8 (x, y) offsets, spaced evenly around the center of the circle
    var x = this.x + collisionPixels[i] * detectionRadius;
    var y = this.y + collisionPixels[i + 1] * detectionRadius;
    pixelData = context.getImageData(x,y,1,1).data; //pixel data at each point
    if (pixelData[3] != 0) {
        counter++;
    }
}
if (counter > 4) {
    this.collision();
}

这里的目的是获取画笔点表面周围8个像素的alpha值; 0 的 alpha 值只是在背景上。如果总共 8 个碰撞像素的数量大于 4(这包括玩家身后的轨迹),那么我调用 collision() 方法。这个函数实际上工作得很好(而且 this IS 在函数内部,所以这些声明是局部的)。

问题是context.getImageData() 使我的内存使用量猛增,并且在 3 或 4 场游戏后帧率下降。只剪掉那条线并分配pixelData 一些其他值可以使一切运行非常顺利,即使在进行其他计算时也是如此。

如何解决此内存泄漏?而且,如果有一种不那么复杂的方法来进行这种类型的碰撞检测,那是什么?

编辑:应要求,这是我的循环:

function loop() {
    now = Date.now();
    delta = now - lastUpdate;
    lastUpdate = now;
    if (!paused) {
        for (var i = 0; i < numPlayers; i++) {
            if (players[i].alive) {
                players[i].update(delta);
                players[i].render();
            }
        }
    }
    requestAnimationFrame(loop);
}

编辑 2:所以我尝试了 Patrick 的 UInt8ClampedArrays 想法:

//8 sensors for collision testing, positioned evenly around the brush point
    var detectionRadius = this.width / 2 + 1;
    var counter = 0;
    for (var i = 0; i < 16; i += 2) {
        var x = this.x + collisionPixels[i] * detectionRadius;
        var y = this.y + collisionPixels[i + 1] * detectionRadius;
        //translate into UInt8ClampedArray for data
        var index = (y * canvas.width + x) * 4 + 3; //+3 so we're at the alpha index
        if (canvasArray[index] != 0) {
            counter++;
        }
    }

而且,在循环的顶部,我添加了一个新的全局变量,每帧更新一次:

var canvasArray = context.getImageData(0,0,canvas.width,canvas.height).data;

希望我做对了。它可以工作,但你玩的每一轮游戏的内存和帧率仍然会变差。将上传一些堆快照。

编辑 3:

快照 1:https://drive.google.com/open?id=0B-8p3yyYzRjeY2pEa2Z5QlgxRUk&authuser=0

快照 2:https://drive.google.com/open?id=0B-8p3yyYzRjeV2pJb1NyazY3OWc&authuser=0

快照 1 在第一场比赛之后,2 在第二场比赛之后。

编辑 4:尝试限制帧率:

function loop() {
    requestAnimationFrame(loop);

    now = Date.now();
    delta = now - lastUpdate;
    //lastUpdate = now;

    if (delta > interval) {
        lastUpdate = now;
        if (!paused) {
            for (var i = 0; i < numPlayers; i++) {
                if (players[i].alive) {
                    players[i].update(delta);
                    players[i].render();
                }
            }
        }
    }
}

在哪里

interval = 1000 / fps;

它会延迟最终的性能影响,但使用此选项时内存仍在攀升。

编辑 5:虽然我确信一定有更好的方法,但我找到了一个运行良好的解决方案。将帧速率限制在 30 左右实际上在长期性能方面是有效的,但我讨厌游戏看起来 30 FPS 的方式.. 所以我构建了一个循环,对于所有更新和渲染,除了碰撞处理之外,它的帧速率没有上限,我以 30 FPS 更新。

function loop() {
    requestAnimationFrame(loop);

    now = Date.now();
    delta = now - lastUpdate;
    lastUpdate = now;

    if (!paused) {
        for (var i = 0; i < numPlayers; i++) {
            if (players[i].alive) {
                players[i].update(delta);
                players[i].render();
            }
        }
        if (now - lastCollisionUpdate > collisionInterval) {
            canvasData = context.getImageData(0, 0, context.canvas.width, context.canvas.height).data;
            for (var i = 0; i < numPlayers; i++) {
                if (players[i].alive) {
                    if (players[i].detectCollisions()) {
                        players[i].collision();
                    }
                }
            }
            lastCollisionUpdate = now;
        }
        canvasData = null;
    }
}

感谢您的回答。您的很多想法都进入了最终(?)产品,我很感激。关闭此线程。

【问题讨论】:

  • “火箭”是什么意思,或多或少?你能提供一个可运行的例子来说明问题吗?
  • 我在这段代码中没有看到任何内存泄漏的原因,但代码效率不高,所以我怀疑问题更多的是你的游戏时间非常紧迫,留给 GC 的时间很少。您能否向我们展示您是如何调用循环的(例如,您是否使用 setTimeout/Interval、requestAnimationFrame ...)?
  • @KenFyrstenberg 编辑到描述中;它是 requestAnimationFrame()。今晚晚些时候我将尝试设置帧速率上限,看看是否会给 GC 时间。
  • 这本身并不能解决您的问题,但是您不必要地重新声明了一些变量。我会在循环外定义 x、y 和索引。

标签: javascript canvas memory-leaks getimagedata


【解决方案1】:

有没有什么时候你可以调用context.getImageData(0, 0, context.canvas.width, context.canvas.height).data,这样你就可以使用那个UInt8ClampedArray而不是你正在使用的多个?此外,当您处理完图像数据(即ImageData,而不是其中的TypedArray)后,您可以尝试在其上调用delete,但我不确定这是否会释放内存。

【讨论】:

  • 我试过运行delete,它并没有释放内存。但第一个想法听起来很有希望:对 UInt8ClampedArrays 不太熟悉,所以我会尽快回复您。
  • @JackBritton 你的pixelDataUInt8ClampedArray,只是为了澄清。
【解决方案2】:

虽然我确信一定有更好的方法,但我找到了一个运行良好的解决方案。将帧速率限制在 30 左右实际上在长期性能方面是有效的,但我讨厌游戏看起来 30 FPS 的方式.. 所以我构建了一个循环,对于所有更新和渲染,除了碰撞处理之外,它的帧速率没有上限,我以 30 FPS 更新。

//separate update cycle for collision detection
var collisionFPS = 30;
var lastCollisionUpdate;
var collisionInterval = 1000 / collisionFPS;
var canvasData;

function loop() {
    requestAnimationFrame(loop);

    now = Date.now();
    delta = now - lastUpdate;
    lastUpdate = now;

    if (!paused) {
        for (var i = 0; i < numPlayers; i++) {
            if (players[i].alive) {
                players[i].update(delta);
                players[i].render();
            }
        }
        if (now - lastCollisionUpdate > collisionInterval) {
            canvasData = context.getImageData(0, 0, context.canvas.width, context.canvas.height).data;
            for (var i = 0; i < numPlayers; i++) {
                if (players[i].alive) {
                    if (players[i].detectCollisions()) {
                        players[i].collision();
                    }
                }
            }
            lastCollisionUpdate = now;
        }
        canvasData = null;
    }
}

可能不是最好的解决方案,但它是一致的。

【讨论】:

    猜你喜欢
    • 2011-11-30
    • 1970-01-01
    • 2016-06-15
    • 2015-11-27
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2013-01-20
    • 2011-10-31
    相关资源
    最近更新 更多