API Docs for:
Show:

File: ../src/resources/shaderCode.js


/**
* ShaderCode is a resource containing all the code associated to a shader
* It is used to define special ways to render scene objects, having full control of the rendering algorithm
* Having a special class helps to parse the data in advance and share it between different materials
* 
* @class ShaderCode
* @constructor
*/

function ShaderCode( code )
{
	this._code = null;

	this._functions = {};
	this._global_uniforms = {};
	this._code_parts = {};
	this._subfiles = {};
	this._compiled_shaders = {};

	this._shaderblock_flags_num = 0; //used to assign flags to dependencies
	this._shaderblock_flags = {};

	this._version = 0;

	if(code)
		this.code = code;
}

ShaderCode.help_url = "https://github.com/jagenjo/litescene.js/blob/master/guides/shaders.md";

//block types
ShaderCode.CODE = 1;
ShaderCode.PRAGMA = 2;

//pargma types
ShaderCode.INCLUDE = 1;
ShaderCode.SHADERBLOCK = 2;
ShaderCode.SNIPPET = 3;

Object.defineProperty( ShaderCode.prototype, "code", {
	enumerable: true,
	get: function() {
		return this._code;
	},
	set: function(v) {
		if(this._code == v)
			return;
		this._code = v;
		this.processCode();
	}
});

//parse the code
//store in a easy to use way
ShaderCode.prototype.processCode = function()
{
	var code = this._code;
	this._global_uniforms = {};
	this._code_parts = {};
	this._compiled_shaders = {};
	this._functions = {};
	this._shaderblock_flags_num = 0;
	this._shaderblock_flags = {};
	this._has_error = false;

	var subfiles = GL.processFileAtlas( this._code );
	this._subfiles = subfiles;

	var num_subfiles = 0;
	var init_code = null; 

	for(var i in subfiles)
	{
		var subfile_name = i;
		var subfile_data = subfiles[i];
		num_subfiles++;

		if(!subfile_name)
			continue;

		if(subfile_name == "js")
		{
			init_code = subfile_data;
			continue;
		}

		//used to declare uniforms without using javascript
		if(subfile_name == "uniforms")
		{
			var lines = subfile_data.split("/n");
			for(var j = 0; j < lines.length; ++j)
			{
				var line = lines[j].trim();
				var words = line.split(" ");
				var varname = words[0];
				var uniform_name = words[1];
				var property_type = words[2];
				var value = words[3];
				if( value !== undefined )
					value = LS.stringToValue(value);
				var options = null;
				var options_index = line.indexOf("{");
				if(options_index != -1)
					options = LS.stringToValue( line.substr(options_index) );
				this._global_uniforms[ varname ] = { name: varname, uniform: uniform_name, type: property_type, value: value, options: options };
			}
			continue;
		}

		var name = LS.ResourcesManager.removeExtension( subfile_name );
		if(name == "default")
			name = "color"; //LEGACY fix
		var extension = LS.ResourcesManager.getExtension( subfile_name );

		if(extension == "vs" || extension == "fs")
		{
			var code_part = this._code_parts[name];
			if(!code_part)
				code_part = this._code_parts[name] = {};

			//parse data (extract pragmas and stuff)
			var glslcode = new GLSLCode( subfile_data );
			for(var j in glslcode.blocks)
			{
				var pragma_info = glslcode.blocks[j];
				if(!pragma_info || pragma_info.type != ShaderCode.PRAGMA)
					continue;
				//assign a flag position in case this block is enabled
				pragma_info.shader_block_flag = this._shaderblock_flags_num; 
				this._shaderblock_flags[ pragma_info.shader_block ] = pragma_info.shader_block_flag;
				this._shaderblock_flags_num += 1;
			}

			code_part[ extension ] = glslcode;
		}
	}

	//compile the shader before using it to ensure there is no errors
	var shader = this.getShader();
	if(!shader)
		return;

	//process init code
	if(init_code)
	{
		//clean code
		init_code = LS.ShaderCode.removeComments(init_code);

		if(init_code) //still some code? (we test it because if there is a single line of code the behaviour changes)
		{
			if(LS.catch_exceptions)
			{
				try
				{
					this._functions.init = new Function( init_code );
				}
				catch (err)
				{
					LS.dispatchCodeError( err, LScript.computeLineFromError(err), this );
				}
			}
			else
				this._functions.init = new Function( init_code );
		}
	}

	//check that all uniforms are correct
	this.validatePublicUniforms( shader );


	//to alert all the materials out there using this shader that they must update themselves.
	LEvent.trigger( LS.ShaderCode, "modified", this );
	this._version += 1;
}

//used when storing/retrieving the resource
ShaderCode.prototype.setData = function(v, skip_modified_flag)
{
	this.code = v;
	if(!skip_modified_flag)
		this._modified = true;
}

