Búsqueda de sitios web

javascript - Pequeño retraso al dibujar imágenes grandes por primera vez en Chrome


Estoy trabajando en un juego sencillo basado en JavaScript usando la etiqueta canvas. Como parte del juego, tengo varias hojas de sprites grandes (por ejemplo, 2816x768 y 4096x4864) que contienen animaciones relacionadas para el personaje en pantalla. Cuando el juego comienza, simplemente se reproduce la animación de inactividad del personaje. Cuando el usuario presiona la barra espaciadora, comienzo a reproducir otra animación de una hoja de sprites completamente diferente.

Aquí está el código que dibuja los sprites:

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);
};

Y aquí está el código que carga imágenes:

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

El problema es que hay un retraso de 1,5 segundos desde que el usuario presiona espacio hasta que el cuadro del nuevo sprite sheet realmente se dibuja. Esto ocurre solo una vez y no afecta la fluidez de la animación siguiente. Ya tengo los sprite sheets precargados usando new Image y el juego ni siquiera comienza hasta que todos los correspondientes eventos image.onload se han disparado, así que sé que el navegador no está esperando a que se carguen. He depurado el JavaScript usando el depurador en Chrome 17.0 y he reducido el retraso a la llamada drawImage en el contexto. Lo más desconcertante es que este retraso no está presente en Firefox 10.0.2, así que es un problema específico de Chrome. Es muy perturbador para la jugabilidad.

¿Qué estoy haciendo mal aquí? ¿Hay alguna manera de reducir este retraso en Chrome?

Editar: Intenté dibujar todo el siguiente fotograma tan pronto como se cargara, como sugirió Peter Wishart, pero eso tuvo un efecto mínimo. También intenté modificar el loadImage de la siguiente manera:

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;
};

Esto también no mostró efecto.

De hecho, encontré una solución, pero es terriblemente ineficiente. Se me ocurrió que Chrome podría estar intentando hacer cosas inteligentes con la memoria de la imagen después de que se haya decodificado. Si una imagen no se usa durante suficiente tiempo, y nuevamente esto es solo una suposición, Chrome eliminará los datos decodificados de la memoria y los recuperará si se necesitan nuevamente. Normalmente, el proceso de decodificación toma una cantidad de tiempo imperceptible, pero las imágenes grandes que uso causan un pico muy drástico en el rendimiento. Con esto cambié el bucle de dibujo a:

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);
    }
}

Esta solución sí funciona, pero como dije, parece un desperdicio terrible. Tengo que dibujar todo el marco del siguiente conjunto de animaciones, solo para evitar que los datos decodificados se vuelvan obsoletos en Chrome.

¿Existen alternativas menos derrochadoras y menos improvisadas a esta estrategia?

Respuesta #1

Con la idea de que Chrome simplemente estaba desechando los datos de imagen decodificados después de algún tiempo, intenté simplemente copiar las imágenes en un canvas fuera de pantalla, bajo la suposición de que Chrome no se molestaría en sacar ese canvas de la memoria. El código para hacer eso encajaba bien en la función 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;
    }
};

También funciona. Hay una pequeña penalización inicial cuando la página se carga, y probablemente use más memoria, pero es una solución viable, ya que la velocidad es más importante que la memoria en esta aplicación.

Respuesta #2

Parece que Chrome ha añadido a la función decode precisamente para este propósito, evitando el retraso de decodificación al agregarlo por primera vez al DOM.

Parece que no está implementado en Firefox o IE.

const img = new Image();
img.src = "bigImage.jpg";
img.decode().then(() => {
     document.body.appendChild(img);
}).catch(() => {
    throw new Error('Could not load/decode big image.');
});

Más información: https://medium.com/dailyjs/image-loading-with-image-decode-b03652e7d2d2