[HTML5] Memory every frame

Hi,

I’ve encountered a rather weird behaviour of my application while running idle. There’re many components and application is running smoothly at 60FPS but I’ve noticed that memory keeps going up and then GC steps in and everything cycles. It does not increase total memory usage over time or anything so I doubt it is a major leak but I was wondering whether this is a normal situation of anything can be done to prevent this.

I’ve failed to find anything while profiling application using Chrome’s DevTools, mainly because I lack skills in profiling JS but also because DevTools display Parent/Child relation of objects in a weird manner cycling parent, child, parent, child, etc. all over the bottom-up stack trace making it harder to manually scan through and see what is the root cause.

As you can see on DevTools screenshot (1) memory stacks up to certain level then GC kicks up (~ 30MB cleaned up, total). Every frame builds memory up a certain level (~ +0.1KB).

(1)

(2)

Any suggestions?
Thanks! :slight_smile:

EDIT: I haven’t mentioned that I’ve found something related to sounds while getting through heap dumps but it still happens after I’ve disabled all the sound channels in application (commented out everything related to sounds).

Hmm, hard to say. Does anything allocate new arrays per-frame, or is anything toggling in visibility (sprite.visible = false;, sprite2.visible = true;) or something else between frames?

I’ve searched a little bit, disabled Starling, Dragonbones and Sounds and it seems that Starling alone generates such problems. I decided to move everything from Starling to OpenFL and for that I need DisplacementMapFilter working or at least knowing how to implement custom filters. I bet having DisplacementMapFilter within OpenFL itself is much better for the community so I’ve tried but I’m stuck and I guess I need your help a little bit. I cannot get this to work - as you can see, I’ve commented out gl_FragColor and made it fully red but no changes are displayed on the screen - I get the exact same bitmap even though shader is at least validated because I can put a breakpoint within __initShader and it catches activity in there.

Shader and mappings are based on Starling implementation for now.

This is my filter Work-In-Progress (ImageDataUitl.displace() also).

package openfl.filters;
#if !flash


// TODO
import lime._internal.graphics.ImageDataUtil;
// TODO
import openfl.display.BitmapDataChannel;
import openfl.geom.Matrix3D;
import openfl.geom.Rectangle;
import openfl.geom.Point;
import openfl.display.BitmapData;
import openfl.display.DisplayObjectRenderer;
import openfl.display.Shader;
import openfl.geom.ColorTransform;

#if !openfl_debug
@:fileXml('tags="haxe,release"')
@:noDebug
#end

@:access(openfl.geom.Point)
@:access(openfl.geom.Rectangle)


