/** * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. * You may add additional accurate notices of copyright ownership. * * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ * */ package alternativa.engine3d.materials.compiler { import alternativa.engine3d.alternativa3d; import flash.display3D.Context3DProgramType; import flash.utils.ByteArray; import flash.utils.Dictionary; import flash.utils.Endian; use namespace alternativa3d; /** * @private * Dynamic shader linker */ public class Linker { /** * Data after linking. */ public var data:ByteArray = null; /** * Number of used slots. */ public var slotsCount:int = 0; /** * Number of lines of the shader code. */ public var commandsCount:int = 0; /** * Linker type. Can be vertex of fragment. */ public var type:String; private var procedures:Vector. = new Vector.(); /** * @private * Variables after linking. */ alternativa3d var _linkedVariables:Object; // Dictionary of temporary variables at this linker. Key is a name of variable, value is a variable. private var _localVariables:Object = {}; // Key - procedure, value - array of strings. private var _inputParams:Dictionary = new Dictionary(); // Key - procedure, value - array of strings. private var _outputParams:Dictionary = new Dictionary(); // Counters of variables by types private var _locals:Vector. = new Vector.(6, true); private var samplers:Object = {}; private var _varyings:Object = {}; /** * Creates a new Linker instance. * * @param programType Type of shader. */ public function Linker(programType:String) { type = programType; } /** * Clears a content. */ public function clear():void { data = null; _locals[0] = _locals[1] = _locals[2] = _locals[3] = _locals[4] = _locals[5] = 0; procedures.length = 0; _varyings = {}; samplers = {}; commandsCount = 0; slotsCount = 0; _linkedVariables = null; _inputParams = new Dictionary(); _outputParams = new Dictionary(); } /** * Adds a new shader procedure. * * @param procedure Procedure to add. * * @see Procedure */ public function addProcedure(procedure:Procedure, ...args):void { for each(var v:Variable in procedure.variablesUsages[VariableType.VARYING]) { if (v == null) continue; var nv:Variable = _varyings[v.name] = new Variable(); nv.name = v.name; nv.type = v.type; nv.index = -1; } procedures.push(procedure); _inputParams[procedure] = args; data = null; } /** * Declaration of variable of given type. * * @param name Name of variable * @param type Type of variable. Should be one of the VariableType constants. The default value is Temporary variable. * * @see VariableType */ public function declareVariable(name:String, type:uint = 2):void { var v:Variable = new Variable(); v.index = -1; v.type = type; v.name = name; _localVariables[name] = v; if (v.type == VariableType.VARYING) { _varyings[v.name] = v; } data = null; } public function declareSampler(output:String, uv:String, sampler:String, options:String):void { if (_localVariables[uv] == null) { throw new ArgumentError("Undefined variable " + uv); } if (_localVariables[sampler] == null) { throw new ArgumentError("Undefined variable " + sampler); } if (_localVariables[output] == null) { declareVariable(output, 2); } data = null; } /** * Setting of input parameters of procedure. * * @param procedure A procedure to which parameters will be set. * @param args Names of variables, separated by the comma, that are passed into the procedure. * Variables must be previously declared, using the method declareVariable(). * * @see #declareVariable() */ public function setInputParams(procedure:Procedure, ...args):void { _inputParams[procedure] = args; data = null; } /** * Setting of output parameters of procedure. * * @param procedure A procedure to which parameters will be set. * @param args Names of variables, separated by the comma, that are passed into the procedure. * Variables must be previously declared, using the method declareVariable(). * * @see #declareVariable() */ public function setOutputParams(procedure:Procedure, ...args):void { _outputParams[procedure] = args; data = null; } /** * Returns of index of variable after the linking. * * @param name Name of variable. * @return Its index for sending to Context3D */ public function getVariableIndex(name:String):int { if (_linkedVariables == null) throw new Error("Not linked"); var variable:Variable = _linkedVariables[name]; if (variable == null) { throw new Error('Variable "' + name + '" not found'); } return variable.index; } /** * Returns index of variable or -1 there is no variable with such name. */ public function findVariable(name:String):int { if (_linkedVariables == null) throw new Error("Has not linked"); var variable:Variable = _linkedVariables[name]; if (variable == null) { return -1; } return variable.index; } /** * Returns the existence of this variable in linked code. * @param name Name of variable */ public function containsVariable(name:String):Boolean { if (_linkedVariables == null) throw new Error("Not linked"); return _linkedVariables[name] != null; } /** * Linking of procedures to one shader. */ public function link():void { if (data != null) return; var v:Variable; var variables:Object = _linkedVariables = {}; var p:Procedure; var i:int, j:int; var nv:Variable; for each (v in _localVariables) { nv = variables[v.name] = new Variable(); nv.index = -1; nv.type = v.type; nv.name = v.name; nv.size = v.size; } data = new ByteArray(); data.endian = Endian.LITTLE_ENDIAN; data.writeByte(0xa0); data.writeUnsignedInt(0x1); // AGAL version, big endian, bit pattern will be 0x01000000 data.writeByte(0xa1); // tag program id data.writeByte((type == Context3DProgramType.FRAGMENT) ? 1 : 0); // vertex or fragment commandsCount = 0; slotsCount = 0; _locals[0] = 0; _locals[1] = 0; _locals[2] = 0; _locals[3] = 0; _locals[4] = 0; _locals[5] = 0; // First iteration - collecting of variables. for each (p in procedures) { var iLength:int = p.variablesUsages.length; _locals[1] += p.reservedConstants; for (i = 0; i < iLength; i++) { var vector:Vector. = p.variablesUsages[i]; var jLength:int = vector.length; for (j = 0; j < jLength; j++) { v = vector[j]; if (v == null || v.name == null) continue; if (v.name == null && i != 2 && i != 6 && i != 3) { throw new Error("Linkage error: Noname variable. Procedure = " + p.name + ", type = " + i.toString() + ", index = " + j.toString()); } nv = variables[v.name] = new Variable(); nv.index = -1; nv.type = v.type; nv.name = v.name; nv.size = v.size; } } } for each (p in procedures) { // Changing of inputs var offset:int = data.length; data.position = data.length; data.writeBytes(p.byteCode, 0, p.byteCode.length); var input:Array = _inputParams[p]; var output:Array = _outputParams[p]; var param:String; var numParams:int; if (input != null) { numParams = input.length; for (j = 0; j < numParams; j++) { param = input[j]; v = variables[param]; if (v == null) { throw new Error("Input parameter not set. paramName = " + param); } if (p.variablesUsages[6].length > j) { var inParam:Variable = p.variablesUsages[6][j]; if (inParam == null) { throw new Error("Input parameter set, but not exist in code. paramName = " + param + ", register = i" + j.toString()); } if (v.index < 0) { v.index = _locals[v.type]; _locals[v.type] += v.size; } while (inParam != null) { inParam.writeToByteArray(data, v.index, v.type, offset); inParam = inParam.next; } } } } if (output != null) { // Output parameters numParams = output.length; for (j = 0; j < numParams; j++) { param = output[j]; v = variables[param]; if (v == null) { if (j == 0 && (i == procedures.length - 1)) { // Output variable continue; } throw new Error("Output parameter have not declared. paramName = " + param); } if (v.index < 0) { if (v.type != 2) { throw new Error("Wrong output type:" + VariableType.TYPE_NAMES[v.type]); } v.index = _locals[v.type]; _locals[v.type] += v.size; } var outParam:Variable = p.variablesUsages[3][j]; if (outParam == null) { throw new Error("Output parameter set, but not exist in code. paramName = " + param + ", register = i" + j.toString()); } while (outParam != null) { outParam.writeToByteArray(data, v.index, v.type, offset); outParam = outParam.next; } } } var vars:Vector. = p.variablesUsages[2]; for (j = 0; j < vars.length; j++) { v = vars[j]; if (v == null) continue; while (v != null) { v.writeToByteArray(data, v.index + _locals[2], VariableType.TEMPORARY, offset); v = v.next; } } resolveVariablesUsages(data, variables, p.variablesUsages[0], VariableType.ATTRIBUTE, offset); resolveVariablesUsages(data, variables, p.variablesUsages[1], VariableType.CONSTANT, offset); resolveVariablesUsages(data, _varyings, p.variablesUsages[4], VariableType.VARYING, offset); resolveVariablesUsages(data, variables, p.variablesUsages[5], VariableType.SAMPLER, offset); commandsCount += p.commandsCount; slotsCount += p.slotsCount; } } private function resolveVariablesUsages(code:ByteArray, variables:Object, variableUsages:Vector., type:uint, offset:int):void { for (var j:int = 0; j < variableUsages.length; j++) { var vUsage:Variable = variableUsages[j]; if (vUsage == null) continue; if (vUsage.isRelative) continue; var variable:Variable = variables[vUsage.name]; if (variable.index < 0) { variable.index = _locals[type]; _locals[type] += variable.size; } while (vUsage != null) { vUsage.writeToByteArray(code, variable.index, variable.type, offset); vUsage = vUsage.next; } } } /** * Returns description of procedures: name, size, input and output parameters. * @return */ public function describeLinkageInfo():String { var str:String; var result:String = "LINKER:\n"; var totalCodes:uint = 0; var totalCommands:uint = 0; for (var i:int = 0; i < procedures.length; i++) { var p:Procedure = procedures[i]; if (p.name != null) { result += p.name + "("; } else { result += "#" + i.toString() + "("; } var args:* = _inputParams[p]; if (args != null) { for each (str in args) { result += str + ","; } result = result.substr(0, result.length - 1); } result += ")"; args = _outputParams[p]; if (args != null) { result += "->("; for each (str in args) { result += str + ","; } result = result.substr(0, result.length - 1); result += ")"; } result += " [IS:" + p.slotsCount.toString() + ", CMDS:" + p.commandsCount.toString() + "]\n"; totalCodes += p.slotsCount; totalCommands += p.commandsCount; } result += "[IS:" + totalCodes.toString() + ", CMDS:" + totalCommands.toString() + "]\n"; return result; } public function get varyings():Object { return _varyings; } public function set varyings(value:Object):void { _varyings = value; data = null; } } }