function init() {

    var canvas = document.getElementById("uno_canvas");
    canvas.style.outline = 'none';
    canvas.style.background = '#000';
    canvas.setAttribute("tabindex", "0");

    gl = null;

    try {
        gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    }
    catch (e) {
    }

    if (!gl) {
        throw "Failed to create WebGL context";
    }

    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame =
          window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'RequestCancelAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function (callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function () { callback(timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };

        var A = Uno.Application,
        AI = A.prototype,
        D = Uno.Delegate,
        DI = D.prototype,
        I = Uno.Isolate,
        started = false,
        startTime = 0,
        prevTime = 0,
        time = 0,
        fixedTime = 0,
        fixedStep = 1.0 / 60.0,
        fpsTime = 0,
        frameCount = 0,
        fps = -1,
        deltaTime = 0,
        currentDrawCallCount = 0,
        currentVertexCount = 0,
        lastDrawCallCount = 0,
        lastVertexCount = 0,
        cursor = 1,
        clearColor = Uno.Float4.$0(0, 0, 0, 0),
        pressedKeys = [],
        camera = Uno.Camera.Default(),
        mouseArgs = Uno.MouseEventArgs.$0(),
        keyArgs = Uno.KeyEventArgs.$0(),
        isMacOSX = navigator.appVersion.indexOf("Mac") != -1;

    (window.onresize = function () {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        camera.Aspect(canvas.width / canvas.height);
        gl.viewport(0, 0, canvas.width, canvas.height);
    })();

    I.GetTime = function() {
        return new Date().getTime() / 1000.0;
    }

    function tick() {
        gl.clearColor(clearColor.X, clearColor.Y, clearColor.Z, clearColor.W);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        window.requestAnimationFrame(tick);

        var ct = I.GetTime();

        if (!started) {
            canvas.style.display = 'none';
            //if (_loadingImageCount > 0) return;
            canvas.style.display = 'block';

            startTime = prevTime = ct;
            fixedTime = 0;
            started = true;
        }

        time = ct - startTime;
        deltaTime = ct - prevTime;
        prevTime = ct;

        frameCount += 1.0;
        fpsTime += deltaTime;

        lastDrawCallCount = currentDrawCallCount;
        lastVertexCount = currentVertexCount;
        currentDrawCallCount = 0;
        currentVertexCount = 0;

        if (fpsTime > 1.0) {
            fps = frameCount / fpsTime;
            fpsTime = 0.0;
            frameCount = 0.0;
        }

        while (fixedTime < time) {
            app.FixedUpdate();
            fixedTime += fixedStep;
        }

        app.Update();
        app.Draw();
    }

    function modifiers(event) {
        var m = 0;
        if (event.metaKey || !isMacOSX && event.ctrlKey) m |= 1 << 16;
        if (event.ctrlKey) m |= 1 << 17;
        if (event.shiftKey) m |= 1 << 18;
        if (event.altKey) m |= 1 << 19;
        return m;
    }

    function setupKey(event) {
        keyArgs.KeyData = event.which | modifiers(event);
        keyArgs.Handled = false;
        return keyArgs;
    }

    function setupMouse(event) {
        mouseArgs.Button = (function (b) {
            if (b == 3) return 2; //Uno.MouseButtons.Right;
            if (b == 2) return 4; //Uno.MouseButtons.Middle;
            return 1; //Uno.MouseButtons.Left;
        })(event.which);
        mouseArgs.Position = Uno.Int2.$0(event.clientX, event.clientY);
        mouseArgs.Delta = Uno.Int2.$0(0, event.wheelDelta);
        mouseArgs.Modifiers = modifiers(event);
        return mouseArgs;
    }

    canvas.addEventListener("mousemove", function (event) {
        if (A.MouseMove != null) A.MouseMove.Invoke(app, setupMouse(event));
    }, true);

    canvas.addEventListener("mousedown", function (event) {
        if (A.MouseDown != null) A.MouseDown.Invoke(app, setupMouse(event));
    }, true);

    canvas.addEventListener("mouseup", function (event) {
        if (A.MouseUp != null) A.MouseUp.Invoke(app, setupMouse(event));
    }, true);

    canvas.onmousewheel = function (event) {
        if (A.MouseWheel != null) A.MouseWheel.Invoke(app, setupMouse(event));
    };

    canvas.onkeydown = function (event) {
        //console.log("keydown: " + event.which);
        if (A.KeyDown != null) {
            pressedKeys.push(event.which);
            A.KeyDown.Invoke(app, setupKey(event));
            if (keyArgs.Handled) return false;
        }
    };

    canvas.onkeyup = function (event) {
        //console.log("keydown: " + event.which);
        if (A.KeyUp != null) {
            pressedKeys.pop(event.which);
            A.KeyUp.Invoke(app, setupKey(event));
        }
    };

    canvas.onblur = function (event) {
        for (var i = 0, l = pressedKeys.length; i < l; i++) {
            keyArgs.KeyData = pressedKeys[i];
            keyArgs.Handled = false;
            A.KeyUp.Invoke(app, keyArgs);
        }
        pressedKeys = [];
    };

    window.oncontextmenu = function () {
        return false;
    };

    window.onbeforeunload = function () {
        if (A.WindowClosing != null && !A.WindowClosing.Invoke()) 
            return "Leave this page?";
    };

    A.ClearColor = function (value) {
        if (value !== undefined) clearColor = value;
        return clearColor;
    };

    A.GLBackbufferViewport = function () { return Uno.Int4.$0(0, 0, canvas.width, canvas.height); };
    A.GLBackbufferFramebuffer = function () { return null; }

    // TODO: Fix
    // gl.getParameter is extremely slow! actually a major bottleneck when ViewportSize is polled frequently
    A.ViewportSize = function () { return A.WindowSize(); } // var vp = gl.getParameter(gl.VIEWPORT); return Uno.Int2.$0(vp[2], vp[3]); };

    A.WindowSize = function (value) { return Uno.Int2.$0(canvas.width, canvas.height); };

    A.WindowTitle = function (value) {
        if (value !== undefined) document.title = title;
        return document.title; 
    }

    A.Cursor = function (value) {
        if (value !== undefined) {
            cursor = value;
            canvas.style.cursor = function () {
                switch (value) {
                    case 1: return 'default';
                    case 2: return 'crosshair';
                    case 3: return 'pointer';
                    case 4: return 'help';
                    case 5: return 'move';
                    case 6: return 'wait';
                    case 7: return 'progress';
                    case 8: return 'resize-n';
                    case 9: return 'resize-e';
                    case 10: return 'resize-s';
                    case 11: return 'resize-w';
                    case 12: return 'resize-ne';
                    case 13: return 'resize-nw';
                    case 14: return 'resize-se';
                    case 15: return 'resize-sw';
                }
                return 'auto';
            } ();
        }
        return cursor;
    }

    A.Camera = function () { return camera; }

    A.Fps = function () { return fps; }
    A.Time = function () { return time; }
    A.DeltaTime = function () { return deltaTime; }
    A.FixedDeltaTime = function () { return fixedStep; }
    A.FixedUpdateRate = function (value) {
        if (value !== undefined) fixedStep = 1.0 / x;
        return 1.0 / fixedStep; 
    }
    A.MultiSamples = function () { return gl.getParameter(gl.SAMPLES); }
    A.CloseWindow = function () { close(); }

    A.DrawCallCount = function () { return lastDrawCallCount; }
    A.VertexCount = function () { return lastVertexCount; }
    A.IncrementDrawCallCounter = function (vc) { currentDrawCallCount++; currentVertexCount += vc; }

    A.$0 = function($) {
        return app = $;
    }

    AI.Main = function() {
        this.Load();
        this.DesignerInit();
        tick();
    };

    D.$0 = function (func, obj) {
        var $ = new D;
        $.f = func;
        $.o = obj || document;
        return $;
    }

    DI.GetMethodId = function () {
        return this.func;
    }

    DI.GetMethodObj = function () {
        return this.obj;
    }

    DI.Invoke = function () {
        if (this.Prev) this.Prev.Invoke.apply(this.Prev, arguments);
        return this.f.apply(this.o, arguments);
    }
};
