Symbolic AGAL experiment
probably has a few bugs
/**
* Copyright yonatan ( http://wonderfl.net/user/yonatan )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/tAJf
*/
// forked from 9re's toon shader test
package
{
import flash.display.*;
import flash.geom.*;
import com.adobe.utils.*;
import flash.display3D.*;
import flash.display3D.textures.*;
import flash.events.*;
import flash.utils.*;
import com.bit101.components.*;
/**
* @author 9re
*/
[SWF(width="465", height="465")]
public class main extends Sprite
{
private static const R0:Number = 5;
private static const N0:int = 72;
private static const R1:Number = 1.8;
private static const N1:int = 64;
private static const K:int = N0 * N1;
private var _stage3D:Stage3D;
private var _context3D:Context3D;
private var _agalSugar:AGALSugar;
private var _projection:PerspectiveMatrix3D;
private var _model:Matrix3D;
private var _world:Matrix3D;
private var _width:int;
private var _height:int;
private var _indecesBuffer:IndexBuffer3D;
private var _timer:int = -1;
private var _vertexMasm:TA;
private var _fragmentMasm:TA;
private var _vertexAsm:TA;
private var _fragmentAsm:TA;
private var _status:TA;
private var _updateBtn:PushButton;
private var _constants:String = "lightDirection: 4, world: 16, model: 16";
private var _attributes:String = "position: 3, normal: 3";
private var _varying:String = "intensity: 1";
private var _samplers:String = "colors";
private var _vCode:String = <><![CDATA[// Edit and click 'Update program'
m44 op, $position, $world
dp3 vt0.x, $normal, $model:0
dp3 vt0.y, $normal, $model:1
dp3 vt0.z, $normal, $model:2
mov vt0.w, $model:3.w
dp3 vt0, $lightDirection, vt0
mov $intensity, vt0]]></>;
private var _fCode:String = <><![CDATA[// Edit and click 'Update program'
mul ft0, $intensity, $2
sub ft0, ft0, $1
tex ft0, ft0, $colors <2d,clamp,linear>
mov oc, ft0]]></>;
public function main()
{
Wonderfl.disable_capture();
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(event:Event = null):void {
if (event) removeEventListener(Event.ADDED_TO_STAGE, init);
stage.align = StageAlign.TOP_LEFT;
// stage.scaleMode = StageScaleMode.NO_SCALE;
var sw:int = stage.stageWidth;
var sh:int = stage.stageHeight;
with(_vertexMasm = new TA(this, 0, 20, _vCode, true)) { setSize(sw/8*3-4, sh/3*2-24) };
with(_fragmentMasm = new TA(this, 0, sh/3*2+20, _fCode, true)){ setSize(sw/8*3-4, sh/3-24) };
with(_vertexAsm = new TA(this, sw/8*3, 20)) { setSize(sw/8*3-4, sh/3*2-24); };
with(_fragmentAsm = new TA(this, sw/8*3, sh/3*2+20)) { setSize(sw/8*3-4, sh/3-24); };
with(_status = new TA(this, sw/8*6, 20)) { setSize(sw/4-4, sh-50); textField.wordWrap=true; };
_updateBtn = new PushButton(this, sw/8*6, sh-24, "Update program", updateProgram);
new Label(this, 0, 0, "Vertex program");
new Label(this, 0, sh/3*2, "Fragment program");
new Label(this, sw/8*3, 0, "Preprocessed vertex program");
new Label(this, sw/8*3, sh/3*2, "Preprocessed fragment program");
new Label(this, sw/8*6, 0, "Status");
stage.addEventListener(MouseEvent.MOUSE_MOVE, function(e:*):void { visible = true; });
stage.addEventListener(Event.MOUSE_LEAVE, function(e:*):void { visible = false; });
_width = stage.stageWidth & ~1;
_height = stage.stageHeight & ~1;
_stage3D = stage.stage3Ds[0];
_stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
_stage3D.requestContext3D(Context3DRenderMode.AUTO);
//_stage3D.viewPort = new Rectangle(0, 0, _width, _height);
}
private function onContext3DCreate(e:Event):void
{
_context3D = _stage3D.context3D;
_context3D.enableErrorChecking = true;
_context3D.setDepthTest(true, Context3DCompareMode.LESS);
//_context3D.setCulling(Context3DTriangleFace.FRONT_AND_BACK);
_context3D.configureBackBuffer(_width, _height, 2, true);
initTorus();
initAGAL();
var texture:Texture = _context3D.createTexture(64, 64, Context3DTextureFormat.BGRA, true);
var textureData:BitmapData = new BitmapData(64, 64);
var heights:Array = [8, 4, 20, 22, 10];
var colors:Array = [0, 0x661100, 0xff7f00, 0xffd400, 0xffff66];
var len:int = colors.length;
var rect:Rectangle = new Rectangle(0, 0, 64);
var yPos:int = 0, h:int;
for (var i:int = 0; i < len; ++i) {
rect.height = h = heights[i];
rect.y = yPos;
yPos += h;
textureData.fillRect(rect, colors[i] | 0xff000000);
}
//textureData.perlinNoise(64, 64, 8, 0, true, true);
texture.uploadFromBitmapData(textureData);
// _context3D.setTextureAt(0, texture);
_agalSugar.setSampler(_context3D, "colors", texture);
//addChild(new Bitmap(textureData)).y = 40;
_model = new Matrix3D;
_world = new Matrix3D;
_projection = new PerspectiveMatrix3D();
_projection.perspectiveFieldOfViewRH(42.7, stage.stageWidth / stage.stageHeight, 1.0, 40.0);
updateProgram();
// _context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 12, new Vector.<Number>([ light.x, light.y, light.z, 1.0]));
// _context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 13, new Vector.<Number>([ eye.x, eye.y, eye.z, 0.0]));
// _context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 14, new Vector.<Number>([ 0.0, 0.0, 0.0, 0.0]));
// _context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, new Vector.<Number>([ 1.0, 1.0, 1.0, 0.0]));
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function initAGAL():void {
_agalSugar = new AGALSugar(
_constants,
_attributes,
_varying,
_samplers,
_vertexMasm.text,
_fragmentMasm.text
);
_vertexAsm.text = _agalSugar.asm.v;
_fragmentAsm.text = _agalSugar.asm.f;
}
private var program:Program3D;
private function updateProgram(e:MouseEvent = null):void {
try {
initAGAL();
if(!program) program = _context3D.createProgram();
program.upload(
_agalSugar.bytecode.v,
_agalSugar.bytecode.f
);
_status.text = "No errors. Symbol table:\n\n";
for(var name:String in _agalSugar._symbols) _status.text += _agalSugar._symbols[name] + "\n";
_context3D.setProgram(program);
//_agalSugar.initLiterals(_context3D);
_agalSugar.setParams(_context3D, { lightDirection: [0.0, 0.0, 1.0, 0.0] }); // initializes literals too
} catch(e:Error) {
_status.text = e.message;
}
}
private function initTorus():void {
var i:int, j:int, k:int, m:int;
var phi:Number, psi:Number;
var verticies:Vector.<Number> = new Vector.<Number>(6 * K, true);
for (i = 0; i < N0; ++i) {
for (j = 0; j < N1; ++j) {
phi = i * 2 * Math.PI / N0;
psi = j * 2 * Math.PI / N1;
k = (j + N1 * i) * 6;
verticies[k] = (R0 + R1 * Math.cos(psi)) * Math.cos(phi);
verticies[k + 1] = (R0 + R1 * Math.cos(psi)) * Math.sin(phi);
verticies[k + 2] = R1 * Math.sin(psi);
verticies[k + 3] = (R0 + (1+R1) * Math.cos(psi)) * Math.cos(phi);
verticies[k + 4] = (R0 + (1+R1) * Math.cos(psi)) * Math.sin(phi);
verticies[k + 5] = (1+R1) * Math.sin(psi);
for (m = 0; m < 3; ++m) {
verticies[k + m + 3] -= verticies[k + m];
}
}
}
var verticiesBuffer:VertexBuffer3D = _context3D.createVertexBuffer(K, 6);
verticiesBuffer.uploadFromVector(verticies, 0, K);
_context3D.setVertexBufferAt(0, verticiesBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
_context3D.setVertexBufferAt(1, verticiesBuffer, 3, Context3DVertexBufferFormat.FLOAT_3);
var indeces:Vector.<uint> = new Vector.<uint>(6 * K, true);
for (i = 0; i < N0; ++i) {
for (j = 0; j < N1; ++j) {
k = j + i * N1;
m = 6 * k;
indeces[m] = k;
indeces[m + 1] = (k + N1) % K;
indeces[m + 2] = (k + 1) % K;
indeces[m + 3] = (k + N1 + 1) % K;
indeces[m + 4] = (k + 1) % K;
indeces[m + 5] = (k + N1) % K;
}
}
_indecesBuffer = _context3D.createIndexBuffer(indeces.length);
_indecesBuffer.uploadFromVector(indeces, 0, indeces.length);
}
private var _pitch:Number = 0.0;
private var _yaw:Number = 0.0;
private var _s:BitmapData;
private function onEnterFrame(event:Event):void {
_context3D.clear(1, 1, 1);
_model.identity();
_model.appendRotation(_pitch += 0.3, Vector3D.X_AXIS);
_model.appendRotation(_yaw += 0.2, Vector3D.Y_AXIS);
_model.appendTranslation(0, 0, -12);
_world.identity();
_world.append(_model);
_world.append(_projection);
_agalSugar.setConstant(_context3D, "world", _world);
_agalSugar.setConstant(_context3D, "model", _model);
// _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _world, true);
// _context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 4, _model, true);
_context3D.drawTriangles(_indecesBuffer);
// if(!_s){_s=new BitmapData(stage.stageWidth&~1,stage.stageHeight&~1,false,0);addChildAt(new Bitmap(_s),0);_context3D.drawToBitmapData(_s)};
_context3D.present();
}
}
}
import com.bit101.components.*;
import flash.display.*;
class TA extends TextArea {
public function TA(parent:DisplayObjectContainer=null, xpos:Number=0, ypos:Number=0, text:String="", editable:Boolean = false) {
super(parent, xpos, ypos, text);
textField.wordWrap = false;
_panel.alpha = 0.5;
if(!(this.editable = editable)) textField.alpha = 0.7;
}
}
import com.bit101.components.*;
Style.LABEL_TEXT = 0;
import com.adobe.utils.AGALMiniAssembler;
import flash.geom.*;
import flash.display3D.*;
import flash.display3D.textures.*;
class AGALSugar {
public var _symbols:Object = {};
protected var _literals:Object = {};
protected var _assembler:AGALMiniAssembler = new AGALMiniAssembler();
public var asm:Object = { v: "", f: "" };
public var bytecode:Object = {};
public function AGALSugar(constants:String, attributes:String, varying:String, samplers:String, vp:String, fp:String) {
var regIdx:int;
var name:String;
var decl:Decl;
var sym:Symbol;
var shader:String; // "v" or "f"
var masm:Object = { v: _cleanCode(vp), f: _cleanCode(fp) };
constants = _cleanDecls(constants);
attributes = _cleanDecls(attributes);
varying = _cleanDecls(varying);
samplers = _cleanSamplerDecls(samplers);
// create all declared symbols
for each(name in _declNames([samplers, constants, attributes, varying].filter(function(...a):*{return a[0]!="";}).join(","))) _newSymbol(name);
// allocate vertex shader registers for attribute decls
regIdx = 0;
for each(decl in _declObjects(attributes)) {
sym = _getSymbol(decl.name);
sym.vr = new RegAlloc("va", decl.size, sym, regIdx, null);
regIdx += decl.size+3>>2;
}
// allocate vertex and fragment regs for varying decls
regIdx = 0;
for each(decl in _declObjects(varying)) {
sym = _getSymbol(decl.name);
sym.fr = sym.vr = new RegAlloc("v", decl.size, sym, regIdx, null);
regIdx += decl.size+3>>2;
}
// automatically copy vertex attributes to varying registers (when used in fragment program)
for each(name in _referencedNames(masm.f)) {
sym = _getSymbol(name);
if(sym.vr && sym.vr.type == "va") {
sym.fr = new RegAlloc("v", sym.vr.data32, sym, regIdx, null);
for(var i:int=0; i<((sym.vr.data32+3)>>2); i++) {
masm.v += "mov v" + regIdx + ", va" + (sym.vr.regIdx+i) + "\n";
regIdx++;
}
}
}
// allocate fragment samplers
regIdx = 0;
for each(name in _split(samplers)) {
sym = _getSymbol(name);
sym.fr = new RegAlloc("fs", 0, sym, regIdx++, null);
}
// allocate vertex and fragment constant regs
for each(shader in ["v", "f"]) {
regIdx = 0;
for each(name in _referencedNames(masm[shader])) {
sym = _getSymbol(name);
if((decl = _findDecl(name, constants))) {
sym[shader+"r"] = new RegAlloc(shader+"c", decl.size, sym, regIdx, null);
regIdx += decl.size+3>>2;
}
}
// create literals (autogenerated constants)
var comp:int = 0;
_literals[shader] = new LiteralData(regIdx);
for each(var literal:Number in _referencedLiterals(masm[shader])) {
sym = _symbols[String(literal)] || _newSymbol(String(literal));
sym.isLiteral = true;
sym[shader+"r"] = new RegAlloc(shader+"c", 1, sym, regIdx, "xyzw".charAt(comp));
_literals[shader].data.push(literal);
if(++comp == 4) {
comp = 0;
regIdx++;
}
}
while(comp++ & 3) _literals[shader].data.push(0); // padding
}
// preprocess programs, replace symbols with registers, assemble bytecode
for each(shader in ["v", "f"]) {
var rex:RegExp = /\$([a-zA-Z_][a-zA-Z0-9_]+|[0-9-\.]+)(:([0-9]+))?/g; // exec result: [0] full match, [1] symbol name, [3] offset (or null)
var res:Array, lastMatchEnd:int = 0;
while(res = rex.exec(masm[shader])) {
asm[shader] += masm[shader].substring(lastMatchEnd, res.index);
sym = _getSymbol(res[1]);
var reg:RegAlloc = sym[shader+"r"];
asm[shader] += reg.type + int(reg.regIdx + int(res[3]));
if(reg.component) asm[shader] += "." + reg.component;
lastMatchEnd = res.index + res[0].length;
}
asm[shader] += masm[shader].substr(lastMatchEnd);
bytecode[shader] = _assembler.assemble(shader == "v" ? "vertex" : "fragment", asm[shader]);
if(_assembler.error != "") throw(new Error(_assembler.error));
}
}
// Set several shader constants/textures from name-value properties (in params argument).
// literalsToo specifies whether to setup const regs for numeric literals as well.
public function setParams(ctx:Context3D, params:Object, literalsToo:Boolean = true, transposeMatrices:Boolean = true):void {
for(var name:String in params) {
if(_getSymbol(name).isSampler) setSampler(ctx, name, params[name]);
else setConstant(ctx, name, params[name], transposeMatrices);
}
if(literalsToo) initLiterals(ctx);
}
public function setConstant(ctx:Context3D, name:String, value:*, transposeMatrices:Boolean = true):void {
var sym:Symbol = _getSymbol(name);
for each(var reg:RegAlloc in [sym.vr, sym.fr]) {
if(reg) {
var size:int = (reg.data32+3) >> 2; // number of regs to set
if(value is Vector.<Number>) ctx.setProgramConstantsFromVector(reg.shaderType, reg.regIdx, value as Vector.<Number>, size);
else if(value is Matrix3D && reg.data32 == 16) ctx.setProgramConstantsFromMatrix(reg.shaderType, reg.regIdx, value as Matrix3D, transposeMatrices);
else if(value is Array) ctx.setProgramConstantsFromVector(reg.shaderType, reg.regIdx, Vector.<Number>(value as Array), size);
else if(value is Number && reg.data32 == 1) ctx.setProgramConstantsFromVector(reg.shaderType, reg.regIdx, Vector.<Number>([Number(value)]), size);
else throw(new Error("Failed to set " + reg.shaderType + " constant " + name + " to " + value + " (bad value type)"));
}
}
}
public function setSampler(ctx:Context3D, name:String, texture:TextureBase):void {
var sym:Symbol = _getSymbol(name);
if(sym.fr && sym.fr.type == "fs") ctx.setTextureAt(sym.fr.regIdx, texture);
else throw(new Error(name + " is not a texture sampler"));
}
public function initLiterals(ctx:Context3D, vs:Boolean = true, fs:Boolean = true):void {
if(vs) ctx.setProgramConstantsFromVector("vertex", _literals.v.offset, _literals.v.data);
if(fs) ctx.setProgramConstantsFromVector("fragment", _literals.f.offset, _literals.f.data);
}
protected function _cleanDecls(decls:String):String { return decls.replace(/\s*/g, "") }; // TODO: validation
protected function _cleanSamplerDecls(decls:String):String { return decls.replace(/\s*/g, "") }; // TODO: validation
protected function _cleanCode(decls:String):String {
decls = decls.replace(/\s*\/\/.*$/gm, ""); // strip comments
decls = decls.replace(/^\s*/gm, ""); // strip whitespace at BOL
return decls;
}
protected function _declNames(decls:String):Array { return _split(decls.replace(/:[^,]*/g, "")); }
protected function _declObjects(decls:String):Array {
return _split(decls).map(function (decl:String, ..._):Object {
var a:Array = decl.split(":");
return new Decl(a[0], a[1]);
});
}
protected function _findDecl(name:String, decls:String):Decl {
for each(var decl:Decl in _declObjects(decls)) if(decl.name == name) return decl;
return null;
}
protected function _newSymbol(name:String):Symbol {
if(_symbols[name]) throw(new Error("Duplicate symbol definition: " + name));
var sym:Symbol = new Symbol;
sym.name = name;
return(_symbols[name] = sym);
}
protected function _getSymbol(name:String):Symbol {
if(!isNaN(Number(name))) name = String(Number(name));
if(!_symbols[name]) throw(new Error("Undefined symbol: " + name));
return _symbols[name];
}
protected function _referencedNames(masm:String):Vector.<String> {
var ret:Vector.<String> = new Vector.<String>;
for each(var name:String in masm.match(/\$[a-zA-Z_][a-zA-Z0-9_]*/g)) {
if(ret.indexOf(name.substr(1)) == -1) ret.push(name.substr(1));
}
return ret;
}
protected function _referencedLiterals(masm:String):Vector.<Number> {
var ret:Vector.<Number> = new Vector.<Number>;
for each(var lit:String in masm.match(/\$[-0-9\.][0-9\.]*/g)) {
var n:Number = Number(lit.substr(1));
if(isNaN(n)) throw(new Error("Bad symbol/number: " + lit));
if(ret.indexOf(n) == -1) ret.push(n);
}
return ret;
}
// splits on ",", returns empty array for empty string!
protected function _split(s:String):Array { return s == "" ? [] : s.split(",") }
}
class Decl {
public var name:String;
public var size:int;
public function Decl(name:String, size:int) {
this.name = name
this.size = size
}
}
class Symbol {
public var name:String;
public var vr:RegAlloc;
public var fr:RegAlloc;
public var isLiteral:Boolean = false;
public function get isConstant():Boolean { return((fr && fr.type == "fc") || (vr && vr.type == "vc")); }
public function get isSampler():Boolean { return(fr && fr.type == "fs"); }
public function toString():String {
return(name + " - " + (isLiteral?"literal ":"") + (isConstant?"constant ":"") + (isSampler?"sampler ":"") + (vr?"\n "+vr:"") + (fr?"\n "+fr:""));
}
}
class RegAlloc {
public var type:String; // vc, va, v, fc, fs
public var data32:int; // size (in components)
public var symbol:Symbol;
public var regIdx:int;
public var component:String; // literals only
public function RegAlloc(type:String, data32:int, symbol:Symbol, regIdx:int, component:String) {
this.type = type;
this.data32 = data32;
this.symbol = symbol;
this.regIdx = regIdx;
this.component = component;
}
public function get shaderType():String { return(type.charAt(0) == "v" ? "vertex" : "fragment"); }
public function toString():String { return(type + regIdx + (component ? ("."+component) : "")); }
}
class LiteralData {
public var offset:int;
public var data:Vector.<Number> = new Vector.<Number>;
public function LiteralData(offset:int) { this.offset = offset; }
}