Source: ext/debug.js

"use strict";

/**
 * Engine debugging API.
 * @module debug
 * @local DebugViewMode
 * @local StageloadCallback
 * @local LoadedCallback
 * @local CodeTestCallback
 * @local EqualsFunction
 * @local OkFunction
 */
b4w.module["debug"] = function(exports, require) {

var m_cfg      = require("__config");
var m_compat   = require("__compat");
var m_ctl      = require("__controls");
var m_cont     = require("__container");
var m_debug    = require("__debug");
var m_ext      = require("__extensions");
var m_load     = require("__loader");
var m_obj      = require("__objects");
var m_obj_util = require("__obj_util");
var m_phy      = require("__physics");
var m_print    = require("__print");
var m_render   = require("__renderer");
var m_scenes   = require("__scenes");
var m_scgraph  = require("__scenegraph");
var m_sfx      = require("__sfx");
var m_shaders  = require("__shaders");
var m_subs     = require("__subscene");
var m_textures = require("__textures");
var m_util     = require("__util");
var m_vec3     = require("__vec3");

var cfg_def = m_cfg.defaults;

var PERF_NUM_CALLS = 5;
var EPS = 0.000001;

var _called_funcs = [];
var _tested_func_name = "";
var _test_result = true;
var _vec2_tmp = new Float32Array(2);
/**
 * Debug view mode.
 * @typedef DebugViewMode
 * @type {Number}
 */

 /**
 * Data loaded callback.
 * @callback LoadedCallback
 */

/**
 * Loading stage callback.
 * @callback StageloadCallback
 * @param {Number} percentage Loading progress (0-100).
 */

/**
 * Code test callback.
 * @callback CodeTestCallback
 * @param {EqualsFunction} equals –°omparison function.
 * @param {OkFunction} ok Code test function.
 */

/**
 * Return the comparison result of entrace params.
 * @callback EqualsFunction
 * @param {*} result Real function result.
 * @param {*} exp_result Expected result.
 */

 /**
 * Check code crash.
 * @callback OkFunction
 * @param {*} result Real function result.
 */


/**
 * Debug view mode: turn off debug view.
 * @const {DebugViewMode} module:debug.DV_NONE
 */
exports.DV_NONE = m_debug.DV_NONE;

/**
 * Debug view mode: turn on the black-and-white wireframe view.
 * @const {DebugViewMode} module:debug.DV_OPAQUE_WIREFRAME
 */
exports.DV_OPAQUE_WIREFRAME = m_debug.DV_OPAQUE_WIREFRAME;

/**
 * Debug view mode: turn on the transparent (superimposed on the source color) wireframe view.
 * @const {DebugViewMode} module:debug.DV_TRANSPARENT_WIREFRAME
 */
exports.DV_TRANSPARENT_WIREFRAME = m_debug.DV_TRANSPARENT_WIREFRAME;

/**
 * Debug view mode: turn on the wireframe view with the front/back faces coloration.
 * @const {DebugViewMode} module:debug.DV_FRONT_BACK_VIEW
 */
exports.DV_FRONT_BACK_VIEW = m_debug.DV_FRONT_BACK_VIEW;

/**
 * Debug view mode: turn on the debug spheres view.
 * @const {DebugViewMode} module:debug.DV_BOUNDINGS
 */
exports.DV_BOUNDINGS = m_debug.DV_BOUNDINGS;

/**
 * Debug view mode: turn on the clusters view.
 * @const {DebugViewMode} module:debug.DV_CLUSTERS_VIEW
 */
exports.DV_CLUSTERS_VIEW = m_debug.DV_CLUSTERS_VIEW;

/**
 * Debug view mode: turn on the batches view.
 * @const {DebugViewMode} module:debug.DV_BATCHES_VIEW
 */
exports.DV_BATCHES_VIEW = m_debug.DV_BATCHES_VIEW;

/**
 * Debug view mode: turn on the render time view.
 * @const {DebugViewMode} module:debug.DV_RENDER_TIME
 */
exports.DV_RENDER_TIME = m_debug.DV_RENDER_TIME;

/**
 * Print info about the physics worker.
 * @method module:debug.physics_stats
 */
exports.physics_stats = function() {
    m_phy.debug_workers();
}

/**
 * Print object info by physics ID.
 * @method module:debug.physics_id
 * @param {Number} id Physics ID
 */
exports.physics_id = function(id) {
    m_print.log("O", m_phy.find_obj_by_body_id(id))

    var bundles = m_phy.get_active_scene()._physics.bundles;

    for (var i = 0; i < bundles.length; i++) {
        var bundle = bundles[i];
        var phy = bundle.physics;

        if (phy.body_id == id)
            m_print.log("B", bundle);
    }
}

/**
 * Print names and info for objects inside the view frustum.
 * @method module:debug.visible_objects
 */
exports.visible_objects = function() {
    var scene = m_scenes.get_active();

    var objs = m_obj.get_scene_objs(scene, "MESH", m_obj.DATA_ID_ALL);

    var main_subscenes = [m_scenes.get_subs(scene, m_subs.MAIN_OPAQUE),
                          m_scenes.get_subs(scene, m_subs.MAIN_BLEND),
                          m_scenes.get_subs(scene, m_subs.MAIN_GLOW)];

    for (var i = 0; i < main_subscenes.length; i++) {
        var subs_main = main_subscenes[i];
        var draw_data = subs.draw_data;

        if (!draw_data.length)
            continue;

        m_print.group(subs_main.type, "DYNAMIC");

        for (var j = 0; j < objs.length; j++) {
            var obj = objs[j];
            var render = obj.render;

            if (render.type != "DYNAMIC")
                continue;

            var is_visible = false;

            for (var k = 0; k < draw_data.length; k++) {
                var bundles = draw_data[k].bundles;
                for (var m = 0; m < bundles.length; m++) {
                    var bundle = bundles[m];
                    if (bundle.do_render && bundle.obj_render == render) {
                        m_print.log_raw(obj.name, obj);
                        is_visible = true;
                        break;
                    }
                }
                if (is_visible)
                    break;
            }
        }

        m_print.groupEnd();

        m_print.groupCollapsed(subs_main.type, "STATIC");

        for (var j = 0; j < objs.length; j++) {
            var obj = objs[j];
            var render = obj.render;

            if (render.type == "DYNAMIC")
                continue;

            var is_visible = false;

            for (var k = 0; k < draw_data.length; k++) {
                var bundles = draw_data[k].bundles;
                for (var m = 0; m < bundles.length; m++) {
                    var bundle = bundles[m];
                    if (bundle.do_render && bundle.obj_render == render) {
                        m_print.log_raw(obj.name, obj);
                        is_visible = true;
                        break;
                    }
                }
                if (is_visible)
                    break;
            }
        }

        m_print.groupEnd();
    }
}

/**
 * Print debug info for the object with the given name
 * @method module:debug.object_info
 * @param {String} name Object name
 */
exports.object_info = function(name) {
    var scene = m_scenes.get_active();

    var objs = m_obj.get_scene_objs(scene, "MESH", m_obj.DATA_ID_ALL);

    for (var i = 0; i < objs.length; i++) {
        var obj = objs[i];

        if (obj.name != name)
            continue;

        m_print.log("Object", obj);

        var subscenes = m_scenes.get_all_subscenes(scene);

        for (var j = 0; j < subscenes.length; j++) {
            var subs = subscenes[j];
            var print_bundles = [];

            var draw_data = subs.draw_data;

            for (var k = 0; k < draw_data.length; k++) {
                var bundles = draw_data[k].bundles;
                for (var m = 0; m < bundles.length; m++) {
                    if (bundles[m].obj_render == obj.render)
                        print_bundles.push(bundles[m]);
                }
            }

            m_print.log("Subscene " + subs.type, print_bundles);
        }
    }
}

/**
 * Print debug info for the object with the given name
 * @method module:debug.objects_stat
 */
exports.objects_stat = function() {
    var scene = m_scenes.get_active();

    console.log("Armatures: " + m_obj.get_scene_objs(scene, "ARMATURE",
            m_obj.DATA_ID_ALL).length);
    console.log("Cameras: " + m_obj.get_scene_objs(scene, "CAMERA",
            m_obj.DATA_ID_ALL).length);
    console.log("Curves: " + m_obj.get_scene_objs(scene, "CURVE",
            m_obj.DATA_ID_ALL).length);
    console.log("Empties: " + m_obj.get_scene_objs(scene, "EMPTY",
            m_obj.DATA_ID_ALL).length);
    console.log("Lamps: " + m_obj.get_scene_objs(scene, "LAMP",
            m_obj.DATA_ID_ALL).length);
    console.log("Meshes: " + m_obj.get_scene_objs(scene, "MESH",
            m_obj.DATA_ID_ALL).length);
    console.log("Speakers: " + m_obj.get_scene_objs(scene, "SPEAKER",
            m_obj.DATA_ID_ALL).length);
}

/**
 * Return the number of vertices in the active scene.
 * @method module:debug.num_vertices
 * @returns {Number} The number of vertices.
 */
exports.num_vertices = function() {

    var num = 0;

    var scene = m_scenes.get_active();

    var main_subscenes = [m_scenes.get_subs(scene, m_subs.MAIN_OPAQUE),
                          m_scenes.get_subs(scene, m_subs.MAIN_BLEND),
                          m_scenes.get_subs(scene, m_subs.MAIN_GLOW)];

    for (var i = 0; i < main_subscenes.length; i++) {

        var subs = main_subscenes[i];

        if (!subs)
            continue;

        var draw_data = subs.draw_data;

        for (var j = 0; j < draw_data.length; j++) {
            var bundles = draw_data[j].bundles;
            for (var k = 0; k < bundles.length; k++) {

                var batch = bundles[k].batch;
                // NOTE: some objects (particles) do not have any submesh
                if (batch)
                    num += batch.num_vertices;
            }
        }
    }

    return num;
}

/**
 * Return the number of all triangles in the active scene.
 * @method module:debug.num_triangles
 * @returns {Number} The number of all triangles.
 */
exports.num_triangles = function() {

    var num = 0;

    var scene = m_scenes.get_active();

    var main_subscenes = [m_scenes.get_subs(scene, m_subs.MAIN_OPAQUE),
                          m_scenes.get_subs(scene, m_subs.MAIN_BLEND),
                          m_scenes.get_subs(scene, m_subs.MAIN_GLOW)];

    for (var i = 0; i < main_subscenes.length; i++) {

        var subs = main_subscenes[i];

        if (!subs)
            continue;

        var draw_data = subs.draw_data;

        for (var j = 0; j < draw_data.length; j++) {
            var bundles = draw_data[j].bundles;
            for (var k = 0; k < bundles.length; k++) {

                var batch = bundles[k].batch;
                // NOTE: some objects (particles) do not have any submesh
                if (batch)
                    num += batch.num_triangles;
            }
        }
    }

    return num;
}

/**
 * Return the number of batches in the main scenes.
 * @method module:debug.num_draw_calls
 * @returns {Number} The number of batches.
 */
exports.num_draw_calls = function() {

    var scene = m_scenes.get_active();

    var main_subscenes = m_scenes.subs_array(scene, [m_subs.MAIN_OPAQUE,
                                                     m_subs.MAIN_BLEND,
                                                     m_subs.MAIN_GLOW,
                                                     m_subs.MAIN_PLANE_REFLECT,
                                                     m_subs.MAIN_PLANE_REFLECT_BLEND]);

    var number = 0;
    for (var i = 0; i < main_subscenes.length; i++) {
        var subs = main_subscenes[i];
        var draw_data = subs.draw_data;
        for (var j = 0; j < draw_data.length; j++)
            number += draw_data[j].bundles.length;
    }

    var cube_reflect_subs = m_scenes.subs_array(scene,
                            [m_subs.MAIN_CUBE_REFLECT, m_subs.MAIN_CUBE_REFLECT_BLEND]);
    for (var i = 0; i < cube_reflect_subs.length; i++) {
        var subs = cube_reflect_subs[i];
        var draw_data = subs.draw_data;
        for (var j = 0; j < draw_data.length; j++)
            number += 6 * draw_data[j].bundles.length;
    }

    return number;
}

/**
 * Return the number of compiled shaders.
 * @method module:debug.num_shaders
 * @returns {Number} The number of compiled shaders.
 */
exports.num_shaders = function() {
    var compiled_shaders = m_shaders.get_compiled_shaders();
    return m_util.get_dict_length(compiled_shaders);
}

/**
 * Return geometry info in the main scenes.
 * @method module:debug.geometry_stats
 * @returns {Object} Geometry info.
 */
exports.geometry_stats = function() {

    var scene = m_scenes.get_active();
    var subscenes = m_scenes.get_all_subscenes(scene);
    var unique_batches = {};

    for (var i = 0; i < subscenes.length; i++) {

        var subs = subscenes[i];

        if (subs.type == m_subs.SINK || subs.type == m_subs.DEBUG_VIEW)
            continue;

        var draw_data = subs.draw_data;
        for (var j = 0; j < draw_data.length; j++) {
            var bundles = draw_data[j].bundles;
            for (var k = 0; k < bundles.length; k++) {
                var batch = bundles[k].batch;
                var render = bundles[k].obj_render;
                // NOTE: some objects (particles) do not have any submesh
                if (batch)
                    if (subs.type != m_subs.COLOR_PICKING && subs.type != m_subs.OUTLINE_MASK
                            || render.origin_selectable || render.origin_outlining)
                        unique_batches[batch.id] = batch;
            }
        }
    }

    var vbo_memory = 0;
    var ibo_memory = 0;

    for (var id in unique_batches) {
        var bufs_data = unique_batches[id].bufs_data;

        if (bufs_data.debug_ibo_bytes)
            ibo_memory += bufs_data.debug_ibo_bytes / (1024 * 1024);

        vbo_memory += bufs_data.debug_vbo_bytes / (1024 * 1024);
    }

    return {"vbo_memory": vbo_memory, "ibo_memory": ibo_memory};
}

/**
 * Return the number of unique textures in the main scenes.
 * @method module:debug.num_textures
 * @returns {Object} Textures info.
 */
exports.num_textures = function() {

    var tex_list = [];

    var memory = 0;

    var scene = m_scenes.get_active();

    var main_subscenes = [m_scenes.get_subs(scene, m_subs.MAIN_OPAQUE),
                          m_scenes.get_subs(scene, m_subs.MAIN_BLEND),
                          m_scenes.get_subs(scene, m_subs.MAIN_GLOW)];

    for (var i = 0; i < main_subscenes.length; i++) {

        var subs = main_subscenes[i];

        if (!subs)
            continue;

        var draw_data = subs.draw_data;
        for (var j = 0; j < draw_data.length; j++) {
            var bundles = draw_data[j].bundles;
            for (var k = 0; k < bundles.length; k++) {

                var batch = bundles[k].batch;
                // NOTE: some objects (particles) do not have any submesh
                if (batch) {
                    var batch_texs = batch.textures;

                    for (var m = 0; m < batch_texs.length; m++) {

                        var batch_tex = batch_texs[m];

                        if (batch_tex.source === "IMAGE" ||
                                batch_tex.source === "ENVIRONMENT_MAP") {

                            var tex = batch_tex.w_texture;

                            if (tex_list.indexOf(tex) === -1) {
                                tex_list.push(tex);
                                var mem = batch_tex.width * batch_tex.height *
                                    4 / (1024 * 1024) / batch_tex.compress_ratio;

                                // mipmaps
                                mem *=  1.3333;

                                memory += mem;
                            }
                        }
                    }
                }
            }
        }
    }

    return {"number": tex_list.length, "memory": memory};
}

/**
 * Return the number and the total size of unique output framebuffers.
 * @method module:debug.num_render_targets
 * @returns {Object} Render targets info.
 */
exports.num_render_targets = function() {

    var list = [];

    var memory = 0;

    var scene = m_scenes.get_active();

    var subscenes = m_scenes.get_all_subscenes(scene);

    for (var i = 0; i < subscenes.length; i++) {

        var subs = subscenes[i];

        if (subs.type == m_subs.SINK)
            continue;

        var cam = subs.camera;
        var c_at = cam.color_attachment;
        var d_at = cam.depth_attachment;

        var subs_textures = [cam.color_attachment, cam.depth_attachment];
        subs_textures.push.apply(subs_textures, subs.textures_internal);

        for (var j = 0; j < subs_textures.length; j++) {
            var tex = subs_textures[j];
            if (m_textures.is_texture(tex) && list.indexOf(tex) == -1) {
                list.push(tex);
                memory += cam.width * cam.height * m_textures.get_texture_texel_size(tex);
            }
        }
    }

    return {"number": list.length, "memory": (memory / 1024 / 1024)};
}

/**
 * Draw a frustum for the active camera.
 * @method module:debug.make_camera_frustum_shot
 */
exports.make_camera_frustum_shot = function() {

    var active_scene = m_scenes.get_active();
    var subs_main = m_scenes.get_subs(active_scene, m_subs.MAIN_OPAQUE);
    if (!subs_main)
        return;

    m_scenes.make_frustum_shot(subs_main.camera, subs_main, [1,1,0]);
}

/**
 * Draw a light frustum, used for rendering the shadow maps.
 * @method module:debug.make_light_frustum_shot
 */
exports.make_light_frustum_shot = function() {

    var active_scene = m_scenes.get_active();
    var subs_main = m_scenes.get_subs(active_scene, m_subs.MAIN_OPAQUE);
    var subscenes_shadow = m_scenes.subs_array(active_scene, [m_subs.SHADOW_CAST]);
    if (!subs_main)
        return;

    for (var i = 0; i < subscenes_shadow.length; i++) {
        var subs_shadow = subscenes_shadow[i];

        var color;
        switch (i) {
        case 0:
            color = [1, 0, 0];
            break;
        case 1:
            color = [0, 1, 0];
            break;
        case 2:
            color = [0, 0, 1];
            break;
        default:
            color = [1, 0, 1];
        }

        m_scenes.make_frustum_shot(subs_shadow.camera, subs_main, color);
    }
}


/**
 * Print info about the active scene graph in DOT format.
 * @method module:debug.scenegraph_to_dot
 */
exports.scenegraph_to_dot = function() {
    var scenes = m_scenes.get_all_scenes();

    for (var i = 0; i < scenes.length; i++) {
        var scene = scenes[i];
        var graph = m_scenes.get_graph(scene);
        m_print.log("\n" + m_scgraph.debug_convert_to_dot(graph));
    }
}

exports.scenes_to_dot = function() {

}

exports.loading_graph_to_dot = function(data_id) {
    data_id = data_id | 0;
    m_print.log("\n" + m_load.graph_to_dot(data_id));
}

/**
 * Print info about the controls module.
 * @method module:debug.controls_info
 */
exports.controls_info = m_ctl.debug;

/**
 * Get the distance between two objects.
 * @method module:debug.object_distance
 * @param {Object3D} obj The first object.
 * @param {Object3D} obj2 The second object.
 * @returns {Number} Distance.
 */
exports.object_distance = function(obj, obj2) {
    var trans = m_tsr.get_trans_view(obj.render.world_tsr);
    var trans2 = m_tsr.get_trans_view(obj2.render.world_tsr);
    var dist = m_vec3.dist(trans, trans2);
    return dist;
}

/**
 * Store a simple telemetry message.
 * @method module:debug.msg
 */
exports.msg = m_debug.msg;

/**
 * Store a flashback telemetry message.
 * @method module:debug.fbmsg
 */
exports.fbmsg = m_debug.fbmsg;

/**
 * Print the list of flashback messages.
 * @method module:debug.print_telemetry
 */
exports.print_telemetry = m_debug.print_telemetry;

/**
 * Plot the list of flashback messages as a gnuplot datafile.
 * @method module:debug.plot_telemetry
 */
exports.plot_telemetry = m_debug.plot_telemetry;

/**
 * Store the callback function result as a flashback message.
 * @method module:debug.fbres
 * @param {Function} fun fun
 * @param {Number} timeout timeout
 */
exports.fbres = function(fun, timeout) {
    if (!timeout)
        timeout = 16;

    var cb = function() {
        m_debug.fbmsg("FBRES", fun());
        setTimeout(cb, timeout);
    }

    cb();
}

/**
 * Check the engine constants, abort if not constant.
 * @method module:debug.assert_constants
 */
exports.assert_constants = function() {
    var VEC3_IDENT = new Float32Array(3);
    var QUAT4_IDENT = new Float32Array([0,0,0,1]);

    var AXIS_X = new Float32Array([1, 0, 0]);
    var AXIS_Y = new Float32Array([0, 1, 0]);
    var AXIS_Z = new Float32Array([0, 0, 1]);
    var AXIS_MX = new Float32Array([-1, 0, 0]);
    var AXIS_MY = new Float32Array([ 0,-1, 0]);
    var AXIS_MZ = new Float32Array([ 0, 0,-1]);

    if (!m_util.cmp_arr(VEC3_IDENT, m_util.VEC3_IDENT))
        throw "Wrong VEC3_IDENT";
    if (!m_util.cmp_arr(QUAT4_IDENT, m_util.QUAT4_IDENT))
        throw "Wrong QUAT4_IDENT";

    if (!m_util.cmp_arr(AXIS_X, m_util.AXIS_X))
        throw "Wrong AXIS_X";
    if (!m_util.cmp_arr(AXIS_Y, m_util.AXIS_Y))
        throw "Wrong AXIS_Y";
    if (!m_util.cmp_arr(AXIS_Z, m_util.AXIS_Z))
        throw "Wrong AXIS_Z";
    if (!m_util.cmp_arr(AXIS_MX, m_util.AXIS_MX))
        throw "Wrong AXIS_MX";
    if (!m_util.cmp_arr(AXIS_MY, m_util.AXIS_MY))
        throw "Wrong AXIS_MY";
    if (!m_util.cmp_arr(AXIS_MZ, m_util.AXIS_MZ))
        throw "Wrong AXIS_MZ";
}

/**
 * Mute the BACKGROUND_MUSIC speakers.
 * @method module:debug.mute_music
 */
exports.mute_music = function() {
    var spks = m_sfx.get_speaker_objects();

    for (var i = 0; i < spks.length; i++) {
        var spk = spks[i];

        if (m_sfx.get_spk_behavior(spk) == "BACKGROUND_MUSIC")
            m_sfx.mute(spk, true);
    }
}

/**
 * Check the object for a finite value.
 * @method module:debug.check_finite
 * @param {*} o Value
 */
exports.check_finite = function(o) {
    m_debug.check_finite(o);
}

/**
 * Set debugging parameters.
 * @method module:debug.set_debug_params
 * @param {DebugParams} params Debug parameters
 * @cc_externs debug_view_mode wireframe_edge_color debug_colors_seed
 */
exports.set_debug_params = function(params) {
    var active_scene = m_scenes.get_active();
    var subs_debug_view = m_scenes.get_subs(active_scene, m_subs.DEBUG_VIEW);

    if (subs_debug_view) {
        if (typeof params.debug_view_mode == "number") {
            switch (params.debug_view_mode) {
            case m_debug.DV_NONE:
            case m_debug.DV_OPAQUE_WIREFRAME:
            case m_debug.DV_TRANSPARENT_WIREFRAME:
            case m_debug.DV_FRONT_BACK_VIEW:
            case m_debug.DV_BOUNDINGS:
            case m_debug.DV_CLUSTERS_VIEW:
            case m_debug.DV_BATCHES_VIEW:
            case m_debug.DV_RENDER_TIME:
                m_scenes.set_debug_view_mode(subs_debug_view, params.debug_view_mode);
                break;
            default:
                m_print.error("set_debug_params(): Wrong debug view mode");
                break;
            }
        }
        if (typeof params.debug_colors_seed == "number")
            m_scenes.set_debug_colors_seed(subs_debug_view, params.debug_colors_seed);
        if (typeof params.render_time_threshold == "number")
            m_scenes.set_render_time_threshold(subs_debug_view, params.render_time_threshold);
        if (typeof params.wireframe_edge_color == "object")
            m_scenes.set_wireframe_edge_color(subs_debug_view,
                    m_util.f32(params.wireframe_edge_color));
    } else
        m_print.error("Debugging is not available on the scene.");
}

exports.get_error_quantity = function() {
    return m_print.get_error_count();
}

exports.get_warning_quantity = function() {
    return m_print.get_warning_count();
}

exports.clear_errors_warnings = function() {
    return m_print.clear_errors_warnings();
}

/**
 * Print shaders' statistics.
 * @method module:debug.analyze_shaders
 * @param {String} [opt_shader_id_part=""] Shader ID (filename) part.
 */
exports.analyze_shaders = function(opt_shader_id_part) {

    var compiled_shaders = m_shaders.get_compiled_shaders();

    var count = 0;
    for (var shader_id in compiled_shaders) {
        if (opt_shader_id_part && shader_id.indexOf(opt_shader_id_part) === -1)
            continue;
        count++;
    }
    var msg = "of " + count + " analyzing...";

    var rslts = {};

    for (var shader_id in compiled_shaders) {

        if (opt_shader_id_part && shader_id.indexOf(opt_shader_id_part) === -1)
            continue;

        var cshader = compiled_shaders[shader_id];
        var stat = get_shaders_stat(cshader.vshader, cshader.fshader);

        var shaders_info = cshader.shaders_info;
        var title = shaders_info.vert + " + " + shaders_info.frag;

        // NOTE: cshader.shaders_info
        stat.cshader = cshader;
        stat.shaders_info = shaders_info;

        var stats = rslts[title] = rslts[title] || [];

        stats.push(stat);
        m_print.log_raw(msg);
    }

    for (var title in rslts) {

        m_print.group("%c" + title, "color: #800");
        var stats = rslts[title];
        print_shader_stats(stats);
        m_print.groupEnd();
    }
}

/**
 * Return stage callback without loading data.
 * @method module:debug.fake_load
 * @param {StageloadCallback} stageload_cb Callback to report about the loading progress
 * @param {Number} [interval=5000] Loading interval
 * @param {Number} [start=0] Start percentage
 * @param {Number} [end=5000] End percentage
 * @param {LoadedCallback} [loaded_cb=null] Callback to be executed right after load
 */
exports.fake_load = m_debug.fake_load;

function get_shaders_stat(vshader, fshader) {

    var ext_ds = m_ext.get_debug_shaders();
    if (!ext_ds) {
        m_print.error("WEBGL_debug_shaders extension not found");
        return;
    }

    var vsrc_trans = ext_ds.getTranslatedShaderSource(vshader);
    var fsrc_trans = ext_ds.getTranslatedShaderSource(fshader);

    if (m_compat.detect_mobile()) {
        vsrc_trans = vsrc_trans.replace("#version", "#version 300 //")
        fsrc_trans = fsrc_trans.replace("#version", "#version 300 //")
        var vout = post_sync("/analyze_shader/vert_gles", vsrc_trans);
        var fout = post_sync("/analyze_shader/frag_gles", fsrc_trans);
    } else {
        // HACK: lower GLSL version for cgc tool
        vsrc_trans = vsrc_trans.replace("#version", "#version 400 //")
        fsrc_trans = fsrc_trans.replace("#version", "#version 400 //")
        var vout = post_sync("/analyze_shader/vert", vsrc_trans);
        var fout = post_sync("/analyze_shader/frag", fsrc_trans);
    }

    var vstats = parse_shader_assembly(vout);
    var fstats = parse_shader_assembly(fout);

    return {
        vsrc: m_debug.get_gl().getShaderSource(vshader),
        vsrc_trans: vsrc_trans,
        vout: vout,
        vstats: vstats,
        fsrc: m_debug.get_gl().getShaderSource(fshader),
        fsrc_trans: fsrc_trans,
        fout: fout,
        fstats: fstats
    };
}

function parse_shader_assembly(data) {
    var stats = {};

    if (!data)
        return stats;

    var lines = data.split("\n");

    for (var i = 0; i < lines.length; i++) {
        var line = lines[i];

        if (line.search(new RegExp(/^[A-Z.]+ ?/)) == -1) {
            continue;
        }

        var op = line.split(" ")[0];

        if (!(op in stats))
            stats[op] = 0;

        stats[op]++;
    }

    var all_ops = 0;
    var tex_ops = 0;
    var attribs = 0;

    for (var op in stats) {
        switch (op) {
        case "KIL":
        case "TEX":
        case "TXB":
        case "TXP":
        case "KIL.F":
        case "TEX.F":
        case "TXB.F":
        case "TXD.F":
        case "TXL.F":
        case "TXQ.F":
        case "TXP.F":
            tex_ops += stats[op];
            all_ops += stats[op];
            break;
        // data type qualifiers
        case "ATTRIB":
            attribs += stats[op];
            break;
        case "ADDRESS":
        case "PARAM":
        case "TEMP":
        case "ALIAS":
        case "OUTPUT":
            break;
        // end program line
        case "END":
            break;
        default:
            all_ops += stats[op];
            break;
        }
    }

    stats["ALL_OPS"] = all_ops;
    stats["TEX_OPS"] = tex_ops;
    stats["ATTRIBS"] = attribs;

    return stats;
}

function post_sync(path, data) {
    var req = new XMLHttpRequest();
    req.open("POST", path, false);
    req.send(data);

    if (req.status == 200)
        return req.responseText;
    else {
        m_print.error(req.responseText);
        throw("Error POST XHR: " + req.status);
    }
}

function print_shader_stats(stats) {
    // sort in descending order by fragment shader operations
    stats.sort(function(a, b) {
        return b.fstats["ALL_OPS"] - a.fstats["ALL_OPS"];
    })

    for (var j = 0; j < stats.length; j++) {
        var stat = stats[j];

        var fstats = stat.fstats;
        var vstats = stat.vstats;

        var mat_names = find_material_names_by_comp_shader(stat.cshader);
        mat_names = mat_names.length ? "\t\t(" + mat_names.join(", ") + ")" : "\t\t(NA)";

        // NOTE some not changing params are commented out
        m_print.groupCollapsed(
            "VERT ->",
            "OPS", vstats["ALL_OPS"],
            "ATT", vstats["ATTRIBS"],
            "TEX", vstats["TEX_OPS"],

            "\t\tFRAG ->",
            "OPS", fstats["ALL_OPS"],
            "TEX", fstats["TEX_OPS"],
            mat_names
        );

        m_print.groupCollapsed("directives");
        // NOTE: perhaps they should be stored in sorted order
        var dirs = stat.shaders_info.directives.slice().sort();
        for (var i = 0; i < dirs.length; i++) {
            var dir = dirs[i];
            m_print.log_raw(dir[0], dir[1]);
        }
        m_print.groupEnd();

        m_print.groupCollapsed("node elements");
        var nelem = stat.shaders_info.node_elements;
        for (var i = 0; i < nelem.length; i++)
            m_print.log_raw(nelem[i]);
        m_print.groupEnd();

        m_print.groupCollapsed("vert source");
        m_print.log_raw(stat.vsrc);
        m_print.groupEnd();

        m_print.groupCollapsed("vert translated source");
        m_print.log_raw(stat.vsrc_trans);
        m_print.groupEnd();

        // ignore them as they used for collective stats
        var ignored_stats = ["ALL_OPS", "TEX_OPS", "ATTRIBS"];

        m_print.groupCollapsed("vert ops stats");
        for (var op in vstats)
            if (ignored_stats.indexOf(op) == -1)
                m_print.log_raw(op, vstats[op]);
        m_print.groupEnd();

        m_print.groupCollapsed("vert assembly");
        m_print.log_raw(stat.vout);
        m_print.groupEnd();

        m_print.groupCollapsed("frag source");
        m_print.log_raw(stat.fsrc);
        m_print.groupEnd();

        m_print.groupCollapsed("frag translated source");
        m_print.log_raw(stat.fsrc_trans);
        m_print.groupEnd();

        m_print.groupCollapsed("frag ops stats");
        for (var op in fstats)
            if (ignored_stats.indexOf(op) == -1)
                m_print.log_raw(op, fstats[op]);
        m_print.groupEnd();

        m_print.groupCollapsed("frag assembly");
        m_print.log_raw(stat.fout);
        m_print.groupEnd();

        m_print.groupEnd();
    }
}

function find_material_names_by_comp_shader(cshader) {

    var names = [];

    var scenes = m_scenes.get_all_scenes();

    for (var i = 0; i < scenes.length; i++) {
        var scene = scenes[i];
        var objects = m_obj.get_scene_objs(scene, "MESH", m_obj.DATA_ID_ALL);

        for (var j = 0; j < objects.length; j++) {
            var obj = objects[j];
            var scene_data = m_obj_util.get_scene_data(obj, scene);

            if (!scene_data || !scene_data.batches.length)
                continue;

            var batches = scene_data.batches;

            for (var k = 0; k < batches.length; k++) {
                var batch = batches[k];

                if (batch.shader == cshader)
                    for (var l = 0; l < batch.material_names.length; l++) {
                        var name = batch.material_names[l];
                        if (names.indexOf(name) == -1)
                            names.push(name);
                    }
            }
        }
    }

    return names;
}

/**
 * Perform simple performance test.
 * @method module:debug.test_performance
 * @param {TestPerformanceCallback} callback Callback
 */
exports.test_performance = function(callback) {
    // waiting for shaders
    if (!m_shaders.check_shaders_loaded()) {
        window.setTimeout(function() {
            exports.test_performance(callback);
        }, 100);
        return;
    }

    var ext = m_ext.get_disjoint_timer_query();
    if (!ext) {
        callback(0, 0);
        return;
    }

    var gl_debug_save = cfg_def.gl_debug;
    // enable it to force timer queries update for
    // paused engine / unloaded scenes
    cfg_def.gl_debug = true;

    var graph = m_scgraph.create_performance_graph();
    m_scenes.generate_auxiliary_batches(null, graph);

    var subs = m_scgraph.find_subs(graph, m_subs.PERFORMANCE);
    var cam = subs.camera;

    for (var i = 0; i < PERF_NUM_CALLS; i++)
        m_render.draw(subs);

    cfg_def.gl_debug = gl_debug_save;

    window.setTimeout(function() {
        m_debug.process_timer_queries(subs);
        // in ms
        var time = subs.debug_render_time;
        // in GB/s (100 texture lookups)
        var bandwidth = (cam.width * cam.height * 4 * 10) /
                (time / 1000) / Math.pow(10, 9);
        callback(time, bandwidth);
    }, 100);
}

exports.calc_vbo_garbage_byte_size = m_debug.calc_vbo_garbage_byte_size;

exports.show_vbo_garbage_info = m_debug.show_vbo_garbage_info;

function call(func, name) {
    var decor_func = function() {
        _called_funcs.push(decor_func);
        _tested_func_name = name;
        return func.apply(func, arguments);

    }
    return decor_func;
}

exports.start_debug = function(module_name) {
    _called_funcs = [];
    _test_result = true;
    var module = require(module_name);
    for (var name in module)
        if (typeof module[name] === "function")
            module[name] = call(module[name], name);
}
exports.check_debug_result = function() {
    return _test_result;
}
/**
 * Test code.
 * @method module:debug.test
 * @param {String} test_name Test name
 * @param {CodeTestCallback} callback Callback
 */
exports.test = function(test_name, callback) {
    try {
        callback();
        return true;
    } catch(e) {
        _test_result = false;
        console.error(test_name + " test was failed. ", e);
        return false;
    }
}

/**
 * Compare color picked at the center of the screen with reference RGBA vector.
 * @param {RGBA} ref_color Reference RGBA vector to compare with.
 */
exports.pix = function(ref_color) {
    var canvas_w = m_cont.get_viewport_width();
    var canvas_h = m_cont.get_viewport_height();

    var canvas_x = canvas_w / 2;
    var canvas_y = canvas_h / 2;

    m_cont.resize(canvas_w, canvas_h, false);

    var scene = m_scenes.get_active();
    var graph = scene._render.graph;
    var subs = m_scgraph.find_on_screen(graph);
    if (!subs)
        m_util.panic("Couldn't find onscreen subscene");

    var cam = subs.camera;
    var viewport_xy = m_cont.canvas_to_viewport_coords(canvas_x, canvas_y,
            _vec2_tmp, subs.camera);

    viewport_xy[1] = cam.height - viewport_xy[1];
    var color = m_render.read_pixels(cam.framebuffer, viewport_xy[0],
            viewport_xy[1]);

    eqv(ref_color, color, 1);
}

exports.eqs = function(result, exp_result) {
    if (JSON.stringify(result) != JSON.stringify(exp_result))
        throw "Wrong result. Function: " + _tested_func_name;
}

exports.eqv = eqv;
function eqv(result, exp_result, eps) {
    if (typeof exp_result != typeof result)
        throw "Wrong expected data type.";
    if (result.length != exp_result.length)
        throw "Wrong expected vector length.";
    var eps = eps ? eps : EPS;
    for (var i = 0; i < result.length; i++)
        if (exp_result[i] > result[i] + eps || exp_result[i] < result[i] - eps)
            throw "Wrong result.";
}

exports.eqf = function(result, exp_result, eps) {
    if (typeof exp_result != "number")
        throw "Wrong expected data type.";
    var eps = eps ? eps : EPS;
    if (exp_result > result + eps || exp_result < result - eps)
        throw "Wrong result.";
}

exports.eq = function(result, exp_result) {
    if (result !== exp_result)
        throw "Wrong result.";
}

exports.stat = function(module_name) {
    var module = require(module_name);
    for (var name in module)
        if (_called_funcs.indexOf(module[name]) == -1)
            console.warn(name + " function wasn't called.");
}

exports.ok = function(exp) {
    if (!Boolean(exp))
        throw "Wrong result. Function: " + _tested_func_name;
}

}