ShaderCode.prototype.getData = function()
{
	return this._code;
}

ShaderCode.prototype.getDataToStore = function()
{
	return this._code;
}

//compile the shader, cache and return
ShaderCode.prototype.getShader = function( render_mode, block_flags )
{
	if( this._has_error )
		return null;

	render_mode = render_mode || "color";
	block_flags = block_flags || 0;

	//search for a compiled version of the shader (by render_mode and block_flags)
	var shaders_map = this._compiled_shaders[ render_mode ];
	if(shaders_map)
	{
		var shader = shaders_map.get( block_flags );
		if(shader)
			return shader;
	}

	//search for the code
	var code = this._code_parts[ render_mode ];
	if(!code)
		return null;

	var context = {}; //used to store metaprogramming defined vars in the shader

	//compute context defines
	for(var i = 0; i < LS.ShadersManager.num_shaderblocks; ++i)
	{
		if( !(block_flags & 1<<i) ) //is flag enabled
			continue;
		var shader_block = LS.ShadersManager.shader_blocks.get(i);
		if(!shader_block)
			continue; //???
		if(!shader_block.enabled_defines)
			continue;
		for(var j in shader_block.enabled_defines)
			context[ j ] = shader_block.enabled_defines[j];
	}

	//vertex shader code
	var vs_code = null;
	if(render_mode == "fx")
		vs_code = GL.Shader.SCREEN_VERTEX_SHADER;
	else if( !code.vs )
		return null;
	else
		vs_code = code.vs.getFinalCode( GL.VERTEX_SHADER, block_flags, context );

	//fragment shader code
	if( !code.fs )
		return;

	var fs_code = code.fs.getFinalCode( GL.FRAGMENT_SHADER, block_flags, context );

	//no code or code includes something missing
	if(!vs_code || !fs_code) 
	{
		this._has_error = true;
		return null;
	}

	//compile the shader and return it
	var shader = this.compileShader( vs_code, fs_code );
	if(!shader)
		return null;

	//cache as render_mode,flags
	if( !this._compiled_shaders[ render_mode ] )
		this._compiled_shaders[ render_mode ] = new Map();
	this._compiled_shaders[ render_mode ].set( block_flags, shader );

	return shader;
}

ShaderCode.prototype.compileShader = function( vs_code, fs_code )
{
	if( this._has_error )
		return null;

	if(!LS.catch_exceptions)
		return new GL.Shader( vs_code, fs_code );
	else
	{
		try
		{
			return new GL.Shader( vs_code, fs_code );
		}
		catch(err)
		{
			this._has_error = true;
			LS.ShadersManager.dumpShaderError( this.filename, err, vs_code, fs_code );
			var error_info = GL.Shader.parseError( err, vs_code, fs_code );
			var line = error_info.line_number;
			var lines = this._code.split("\n");
			var code_line = -1;
			if(error_info.line_code)
			{
				var error_line_code = error_info.line_code.trim();
				for(var i = 0; i < lines.length; ++i)
					lines[i] = lines[i].trim();
				code_line = lines.indexOf( error_line_code ); //bug: what if this line is twice in the code?...
			}
			LS.dispatchCodeError( err, code_line, this, "shader" );
		}
	}
	return null;
}

ShaderCode.prototype.validatePublicUniforms = function( shader )
{
	if(!shader)
		throw("ShaderCode: Shader cannot be null");

	for( var i in this._global_uniforms )
	{
		var property_info = this._global_uniforms[i];
		var uniform_info = shader.uniformInfo[ property_info.uniform ];
		if(!uniform_info)
		{
			info.disabled = true;
			continue;
		}
	}
}


//makes this resource available 
ShaderCode.prototype.register = function()
{
	LS.ResourcesManager.registerResource( this.fullpath || this.filename, this );
}

//searches for materials using this ShaderCode and forces them to be updated (update the properties)
ShaderCode.prototype.applyToMaterials = function( scene )
{
	scene = scene || LS.GlobalScene;
	var filename = this.fullpath || this.filename;

	//materials in the resources
	for(var i in LS.ResourcesManager.resources)
	{
		var res = LS.ResourcesManager.resources[i];
		if( res.constructor !== LS.ShaderMaterial || res._shader != filename )
			continue;

		res.processShaderCode();
	}

	//embeded materials
	var nodes = scene.getNodes();
	for(var i = 0; i < nodes.length; ++i)
	{
		var node = nodes[i];
		if(node.material && node.material.constructor === LS.ShaderMaterial && node.material._shader == filename )
			node.material.processShaderCode();
	}
}

//used in editor
ShaderCode.prototype.hasEditableText = function() { return true; }

ShaderCode.removeComments = function( code )
{
	// /^\s*[\r\n]/gm
	return code.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, '');
}