@:final class DisplacementMapFilter extends BitmapFilter {


    @:noCompletion private static var __displacementMapShader = new DisplacementMapShader();

    public var alpha (get, set):Float;
    public var color (get, set):Int;
//    public var componentX (get, set):Int;
//    public var componentY (get, set):Int;
//    public var mapBitmap (get, set):BitmapData;
//    public var mapPoint (get, set):Point;
//    public var mode (get, set):String;
//    public var scaleX (get, set):Float;
//    public var scaleY (get, set):Float;

    @:noCompletion private var __alpha:Float;
    @:noCompletion private var __color:Int;

    @:noCompletion private var __componentX:Int;
    @:noCompletion private var __componentY:Int;

    @:noCompletion private var __mapBitmap:BitmapData;
    @:noCompletion private var __mapPoint:Point;

    @:noCompletion private var __mode:String;

    @:noCompletion private var __scaleX:Float;
    @:noCompletion private var __scaleY:Float;

    @:noCompletion private var __horizontalPasses:Int;
    @:noCompletion private var __verticalPasses:Int;

    private static var sOffset:Array<Float> = [0.5, 0.5, 0.0, 0.0];
    private static var sMatrix:Matrix3D = new Matrix3D();
    private static var sMatrixData:Array<Float> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    public function new(mapBitmap:BitmapData = null, mapPoint:Point = null, componentX:Int = 0, componentY:Int = 0, scaleX:Float = 0.0, scaleY:Float = 0.0, mode:String = 'wrap', color:Int = 0, alpha:Float = 0.0) {
        super();

        __color = color;
        __alpha = alpha;

        __componentX = componentX;
        __componentY = componentY;

        __mapBitmap = mapBitmap;
        __mapPoint = mapPoint;

        __mode = mode;

        __scaleX = scaleX;
        __scaleY = scaleY;

        __needSecondBitmapData = false;
        __preserveObject = true;
        __renderDirty = true;

        __numShaderPasses = 1;
    }


    public override function clone():BitmapFilter {
        return new DisplacementMapFilter (
        __mapBitmap.clone(),
        __mapPoint.clone(),
        __componentX,
        __componentY,
        __scaleX,
        __scaleY,
        __mode,
        __color,
        __alpha
        );
    }


    @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData {

        ImageDataUtil.displace(bitmapData.image, sourceBitmapData.image, __mapBitmap.image, __componentX, __componentY, __scaleX, __scaleY);

        return bitmapData;

    }

    private function __getMapMatrix(out:Matrix3D):Matrix3D {
        if (out == null) {
            out = new Matrix3D();
        }

        var columnX:Int, columnY:Int;
        var scale:Float = 1.0;
        var textureWidth:Float = 720.0;
        var textureHeight:Float = 660.0;


        for (i in 0...16) {
            sMatrixData[i] = 0;
        }

        if (__componentX == BitmapDataChannel.RED) {
            columnX = 0;
        } else if (__componentX == BitmapDataChannel.GREEN) {
            columnX = 1;
        } else if (__componentX == BitmapDataChannel.BLUE) {
            columnX = 2;
        } else {
            columnX = 3;
        }

        if (__componentY == BitmapDataChannel.RED) {
            columnY = 0;
        } else if (__componentY == BitmapDataChannel.GREEN) {
            columnY = 1;
        } else if (__componentY == BitmapDataChannel.BLUE) {
            columnY = 2;
        } else {
            columnY = 3;
        }

        sMatrixData[Std.int(columnX * 4)] = __scaleX * scale / textureWidth;
        sMatrixData[Std.int(columnY * 4 + 1)] = __scaleY * scale / textureHeight;

        out.copyRawDataFrom(new Vector<Float>(16, true, sMatrixData));

        return out;
    }


    @:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int):Shader {

        #if !macro

        __getMapMatrix(sMatrix);

        __displacementMapShader.offsets.value = sOffset;
        __displacementMapShader.displacements.value = sMatrixData;

        __displacementMapShader.mapTexture.input = __mapBitmap;

        #end

        js.Browser.console.log(__displacementMapShader);

        return __displacementMapShader;
    }


    // Get & Set Methods

    @:noCompletion private function get_alpha():Float {
        return __alpha;
    }


    @:noCompletion private function set_alpha(value:Float):Float {
        if (value != __alpha) {
            __renderDirty = true;
        }

        return __alpha = value;
    }

    @:noCompletion private function get_color():Int {
        return __color;
    }


    @:noCompletion private function set_color(value:Int):Int {
        if (value != __color) {
            __renderDirty = true;
        }

        return __color = value;
    }

}


#if !openfl_debug
@:fileXml('tags="haxe,release"')
@:noDebug
#end


private class DisplacementMapShader extends BitmapFilterShader {


    @:glFragmentSource(

    "
        uniform sampler2D openfl_Texture;
        uniform sampler2D mapTexture;
    
        uniform mat4 openfl_Matrix;
    
        uniform vec4 offsets;
        uniform mat4 displacements;
    
        varying vec2 openfl_TextureCoordv;
        varying vec2 mapTextureCoordv;
    
            void main(void) {
                vec4 map_coords = texture2D(mapTexture, mapTextureCoordv);
                vec4 map_coords_offseted = map_coords - offsets;
    
                vec4 displacements_multiplied = map_coords_offseted * openfl_Matrix;
                vec4 result = vec4(openfl_TextureCoordv, 0.0, 0.0) + displacements_multiplied;
    
                gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); //texture2D(openfl_Texture, vec2(result));
            }
    "
    )


    @:glVertexSource(

    "
        uniform mat4 openfl_Matrix;

		attribute vec4 openfl_Position;
		attribute vec2 openfl_TextureCoord;

		varying vec2 openfl_TextureCoordv;
        varying vec2 mapTextureCoordv;

		void main(void) {
			gl_Position = openfl_Matrix * openfl_Position;

			openfl_TextureCoordv = openfl_TextureCoord;
			mapTextureCoordv = openfl_TextureCoord;
		}
    "

    )


    public function new() {

        super();

        #if !macro
        offsets.value = [0.5, 0.5, 0.0, 0.0];
        displacements.value = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        #end

    }


}


