HaxeFlixel / flixel

Free, cross-platform 2D game engine powered by Haxe and OpenFL
https://haxeflixel.com/
MIT License
1.97k stars 434 forks source link

Improve the perf window of the debugger #508

Closed Gama11 closed 10 years ago

Gama11 commented 11 years ago

The Hi-ReS! Stats repo seems to offer stats that are a little more advanced than those of the flixel debugger - specifically the MAX stat ("maxmium memory the application reached") and graphs that illustrate all the stas nicely. I'm thinking it would be beneficial to add these two things to the perf window.

Screenshot of the Hi-ReS! Stats window:

gamedevsam commented 11 years ago

I have code that spawns this gismo and works with OpenFL and NME on all platforms. Here it is:

/**
 * stats.hx
 * http://github.com/mrdoob/stats.as
 * 
 * NME port by fermmm
 * http://fermmm.wordpress.com/
 * 
 * Released under MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * How to use:
 * 
 *  addChild( new Stats() );
 *
 **/

package ;

import org.flixel.FlxU;
import nme.display.BitmapData;
import nme.display.Sprite;
import nme.events.Event;
import nme.events.MouseEvent;
import nme.geom.Matrix;
import nme.geom.Rectangle;
import flash.system.System;
import nme.text.TextField;
import nme.display.Stage;
import nme.text.TextFormat;
import Xml;
import nme.Lib;

class Stats extends Sprite {

    static inline var GRAPH_WIDTH : Int = 70;
    static inline var XPOS : Int = 69;//width - 1
    static inline var YPOS : Int = #if(debug) 15 #else 0 #end;//width - 1
    static inline var GRAPH_HEIGHT : Int = 50;
    static inline var TEXT_HEIGHT : Int = 50 + YPOS;

    private var text : TextField;

    private var timer : Int;
    private var fps : Int;
    private var ms : Int;
    private var ms_prev : Int;
    private var mem : Float;
    private var mem_max : Float;

    private var graph : BitmapData;
    private var rectangle : Rectangle;
    private var alignRight : Bool;

    private var fps_graph : Int;
    private var mem_graph : Int;
    private var ms_graph : Int;
    private var mem_max_graph : Int;
    private var _stage:Stage;

    private var fpsStr      :String;
    private var memStr      :String;
    private var memMaxStr   :String;
    private var msStr       :String;

    /**
     * <b>Stats</b> FPS, MS and MEM, all in one.
     */
    public function new(alignRight:Bool = true) 
    {
        super();
        this.alignRight = alignRight;
        mem_max = 0;
        fps = 0;

        text = new TextField();
        text.defaultTextFormat = new TextFormat("_sans", 9, null, null, null, null, null, null, null, null, null, null, -2);
        text.multiline = true;
        text.y = YPOS;
        text.width = GRAPH_WIDTH;
        text.height = TEXT_HEIGHT;
        text.selectable = false;
        text.mouseEnabled = false;

        rectangle = new Rectangle(GRAPH_WIDTH - 1, 0, 1, GRAPH_HEIGHT);

        this.addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true);
        this.addEventListener(Event.REMOVED_FROM_STAGE, destroy, false, 0, true);
    }

    private function init(e : Event) {

        _stage = flash.Lib.current.stage;
        graphics.beginFill(Colors.bg);
        graphics.drawRect(0, YPOS, GRAPH_WIDTH, TEXT_HEIGHT);
        graphics.endFill();

        this.addChild(text);

        graph = new BitmapData(GRAPH_WIDTH, GRAPH_HEIGHT, false, Colors.bg);
        graphics.beginBitmapFill(graph, new Matrix(1, 0, 0, 1, 0, TEXT_HEIGHT));
        graphics.drawRect(0, TEXT_HEIGHT, GRAPH_WIDTH, GRAPH_HEIGHT);

        this.addEventListener(Event.ENTER_FRAME, update);

        if (alignRight)
            x = Lib.current.stage.stageWidth - width;
    }

    private function destroy(e : Event) {

        graphics.clear();

        while(numChildren > 0)
            removeChildAt(0);           

        graph.dispose();

        removeEventListener(Event.ENTER_FRAME, update);

    }

    private function update(e : Event) {

        timer = flash.Lib.getTimer();

        //after a second has passed 
        if( timer - 1000 > ms_prev ) {

            mem = System.totalMemory * 0.000000954;
            mem_max = mem_max > mem ? mem_max : mem;

            fps_graph = GRAPH_HEIGHT - Std.int( Math.min(GRAPH_HEIGHT, ( fps / _stage.frameRate ) * GRAPH_HEIGHT) );

            mem_graph = GRAPH_HEIGHT - normalizeMem(mem);
            mem_max_graph = GRAPH_HEIGHT - normalizeMem(mem_max);
            //milliseconds since last frame -- this fluctuates quite a bit
            ms_graph = Std.int( GRAPH_HEIGHT - ( ( timer - ms ) >> 1 ));
            graph.scroll(-1, 0);

            graph.fillRect(rectangle, Colors.bg);
            graph.lock();
            graph.setPixel(XPOS, fps_graph, Colors.fps);
            graph.setPixel(XPOS, mem_graph, Colors.mem);
            graph.setPixel(XPOS, mem_max_graph, Colors.memmax);
            graph.setPixel(XPOS, ms_graph, Colors.ms);
            graph.unlock();

            fpsStr      = "FPS: " + fps + " / " + stage.frameRate;
            memStr      = "MEM: " + FlxU.roundDecimal(mem, 3);
            memMaxStr   = "MAX: " + FlxU.roundDecimal(mem_max, 3);

            //reset frame and time counters
            fps = 0;
            ms_prev = timer;

            return;
        }

        //increment number of frames which have occurred in current second
        fps++;

        msStr = "MS: " + (timer - ms);
        ms = timer;

        var htmlText:String = "<font color='" + Colors.fpsCSS +"'>" + fpsStr + "</font>" +
                        "<br>" +
                        "<font color='" + Colors.memCSS +"'>" + memStr + "</font>" +
                        "<br>" +
                        "<font color='" + Colors.memmaxCSS +"'>" + memMaxStr + "</font>" +
                        "<br>" +
                        "<font color='" + Colors.msCSS +"'>" + msStr + "</font>";

        text.htmlText = htmlText;
    }

    function normalizeMem(_mem:Float):Int {
        return Std.int( Math.min( GRAPH_HEIGHT, Math.sqrt(Math.sqrt(_mem * 5000)) ) - 2);
    }

}