//parses ShaderLab (unity) syntax
ShaderCode.parseShaderLab = function( code )
{
	var root = {};
	var current = root;
	var current_token = [];
	var stack = [];
	var mode = 0;
	var current_code = "";

	var lines = ShaderCode.removeComments( code ).split("\n");
	for(var i = 0; i < lines.length; ++i)
	{
		var line = lines[i].trim();
		var words = line.match(/[^\s"]+|"([^"]*)"/gi);
		if(!words)
			continue;

		if(mode != 0)
		{
			var w = words[0].trim();
			if(w == "ENDGLSL" || w == "ENDCG" )
			{
				mode = 0;
				current.codetype = mode;
				current.code = current_code;
				current_code = "";
			}
			else
			{
				current_code += line + "\n";
			}
			continue;
		}

		for(var j = 0; j < words.length; ++j)
		{
			var w = words[j];

			if(w == "{")
			{
				var node = {
					name: current_token[0], 
					params: current_token.slice(1).join(" "),
					content: {}
				};
				current[ node.name ] = node;
				current_token = [];
				stack.push( current );
				current = node.content;
			}
			else if(w == "}")
			{
				if(stack.length == 0)
				{
					console.error("error parsing ShaderLab code, the number of { do not matches the }");
					return null;
				}
				if(current_token.length)
				{
					current[ current_token[0] ] = current_token.join(" ");
					current_token = [];
				}
				current = stack.pop();
			}
			else if(w == "{}")
			{
				var node = {
					name: current_token[0], 
					params: current_token.slice(1).join(" "),
					content: {}
				};
				current[ node.name ] = node;
				current_token = [];
			}
			else if(w == "GLSLPROGRAM" || w == "CGPROGRAM" )
			{
				if( w == "GLSLPROGRAM" )
					mode = 1;
				else
					mode = 2;
				current_code = "";
			}
			else 
				current_token.push(w);
		}
	}

	return root;
}


LS.ShaderCode = ShaderCode;
LS.registerResourceClass( ShaderCode );

// now some shadercode examples that could be helpful

//Example code for a shader (used in editor) **************************************************
ShaderCode.examples = {};

ShaderCode.examples.fx = "\n\
\\fx.fs\n\
	precision highp float;\n\
	\n\
	uniform float u_time;\n\
	uniform vec4 u_viewport;\n\
	uniform sampler2D u_texture;\n\
	varying vec2 v_coord;\n\
	void main() {\n\
		gl_FragColor = texture2D( u_texture, v_coord );\n\
	}\n\
";

ShaderCode.examples.color = "\n\
\n\
\\js\n\
//define exported uniforms from the shader (name, uniform, widget)\n\
this.createUniform(\"Number\",\"u_number\",\"number\");\n\
this.createSampler(\"Texture\",\"u_texture\");\n\
\n\
\\color.vs\n\
\n\
precision mediump float;\n\
attribute vec3 a_vertex;\n\
attribute vec3 a_normal;\n\
attribute vec2 a_coord;\n\
\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
\n\
//matrices\n\
uniform mat4 u_model;\n\
uniform mat4 u_normal_model;\n\
uniform mat4 u_view;\n\
uniform mat4 u_viewprojection;\n\
\n\
//globals\n\
uniform float u_time;\n\
uniform vec4 u_viewport;\n\
uniform float u_point_size;\n\
\n\
//camera\n\
uniform vec3 u_camera_eye;\n\
void main() {\n\
	\n\
	vec4 vertex4 = vec4(a_vertex,1.0);\n\
	v_normal = a_normal;\n\
	v_uvs = a_coord;\n\
	\n\
	//vertex\n\
	v_pos = (u_model * vertex4).xyz;\n\
	//normal\n\
	v_normal = (u_normal_model * vec4(v_normal,0.0)).xyz;\n\
	gl_Position = u_viewprojection * vec4(v_pos,1.0);\n\
}\n\
\n\
\\color.fs\n\
\n\
precision mediump float;\n\
//varyings\n\
varying vec3 v_pos;\n\
varying vec3 v_normal;\n\
varying vec2 v_uvs;\n\
//globals\n\
uniform vec3 u_camera_eye;\n\
uniform vec4 u_clipping_plane;\n\
uniform float u_time;\n\
uniform vec3 u_background_color;\n\
uniform vec3 u_ambient_light;\n\
\n\
uniform float u_number;\n\
uniform sampler2D u_texture;\n\
\n\
//material\n\
uniform vec4 u_material_color; //color and alpha\n\
void main() {\n\
	vec3 N = normalize( v_normal );\n\
	vec3 L = vec3( 0.577, 0.577, 0.577 );\n\
	vec4 color = u_material_color;\n\
	color.xyz *= max(0.0, dot(N,L) );\n\
	gl_FragColor = color;\n\
}\n\
\n\
";