#else
typedef DisplacementMapFilter = flash.filters.DisplacementMapFilter;
#end

and this is my test app:

class Main extends Sprite {
    public function new() {

        super();

        this.addChild(new Bitmap(Assets.getBitmapData('assets/test.png')));

        this.filters = [
            new DisplacementMapFilter(
                Assets.getBitmapData('assets/test_map.jpg')
            )
        ];
    }
}

Setting __preserveObject = false made shader run as expected but now I’m struggling passing second texture to fragment shader which is the map for displacements (later on I want to pass more textures, up to 4 but for application-specific purposes only). You can see in the previous message how I set up sampler2d in fragment shader for map uniform sampler2D mapTexture and how I assign mapTexture to it __displacementMapShader.mapTexture.input = __mapBitmap;

I tried setting gl_FragColor directly from mapTexture sampler but it seems to have the openfl_Texture inside instead of my custom mapTexture.

EDIT: I’ve come across this topic Sampling second texture in GLSL shader and I checked - I’ve got it covered since I’m working on develop branch for both Lime and OpenFL. It still does not seem to work correctly.

Looks like these three lines uncommented fixed it but I guess they were commented out for a reason Try and merge Shader enable/disable logic into Stage3D better (3.10.2018) - wasn’t that related to two issues reported in 8.5.1 ?

			// __context3D.__flushGLProgram ();
			// __context3D.__flushGLTextures ();
			// __currentShader.__enable ();

I’ve mostly finished, need to do some more stuff like including mapPoint to offset components etc… This is how it looks at this point. Left one is HTML5 target, right one is FLASH target. This is DisplacementMap for RED channel with 10.0/10.0 scaleX/scaleY.

2 Likes

OK. I forked develop branch. Here you can check things out - for now I’ve uncommented these three lines but I’m waiting for you to respond before I do any more changes:

1 Like

Thank you! I’ve merged your filter in to the develop branch, with some additional changes to fix a compile error and to make Flash more consistent.

Here’s the sample code I used:

package;


import openfl.display.Bitmap;
import openfl.display.Sprite;
import openfl.filters.DisplacementMapFilter;
import openfl.filters.DisplacementMapFilterMode;
import openfl.geom.Point;
import openfl.utils.Assets;


class Main extends Sprite {
	
	
	public function new () {
		
		super ();
		
		var channel1 = 1;
		var channel2 = 2;
		var x_mult = 40;
		var y_mult = 40;
		var mode:DisplacementMapFilterMode = CLAMP;
		var offset = new Point ();
		
		var filter = new DisplacementMapFilter (Assets.getBitmapData ("assets/displace03.png"), offset, channel1, channel2, x_mult, y_mult, mode);
		
		addChild (new Bitmap (Assets.getBitmapData ("assets/patt.png")));
		filters = [ filter ];
		
	}
	
	
}

With these images

patt

displace03

Thanks a lot for responding! I’ve come across another problem I’m not sure how to approach - as you can see on my comparision screenshot between FLASH and HTML5 targets you can see that HTML5 is not as smooth as the FLASH one - I’ve tried implementing bilinear filtering within fragment shader but it does not seem to help (or I failed to implement it correctly, I’ll be checking that soon) - in my application-specific use it looks a lot worse that Starling’s. Any suggestions what can I try more to have similar smoothness in GLSL approach ?

