Chrome Performance Issue With Bitmap Area Sampling Effect (JavaScript)

61 views Asked by At

I am writing an HTML5 game using the engine Phaser, in which I am implementing what are essentially live backgrounds, backgrounds that respond to the movements of the game objects. The first I am working with is a water ripple effect that uses area sampling on the bitmapData object. I thought I had a performance issue in my code, but it turns out that Firefox runs it like a dream. Chrome runs a little slower to begin with and slows to less than 10 FPS when my game objects go too close to the top or bottom of the screen. (I am at a loss for why that makes a difference.)

This thread suggests that Chrome has poor image processing performance and suggests to break large image data up into smaller pieces. I don't know if this is possible in my case, because this is not simply an image displaying on the screen but an effect based on pixels next to each other that refreshes each frame. Even if it is possible, I think Chrome would end up having to do the same amount of work or more to get the four individual bitmaps to interact with each other as if they were one.

I've been doing performance tests in Chrome for a few hours, and the issue is definitely that it is getting caught up on the method that actually creates the effect by reading pixels from a source imageData and writing them to another location in a target imageData (the ws.displace(x,y) method below).

function waterStage(canvas) {

    var ws = new Object();
    ws.dampFactor = 16;
    ws.magFactor = 150;
    ws.dispFactor = 0.5;
    ws.lumFactor = 1;

    ws.width = canvas.width;
    ws.height = canvas.height;

    // Initialize height data caches
    ws.pMaps = [];
    var map1 = new Array(ws.width+2);
    var map2 = new Array(ws.width+2);

    for (x=0; x < map1.length; x++) {
        map1[x] = new Array(ws.height+2);
        map2[x] = new Array(ws.height+2);
    }

    for (x=0; x < map1.length; x++) {
        for (y=0; y < map1[x].length; y++) {
            map1[x][y] = 0;
            map2[x][y] = 0;
        }
    }

    ws.pMaps.push(map1, map2);

    ws.stageInit = function(canvas) {
        canvas.fill(100,100,100);
        canvas.ctx.strokeStyle = "#000000";
        canvas.ctx.lineWidth = 2;
        canvas.ctx.moveTo(0,0);
        for (y=0; y < ws.height; y+=10) {
            canvas.ctx.beginPath();
            canvas.ctx.moveTo(0,y);
            canvas.ctx.lineTo(ws.width,y);
            canvas.ctx.closePath();
            canvas.ctx.stroke();
        }

        ws.sourceData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
        ws.targetData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
    }

    ws.setWave = function(pnt) {
        ws.pMaps[0][pnt.x-1][pnt.y-1] = ws.magFactor//*pnt.magnitude;
    }

    ws.resolveWaves = function(x,y) {
        // Calculate the net result of the wave heights
        ws.pMaps[1][x][y] = ((ws.pMaps[0][x-1][y]+ws.pMaps[0][x+1][y]+ws.pMaps[0][x][y-1]+ws.pMaps[0][x][y+1]) / 2)
                            -ws.pMaps[1][x][y];
        ws.pMaps[1][x][y] -= (ws.pMaps[1][x][y]/ws.dampFactor);
    }

    ws.displace = function(x,y) {

        var displace = Math.floor(ws.pMaps[1][x][y]*ws.dispFactor);
        var xCorrect = x-1, yCorrect = y-1;
        var targetIndex = (xCorrect + yCorrect * ws.width)*4;

        if (displace == 0) {
            ws.targetData.data[targetIndex] = ws.sourceData.data[targetIndex];
            ws.targetData.data[targetIndex+1] = ws.sourceData.data[targetIndex+1];
            ws.targetData.data[targetIndex+2] = ws.sourceData.data[targetIndex+2];
        }

        else {

            if (displace < 0) {
                displace += 1;
            }

            var sourceX = displace+xCorrect;
            var sourceY = displace+yCorrect;
            var sourceIndex = (sourceX + sourceY * ws.width)*4;

            //var lum = ws.pMaps[1][x][y]*ws.lumFactor;

            ws.targetData.data[targetIndex] = ws.sourceData.data[sourceIndex];//+lum;
            ws.targetData.data[targetIndex+1] = ws.sourceData.data[sourceIndex+1];//+lum;
            ws.targetData.data[targetIndex+2] = ws.sourceData.data[sourceIndex+2];//+lum;

        }
    }

    ws.stageRefresh = function(moves, canvas) {
        canvas.clear();

        for (j=0; j < moves.length; j++) {
            ws.setWave(moves[j]);
        }

        for (x=1; x <= ws.width; x++) {
            if (ws.pMaps[1][x][0] != 0 || ws.pMaps[0][x][0] != 0) {
                alert("TOP ROW ANOMALY");
            }
            for (y=1; y <= ws.height; y++) {
                ws.resolveWaves(x,y);
                ws.displace(x,y);
            }
        }

        ws.pMaps.sort(function(a,b) { return 1 });
        //ws.pMaps[0] = ws.pMaps[1];
        //ws.pMaps[1] = temp;

        canvas.ctx.putImageData(ws.targetData, 0, 0);
    }

    return ws;
}

canvas is the bitmapData that is given as the texture for the background (not an HTML5 canvas; sorry if that's confusing). ws.stageRefresh(moves,canvas) is called on every frame update.

Before I try to make the split-into-four-bitmaps solution work, does anyone have any guidance for other ways to improve the performance of this effect on Chrome?

0

There are 0 answers