VGBenchmark

Overview

A benchmark app that initially displays 150 random shapes - text, circles, rectangles and SVG files.

To increase shape count by 10 - tap the top third of the screen, to decrease shape count by 10 - tap the bottom third of the screen. To change quality settings, tap the middle of the screen.

Try It

Use the following Loom CLI commands to run this example:

loom new MyVGBenchmark --example VGBenchmark
cd MyVGBenchmark
loom run

Screenshots

VGBenchmark Screenshot

Code

src/Benchmark.ls

package
{
    import loom.Application;
    import loom2d.display.Shape;
    import loom2d.display.Stage;
    import loom2d.display.StageScaleMode;
    import loom2d.display.SVG;
    import loom2d.display.TextFormat;
    import loom2d.events.Touch;
    import loom2d.events.TouchEvent;
    import loom2d.events.TouchPhase;
    import loom2d.events.KeyboardEvent;
    import loom.platform.LoomKey;
    import loom2d.Loom2D;
    import loom2d.math.Rectangle;
    import loom.gameframework.TimeManager;
    import loom.gameframework.LoomGroup;
    import system.Math;

    private enum ShapeType
    {
        Circle,
        Square,
        RoundedSquare,
        Path,
        Text,
        SVG,
    }

    /*
     * A separate class to hold const data
     */
    private class Data
    {
        public static var strings:Vector.<String> = new Vector.<String>
        [
            "Hello",
            "World",
            "Loom",
            "Vector",
            "Graphics"
        ];
        public static var svg:Vector.<SVG> = new Vector.<SVG>
        [
            SVG.fromFile("assets/loom_vector_logo_mod.svg"),
            SVG.fromFile("assets/Hand_left.svg"),
            SVG.fromFile("assets/nano.svg")
        ];
    }

    /*
     * This is a benchmark app that tries to push the limits of vector graphics rendering.
     * It should expose any bottlenecks in the rendering pipeline.
     *
     * For input control see onTouch method.
     */
    public class Benchmark extends Application
    {
        private var time:TimeManager;
        private var frames:Number;
        private var totalTime:Number;
        private var avgDt:Number;
        private var overlay:Shape;
        // Format for overlay text
        private var overlayFormat:TextFormat;

        // Display string for the quality setting
        private var qualityStr:String;

        // Text format for the benchmark
        private var textFormat:TextFormat;

        // Constants
        private const SHAPE_INTERVAL:Number = 10;
        private const SHAPE_NUM_START:Number = 150;

        // Comment these out if you want to disable certian types, for ex. SVG
        private var shapeTypes:Vector.<ShapeType> = new Vector.<ShapeType> [
            ShapeType.Circle,
            ShapeType.Square,
            ShapeType.RoundedSquare,
            ShapeType.Path,
            ShapeType.Text,
            ShapeType.SVG
        ];

        public function generateShape():Shape
        {
            var shape = new Shape();

            var color = Math.randomRangeInt(0, 255) |
                        Math.randomRangeInt(0, 255) << 8 |
                        Math.randomRangeInt(0, 255) << 16;

            shape.graphics.lineStyle(Math.randomRange(1, 5), color);

            // 50% chance to fill the shape (if available)
            var fill:Boolean = Math.random() > 0.5;
            if (fill)
            {
                var color2 = Math.randomRangeInt(0, 255) |
                             Math.randomRangeInt(0, 255) << 8 |
                             Math.randomRangeInt(0, 255) << 16;
                shape.graphics.beginFill(color2, 1);
            }

            var type = shapeTypes[Math.randomRangeInt(0, shapeTypes.length - 1)];
            switch(type)
            {
                case ShapeType.Text:
                    var str = Data.strings[Math.randomRangeInt(0, Data.strings.length - 1)];
                    textFormat.color = color;
                    shape.graphics.textFormat(textFormat);
                    shape.graphics.drawTextLine(0, 0, str);
                    break;
                case ShapeType.SVG:
                    var svg = Data.svg[Math.randomRangeInt(0, Data.svg.length - 1)];
                    shape.graphics.drawSVG(svg, 0, 0, 0.25, 0.5);
                    break;
                case ShapeType.Circle:
                    var radius = Math.randomRange(5, 25);
                    shape.graphics.drawCircle(radius, radius, radius);
                    break;
                case ShapeType.Square:
                    shape.graphics.drawRect(
                        Math.randomRange(0, stage.stageWidth),
                        Math.randomRange(0, stage.stageHeight),
                        Math.randomRange(0, stage.stageWidth / 2),
                        Math.randomRange(0, stage.stageHeight / 2));
                    break;
                case ShapeType.RoundedSquare:

                    shape.graphics.drawRoundRect(
                        Math.randomRange(0, stage.stageWidth),
                        Math.randomRange(0, stage.stageHeight),
                        Math.randomRange(0, stage.stageWidth / 2),
                        Math.randomRange(0, stage.stageHeight / 2),
                        Math.randomRange(0, 15),
                        Math.randomRange(0, 15));
                    break;
                case ShapeType.Path:
                    shape.graphics.moveTo(0, 0);
                    shape.graphics.curveTo(
                        Math.randomRange(0, stage.stageWidth),
                        Math.randomRange(0, stage.stageHeight),
                        Math.randomRange(0, stage.stageWidth),
                        Math.randomRange(0, stage.stageHeight));
                    break;
            }

            if (fill)
            {
                shape.graphics.endFill();
            }

            shape.x = Math.randomRange(0, stage.stageWidth);
            shape.y = Math.randomRange(0, stage.stageHeight);

            return shape;
        }

        override public function run():void
        {
            stage.scaleMode = StageScaleMode.LETTERBOX;
            stage.color = 0x888888;

            TextFormat.load("sans", "assets/SourceSansPro-Regular.ttf");

            textFormat = new TextFormat("sans", 20, 0xFFFFFF, false);

            time = LoomGroup.rootGroup.getManager(TimeManager) as TimeManager;
            frames = 0;
            totalTime = 0;
            avgDt = 1;

            for (var i = 0; i < SHAPE_NUM_START; i++)
            {
                stage.addChild(generateShape());
            }

            overlay = new Shape();
            stage.addChild(overlay);
            overlayFormat = new TextFormat("sans", 14, 0xFFFFFF, false);
            overlay.graphics.textFormat(overlayFormat);

            stage.addEventListener(TouchEvent.TOUCH, onTouch);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);

            qualityStr = getQualityStr();
        }

        /**
         * Touch middle to cycle over different vector rendering qualities
         * Touch top to increase shape count
         * Touch bottom to decrease shape count
         */
        private function onTouch(e:TouchEvent):void
        {
            var t:Touch = e.getTouch(stage, TouchPhase.BEGAN);
            if (!t) return;
            if (t.globalY < stage.stageHeight / 3)
            {
                // Add new n shapes
                for (var i = 0; i < SHAPE_INTERVAL; i++)
                    stage.addChildAt(generateShape(), stage.numChildren - 1);
            }
            else if (t.globalY > stage.stageHeight / 3 * 2)
            {
                // Remove first n shapes
                for (var j = 0; j < SHAPE_INTERVAL; j++)
                    if (stage.numChildren > 1)
                        stage.removeChildAt(stage.numChildren - 2);
                    else
                        break;
            }
            else if (t.globalY > stage.stageHeight / 3 && t.globalY < stage.stageHeight / 3 * 2)
            {
                // Change the rendering quality
                switch (stage.vectorQuality) {
                    case Stage.VECTOR_QUALITY_ANTIALIAS | Stage.VECTOR_QUALITY_STENCIL_STROKES:
                        stage.vectorQuality = Stage.VECTOR_QUALITY_ANTIALIAS;
                        trace("Vector quality set to ANTIALIAS");
                        break;
                    case Stage.VECTOR_QUALITY_ANTIALIAS:
                        stage.vectorQuality = Stage.VECTOR_QUALITY_STENCIL_STROKES;
                        trace("Vector quality set to STENCIL");
                        break;
                    case Stage.VECTOR_QUALITY_STENCIL_STROKES:
                        stage.vectorQuality = Stage.VECTOR_QUALITY_NONE;
                        trace("Vector quality set to NONE");
                        break;
                    default:
                        stage.vectorQuality = Stage.VECTOR_QUALITY_ANTIALIAS | Stage.VECTOR_QUALITY_STENCIL_STROKES;
                        trace("Vector quality set to ANTIALIAS and STENCIL");
                }
                qualityStr = getQualityStr();
            }
        }

        function keyDownHandler(event:KeyboardEvent):void
        {
            var keycode = event.keyCode;
            if (keycode == LoomKey.UP_ARROW)
            {
                if (stage.tessellationQuality < 10)
                    stage.tessellationQuality++;
            }
            else if (keycode == LoomKey.DOWN_ARROW)
            {
                if (stage.tessellationQuality > 1)
                    stage.tessellationQuality--;
            }
        }

        override public function onTick()
        {
            // FPS calculations
            frames++;
            totalTime += time.deltaTime;

            // half second average
            if (totalTime >= 0.5)
            {
                avgDt = totalTime / frames;
                frames = 0;
                totalTime = 0;
            }

            // Rotate the shapes so stuttering becomes visible
            for (var i = 0; i < stage.numChildren; i++)
            {
                var shape:Shape = stage.getChildAt(i) as Shape;

                if (shape == null)
                    continue;
                if (shape == overlay)
                    continue;

                shape.rotation += time.deltaTime;
            }
        }

        override public function onFrame()
        {
            overlay.graphics.clear();

            overlay.graphics.beginFill(0x000000);
            overlay.graphics.drawRect(0, 0, stage.stageWidth, 14);
            overlay.graphics.endFill();
            overlay.graphics.textFormat(overlayFormat);
            overlay.graphics.drawTextLine(0, 0, String.format("FPS: %0.1f Shapes: %d Quality: %s Tesselation max level: %d", 1 / avgDt, stage.numChildren - 1, qualityStr, stage.tessellationQuality));
        }

        private function getQualityStr():String
        {
            switch (stage.vectorQuality)
            {
                case Stage.VECTOR_QUALITY_ANTIALIAS | Stage.VECTOR_QUALITY_STENCIL_STROKES:
                    return "Antialias & Stencil";
                case Stage.VECTOR_QUALITY_ANTIALIAS:
                    return "Antialias";
                case Stage.VECTOR_QUALITY_STENCIL_STROKES:
                    return "Stencil";
                case Stage.VECTOR_QUALITY_NONE:
                    return "None";
            }

            return "<unknown>";
        }
    }
}

: