首次在Chrome中绘制大图像的延迟很小

zac*_*297 5 javascript google-chrome canvas

我正在使用canvas标签开发一个基于JavaScript的简单游戏。作为游戏的一部分,我有几个大的精灵页面(例如2816x768和4096x4864),每个页面都包含屏幕上角色的相关动画。当游戏开始时,游戏只是播放角色的空闲动画。当用户按下空格键时,我将开始使用完全不同的拼版纸播放另一个动画。

这是绘制精灵的代码:

Sprite.prototype.drawFrame = function(x, y)
{
    ctx.drawImage(this.image,
        x*this.width, y*this.height,
        this.width, this.height,

        this.position[0], this.position[1],
        this.width, this.height);
};
Run Code Online (Sandbox Code Playgroud)

这是加载图像的代码:

Stage.prototype.loadImage = function(src)
{
    var image = new Image();
    this.incAssets();
    var stage = this;
    image.onload = function()
    {
        stage.decAssets();
    }
    image.src = src;
    return image;
}
Run Code Online (Sandbox Code Playgroud)

问题是从用户按下空格到从新的Sprite Sheet实际绘制帧起有1.5秒的延迟。这仅是一次,并且不会影响以下动画的平滑度。我已经使用了精灵表,new Image并且直到所有相应的image.onload事件都触发后,游戏才开始运行,所以我知道浏览器不会在等待它们的加载。我已经使用Chrome 17.0中的调试器逐步调试了JavaScript,并缩小drawImage了上下文调用的延迟。最令人困惑的是,此延迟在Firefox 10.0.2中不存在,因此这是Chrome特定的问题。这对游戏的发展非常不利。

我在这里做错了什么?无论如何,我可以减少Chrome的延迟吗?

编辑:我尝试按照Peter Wishart的建议尽快绘制整个下一帧,但是效果很小。我也尝试修改loadImage如下:

Stage.prototype.loadImage = function(src)
{
    var image = new Image();
    this.incAssets();
    var stage = this;
    image.onload = function()
    {
        ctx.drawImage(image, 0, 0);
        stage.decAssets();
    }
    image.src = src;
    return image;
};
Run Code Online (Sandbox Code Playgroud)

这也没有显示效果。

实际上,我确实找到了解决方案,但是效率很低。在我看来,Chrome浏览器在解码后可能会尝试使用图像存储器进行智能处理。如果图片闲置了足够长的时间,而这又只是一个猜测,Chrome会从内存中删除解码后的数据,并在需要时将其拉回。通常,解码过程会花费很少的时间,但是我使用的大图像会导致性能急剧下降。使用此方法,我将绘制循环更改为:

function draw()
{
    var currentTime = new Date().getTime();
    var deltaTime = currentTime - lastTime;
    lastTime = currentTime;

    var dt = deltaTime / 1000.0;

    // The hack that keeps all decoded image data in memory is as following.
    if (this.stage.nextStage != undefined) 
        this.stage.nextStage.draw(0); // The 0 here means the animations advance by 0 milliseconds, thereby keeping the state static.

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(backgroundImage, 0, 0, canvas.width, canvas.height);
    if (stage != undefined && stage.loaded)
    {
        stage.draw(dt);
    }
}
Run Code Online (Sandbox Code Playgroud)

该解决方案确实有效,但是就像我说的那样,这似乎是一个可怕的浪费。我必须绘制下一组动画的整个框架,以防止解码后的数据在Chrome中过时。

有没有比这种策略更省钱,更轻松的替代方案?

zac*_*297 4

考虑到 Chrome 一段时间后会丢弃解码后的图像数据,我尝试将图像复制到屏幕外画布中,假设 Chrome 不会费心从内存中取出该画布。执行此操作的代码非常适合该loadImage函数。

Stage.prototype.loadImage = function(src)
{
    var useCanvasCache = Prototype.Browser.WebKit; // If we are in a WebKit browser (e.g. Chrome)
    var decodeCanvas;
    var dectodeCtx;
    if (useCanvasCache)
    {
        // Creates a canvas to store the decoded image.
        decodeCanvas = document.createElement('canvas');
        dectodeCtx = decodeCanvas.getContext('2d');
    }
    var image = new Image();
    this.incAssets();
    var stage = this;
    image.onload = function()
    {
        stage.decAssets();
        // Simply transfer the image to the canvas to keep the data unencoded in memory.
        if (useCanvasCache)
        {
            decodeCanvas.width = image.width;
            decodeCanvas.height = image.height;
            dectodeCtx.drawImage(image, 0, 0);
        }

    }
    image.src = src;
    // Canvas works the same as an image in a drawImage call, so we can decide which to return.
    if (useCanvasCache)
    {
        return decodeCanvas;
    }
    else
    {
        return image;
    }
};
Run Code Online (Sandbox Code Playgroud)

它也有效。当页面加载时,初始损失很小,并且可能会使用更多内存,但这是一个可行的解决方案,因为在此应用程序中速度比内存更重要。