SceneJS_webgl_Program = function(hash, context, vertexSources, fragmentSources, logging) {
    
    var a, i, u, u_name, location, shader;
    
    this.hash = hash;

    /* Create shaders from sources
     */
    var shaders = [];
    for (i = 0; i < vertexSources.length; i++) {
        shaders.push(new SceneJS_webgl_Shader(context, context.VERTEX_SHADER, vertexSources[i], logging));
    }
    for (i = 0; i < fragmentSources.length; i++) {
        shaders.push(new SceneJS_webgl_Shader(context, context.FRAGMENT_SHADER, fragmentSources[i], logging));
    }

    /* Create program, attach shaders, link and validate program
     */
    var handle = context.createProgram();

    for (i = 0; i < shaders.length; i++) {
        shader = shaders[i];
        if (shader.valid) {
            context.attachShader(handle, shader.handle);
        }
    }
    context.linkProgram(handle);

    this.valid = true;

    this.valid = this.valid && (context.getProgramParameter(handle, context.LINK_STATUS) != 0);

    var debugCfg = SceneJS_debugModule.getConfigs("shading");
    if (debugCfg.validate !== false) {
        context.validateProgram(handle);

        this.valid = this.valid && (context.getProgramParameter(handle, context.VALIDATE_STATUS) != 0);
    }

    if (!this.valid) {
        logging.debug("Program link failed: " + context.getProgramInfoLog(handle));
        throw SceneJS_errorModule.fatalError(
                SceneJS.errors.SHADER_LINK_FAILURE, "Shader program failed to link");
    }

    /* Discover active uniforms and samplers
     */
    var uniforms = {};
    var samplers = {};

    var numUniforms = context.getProgramParameter(handle, context.ACTIVE_UNIFORMS);

    /* Patch for http://code.google.com/p/chromium/issues/detail?id=40175)  where
     * gl.getActiveUniform was producing uniform names that had a trailing NUL in Chrome 6.0.466.0 dev
     * Issue ticket at: https://xeolabs.lighthouseapp.com/projects/50643/tickets/124-076-live-examples-blank-canvas-in-chrome-5037599
     */
    for (i = 0; i < numUniforms; ++i) {
        u = context.getActiveUniform(handle, i);
        if (u) {
            u_name = u.name;
            if (u_name[u_name.length - 1] == "\u0000") {
                u_name = u_name.substr(0, u_name.length - 1);
            }
            location = context.getUniformLocation(handle, u_name);
            if ((u.type == context.SAMPLER_2D) || (u.type == context.SAMPLER_CUBE) || (u.type == 35682)) {

                samplers[u_name] = new SceneJS_webgl_ProgramSampler(
                        context,
                        handle,
                        u_name,
                        u.type,
                        u.size,
                        location,
                        logging);
            } else {
                uniforms[u_name] = new SceneJS_webgl_ProgramUniform(
                        context,
                        handle,
                        u_name,
                        u.type,
                        u.size,
                        location,
                        logging);
            }
        }
    }

    /* Discover attributes
     */
    var attributes = {};

    var numAttribs = context.getProgramParameter(handle, context.ACTIVE_ATTRIBUTES);
    for (i = 0; i < numAttribs; i++) {
        a = context.getActiveAttrib(handle, i);
        if (a) {
            location = context.getAttribLocation(handle, a.name);
            attributes[a.name] = new SceneJS_webgl_ProgramAttribute(
                    context,
                    handle,
                    a.name,
                    a.type,
                    a.size,
                    location,
                    logging);
        }
    }

    this.setProfile = function(profile) {
        this._profile = profile;
    };

    this.bind = function() {
        context.useProgram(handle);
        if (this._profile) {
            this._profile.program++;
        }
    };

    this.getUniformLocation = function(name) {
        var u = uniforms[name];
        if (u) {
            return u.getLocation();
        } else {
            // SceneJS_loggingModule.warn("Uniform not found in shader : " + name);
        }
    };

    this.getUniform = function(name) {
        var u = uniforms[name];
        if (u) {
            return u;
        } else {
            //      SceneJS_loggingModule.warn("Shader uniform load failed - uniform not found in shader : " + name);
        }
    };

    this.setUniform = function(name, value) {
        var u = uniforms[name];
        if (u) {
            u.setValue(value);
            if (this._profile) {
                this._profile.uniform++;
            }
        } else {
            //      SceneJS_loggingModule.warn("Shader uniform load failed - uniform not found in shader : " + name);
        }
    };

    this.getAttribute = function(name) {
        var attr = attributes[name];
        if (attr) {
            return attr;
        } else {
            //  logging.warn("Shader attribute bind failed - attribute not found in shader : " + name);
        }
    };

    this.bindFloatArrayBuffer = function(name, buffer) {
        var attr = attributes[name];
        if (attr) {
            attr.bindFloatArrayBuffer(buffer);
            if (this._profile) {
                this._profile.varying++;
            }
        } else {
            //  logging.warn("Shader attribute bind failed - attribute not found in shader : " + name);
        }
    };

    this.bindTexture = function(name, texture, unit) {
        var sampler = samplers[name];
        if (sampler) {
            if (this._profile) {
                this._profile.texture++;
            }
            return sampler.bindTexture(texture, unit);
        } else {
            return false;
        }
    };

    this.unbind = function() {
        //     context.useProgram(0);
    };

    this.destroy = function() {
        if (this.valid) {
            //   logging.debug("Destroying shader program: '" + hash + "'");
            context.deleteProgram(handle);
            for (var s in shaders) {
                context.deleteShader(shaders[s].handle);
            }
            attributes = null;
            uniforms = null;
            samplers = null;
            this.valid = false;
        }
    };
}
A program on an active WebGL context

@private
@param hash SceneJS-managed ID for program
@param context WebGL context
@param vertexSources Source codes for vertex shaders
@param fragmentSources Source codes for fragment shaders
@param logging Program and shaders will write to logging's debug channel as they compile and link