EDIT: I’ll check what you’ve fixed so I can provide a better code next time, thanks :slight_smile:

Oh! It was mostly the TODO for the software end of it, and I added some externs for Flash in order to allow DisplacementMapFilterMode to work smoothly between it and other targets. That’s only required when adding something new, it’s pretty rare and I don’t mind helping with that.

Perhaps if you tried displacing the UV coordinates in the vertex shader rather than displacing in the fragment shader? I don’t know if that’s possible (or if it would work properly), but that might help continue to support smoothing. Clamping could also be done there if you have the texture width/height, which I think we provide to the default shader:

You can also join the Discord server if you ever want to chat :slight_smile:

I tried moving it to the vertex shader myself, but I’m not seeing any results. Maybe a logic error on my part?

private class DisplacementMapShader extends BitmapFilterShader {
	
	
	@:glVertexSource("
		
		#pragma header
		
		uniform sampler2D mapTexture;
		
		uniform vec2 mapTextureCoordsOffset;
		uniform vec4 uOffsets;
		uniform mat4 uDisplacements;
		
		void main(void) {
			
			vec2 mapTextureCoords = openfl_TextureCoord - mapTextureCoordsOffset;
			vec4 map_color = texture2D (mapTexture, mapTextureCoords);
			vec4 map_color_mod = map_color - uOffsets;
			
			map_color_mod = map_color_mod * vec4 (map_color.w, map_color.w, 1.0, 1.0) * 0.5;
			
			vec4 displacements_multiplied = map_color_mod * uDisplacements;
			vec4 result = vec4 (openfl_TextureCoord.x, openfl_TextureCoord.y, 0.0, 1.0) + displacements_multiplied;
			
			openfl_TextureCoordv = vec2 (result);
			gl_Position = openfl_Matrix * openfl_Position;
			
		}
		
	")
	
	
	public function new () {
		
		super ();
		
	}
	
	
}

I’ll try to move these from fragment to vertex tomorrow. I’ve made it in fragment because in AGAL (Starling) it was done within fragment shader and in vertex UVs were passed with simple "mov v0, va1". I will report on my progress as soon as I get something new to show :slight_smile:

FilterEffect.tex("ft0", "v1", 1, _mapTexture, false), // read map texture
"sub ft1, ft0, fc0",          // subtract 0.5 -> range [-0.5, 0.5]
"mul ft1.xy, ft1.xy, ft0.ww", // zero displacement when alpha == 0
"m44 ft2, ft1, fc2",          // multiply matrix with displacement values
"add ft3,  v0, ft2",          // add displacement values to texture coords
"sat ft3.xy, ft3.xy",         // move texture coords into range 0-1
"min ft3.xy, ft3.xy, fc1.xy", // move texture coords into range 0-maxUV
FilterEffect.tex("oc", "ft3", 0, texture)  // read input texture at displaced coords

I’ll join Discord soon too :slight_smile: thanks :slight_smile:

Did you have these quality issues in the Starling effect?

It’s also possible something needs to be clamped

“sat ft3.xy, ft3.xy”, // move texture coords into range 0-1

clamp (result, 0.0, 1.0);

Maybe something like this somewhere?

Nope. In Starling, OpenFL version, it was really smooth and similar to the one I’ve got in our base Flash/Starling app. I’ve used 1.8.13 instead of 2.X because OpenFL masks did not go well with 2.X (we’ve talked about this in another thread, it was something related to framebuffer). So I basically based this filter implementation on: https://github.com/openfl/starling/blob/v1.8/starling/filters/DisplacementMapFilter.hx

I will be working on this for sure because now, after moving Dragonbones from Starling renderer to OpenFL renderer I’d love to get rid of Starling fully since the only thing that has left based on Starling is something based on DisplacementMapFilter. I’ll try to create a sample app that will show comparision between Starling’s, new OpenFL’s and Flash’s filter.

2 Likes

Oh, one other thing –

I believe when you do a -verbose build, we (by default) print out how the AGAL shaders were converted to GLSL. That might also be helpful, to see how the shader translation looks afterward on this

I’m going to check every option tonight - I hope for some good results soon. I knew that -verbose shows AGAL shaders GLSL version and I’ve even checked them out but never thought about porting them 1:1 to OpenFL - no wonder why because it seems like a simplest idea :slight_smile:. I’ve just made Starling working within my example and here are the results to check:

GLSL, Starling 1.8.13:

[openfl.display3D.Program3D] // AGAL vertex shader
#version 100
precision highp float;
attribute vec4 va0;
attribute vec4 va1;
attribute vec4 va2;
uniform mat4 vc0;
varying vec4 v0;
varying vec4 v1;
uniform vec4 vcPositionScale;
void main() {
	gl_Position = va0 * vc0; // m44
	v0 = va1; // mov
	v1 = va2; // mov
	gl_Position *= vcPositionScale;
}

CustomRendering.js:30618 [openfl.display3D.Program3D] // AGAL fragment shader
#version 100
precision highp float;
uniform vec4 fc0;
uniform mat4 fc1;
varying vec4 v0;
varying vec4 v1;
uniform sampler2D sampler1;
uniform sampler2D sampler0;
void main() {
	vec4 ft0;
	vec4 ft1;
	vec4 ft2;
	vec4 ft3;
	ft0 = texture2D(sampler1, v1.xy); // tex
	ft1 = ft0 - fc0; // sub
	ft2 = ft1 * fc1; // m44
	ft3 = v0 + ft2; // add
	gl_FragColor = texture2D(sampler0, ft3.xy); // tex
}

GLSL, Starling 2.4.1 (there is indeed clamp() used):

[openfl.display3D.Program3D] // AGAL vertex shader
#version 100
precision highp float;
attribute vec4 va0;
attribute vec4 va1;
attribute vec4 va2;
uniform mat4 vc0;
varying vec4 v0;
varying vec4 v1;
uniform vec4 vcPositionScale;
void main() {
	gl_Position = va0 * vc0; // m44
	v0 = va1; // mov
	v1 = va2; // mov
	gl_Position *= vcPositionScale;
}

CustomRendering.js:30973 [openfl.display3D.Program3D] // AGAL fragment shader
#version 100
precision highp float;
uniform mat4 fc2;
uniform vec4 fc1;
uniform vec4 fc0;
varying vec4 v1;
varying vec4 v0;
uniform sampler2D sampler1;
uniform sampler2D sampler0;
void main() {
	vec4 ft0;
	vec4 ft2;
	vec4 ft3;
	vec4 ft1;
	ft0 = texture2D(sampler1, v1.xy); // tex
	ft1 = ft0 - fc0; // sub
	ft1.xy = ft1.xy * ft0.ww; // mul
	ft2 = ft1 * fc2; // m44
	ft3 = v0 + ft2; // add
	ft3.xy = clamp(ft3.xy, 0.0, 1.0); // saturate
	ft3.xy = min(ft3.xy, fc1.xy); // min
	gl_FragColor = texture2D(sampler0, ft3.xy); // tex
}

and screenshots for comparision:

HTML5 Target, DisplacementMapFilter, RED/RED, 10.0/10.0, Starling 1.8.13 + OpenFL:

HTML5 Target, DisplacementMapFilter, RED/RED, 10.0/10.0, Starling 2.4.1 + OpenFL:

FLASH Target, DisplacementMapFilter, RED/RED, 10.0/10.0, Starling 2.4.1 + OpenFL:

I’ve got it. Setting __currentShader.__texture.filter = true ? LINEAR : NEAREST; to LINEAR (true) which is OpenGLRenderer::205 gives following result and I guess it will be looking good in our app (will check it tomorrow, at work).

And setting bitmap.smoothing = true does not help of course because it would be too easy. I’ve found out that in OpenGLRenderer::__renderFilterPass::866 there is a call for applyBitmapData with smooth = false explicite and it was set to false from true in commit with message Fix rendering on WebGL1 along with clamping instead of repeat.

EDIT: That one alone is false - every other call is true and thus mocking it to true fixes the problem (at least this one, probably creates more other problems).