class Colors {

    public static inline var bg : Int = 0x000033;
    public static inline var fps : Int = 0xffff00;
    public static inline var ms : Int = 0x00ff00;
    public static inline var mem : Int = 0x00ffff;
    public static inline var memmax : Int = 0xff0070;
    public static inline var bgCSS : String = "#000033";
    public static inline var fpsCSS : String = "#ffff00";
    public static inline var msCSS : String = "#00ff00";
    public static inline var memCSS : String = "#00ffff";
    public static inline var memmaxCSS : String = "#ff0070";

}
Gama11 commented 11 years ago

Urm... That's basically just the class from the repo I linked?

gamedevsam commented 11 years ago

Ohh, lolz, but this one works on OpenFL and Neko, which that one doesn't I think.

Gama11 commented 11 years ago

@crazysam Ok, I see. Yeah, that repo hasn't been updated in two years, despite being very popular. There are probably some more updated forks out there.

Gama11 commented 10 years ago

Done by @Beeblerox! 6771af7fa443b0a73755a7e3bf5c88c8bf08b037

gamedevsam commented 10 years ago

Looks awesome :D

tbrosman commented 9 years ago

I know this thread is probably pretty stale at this point, but out of curiosity why did @Beeblerox's version use FlxTextFormat instead of TextField.htmlText? I've been playing around with both and have been having mixed results with the htmlText in Flixel:
tags seem to work, but font color doesn't. Does it just not work in (Haxe) Flixel yet?

Gama11 commented 9 years ago

@tbrosman What do you mean, FlxTextFormat? There are no flixel text classes involved here, this is all flash display list API.

htmlText has issues on certain targets, I never got it to work properly on HTML5 IIRC (ironically). Where have you been trying to use it? With a flash TextField? A FlxText?

tbrosman commented 9 years ago

Ah, yeah you're correct, it's just TextFormat. My eyes just put Flx* in front of everything when I read Flixel code ;)

I get some odd results when I try using htmlText depending on whether I use FlxText or a TextField directly:

        bgColor = 0xFF888888;

        var test = "<font color='#FF0000'>t<u>e</u><i>s</i>t</font>ing<br><br>asdf";

        var flxText = new FlxText();
        flxText.textField.htmlText = test;
        add(flxText);

        var flashText = new TextField();
        flashText.htmlText = test;
        flashText.y = 40;
        Lib.current.stage.addChild(flashText);

html text test

Somehow linebreaks don't show up properly in the OpenFL Textfield (not sure what I'm missing) and colors don't show up in the FlxText. Is this some quirk of the render to bitmap logic?

I'm using Flixel 3.3.8 + OpenFL 3.0.3.

Tiago-Ling commented 9 years ago

Hey guys, one question about the debugger windows:

Even tough i'm down to just 4 FlxAtlasFrames the field drawTiles appears with numbers as high as 20-30. Is the overhead of the debugger window so much that you cannot safely rely on the drawTiles value?

I know that each normal FlxText (using .ttf fonts) uses one draw call right? I think this could maybe be changed for FlxBitmapText.

Another thing that could be made is for the drawTiles field to not count the debugger window own draw calls - this way the user can have a better idea of how much he/she needs to optmize.

What do you think - is this desirable and / or feasible?

Gama11 commented 9 years ago

The debugger does not affect any rendering stats, as it doesn't use flixel but flash sprites for rendering.

Tiago-Ling commented 9 years ago

So this means that using an FlxAtlasFrames does not guarantee only one drawTiles call. I wonder what causes additional draw calls when using atlases - surely it is not one per FlxSprite, my game has a lot more than the number displayed on screen at anytime. Could it be one per different frame used? Sorry for the off-topic btw.

Beeblerox commented 9 years ago

@Tiago-Ling different sprite's blending or sprite's tinting/no-tinting causes additional drawcalls

Tiago-Ling commented 9 years ago

@Beeblerox So in theory, using 4 atlases with no blending or tinting should make only 4 draw calls?

Beeblerox commented 9 years ago

@Tiago-Ling if you draw frames for each atlas in series (say, frames from the first atlas, then from the third one, then from the second, and then from the fourth) then there will be four draw calls, but in more general case you "mix" frames from different atlases, so the number of draw calls will be equal to the number of texture switches

sruloart commented 9 years ago

You can always create a new tinted/blended FlxAtlas from the tinted/mixed frames of the FlxAtlases in use. UbiArt engine saves (a lot of) atlases for different textures (color presets) as well.