/*
* Sketch Virtual Machine
* Darren Findlay
*
* 15th January 2015
*
*/
var MVM = MVM || {};
/**
* @classdesc The virtual machine used to execute Sketch programs.
* @class MVM.VM
* @param {WebGLRenderingContext} glctx - WebGL context of the canvas to operate on.
* @param {Palette.Manager} manager - Shader Manager used to abstract draw calls and shader manipulation.
* @param {Array} codeStore - MVM program code generated by an instance of {@link Sketch.SketchGen}.
* @param {boolean} debugMode - print additional information to the console during execution.
* @public
* @author Darren Findlay
*/
MVM.VM = function(glctx, manager, codeStore, debugMode) {
/*
* Struct layouts
*
* x y
* Point [100, 150]
*
* r g b a x1 y1 x2 y2
* Line [ 0 , 0 ,255,255,100,100,200,200]
*
* r g b a x1 y1 x2 y2 x3 y3 0 or More points
* Polygon [ 0 , 0 ,255,255,100,100,200,200,150, 0 ,.............]
*
*/
//TEMP
var constantPool = [];
var labelTable = [];
// WebGL context
var glctx = glctx;
// Shader manager
var manager = manager;
// Loop Counter - For debugging
var lc = 0;
// Points to the next instruction in the code store to execute
var cp = 0;
// Points to the first free location after the program
var cl;
// Data store (Stack)
window.MVM.dataStore = [];
var data = new MVM.DataModel();
// Points to the first free space at the top of the data store
var sp = 0;
// Points to the first location of the top most frame
var fp = 0;
// Local Offset. The off set of the first local address from the frame pointer
var LO = 2;
// Address of the dynamic link in a frame
var DLA = 0;
// Address of the retrun address of a frame
var RA = 1;
// Global data store
var globalStore = [];
// Flags wether the virtual machine should hand over control
// to the browser so te canvas can be rendered
var needsUpdate = 0;
this.dead = false;
this.interpret = function() {
if(this.dead === true){
return;
}
var dataStore = window.MVM.dataStore;
if(debugMode) console.log(codeStore);
cl = codeStore.length;
var opCodes = MVM.opCodes;
while (cp < cl && needsUpdate == 0) {
lc++
var opCode = codeStore[cp];
cp++;
switch (opCode) {
case opCodes.STOREG:
//Store a value in a given relative stack frame, in a given index.
//USE: STOREG index
//e.g. STORER 0 stores the top value on the stack in slot 0 of the root scope frame.
var ind = codeStore[cp++];
var val = data.current()
.pop();
data.root
.setVar(ind, val);
if(debugMode) console.log("STOREG: " + val + " in index " + ind);
break;
case opCodes.LOADG:
//Load a value from the root scope frame onto the current stack, from a given index.
//USE: LOADG index
//e.g. LOADG 0 loads the value in slot 0 of the root scope frame.
var ind = codeStore[cp++];
var val = data.root
.getVar(ind);
data.current()
.push(val);
if(debugMode) console.log("LOADG: " + val + " from index " + ind);
break;
case opCodes.STOREL:
//Store a value in the current scope frame, in a given index.
//USE: STOREL index
//e.g. STOREL 0 stores the top value on the stack in slot 0 of the current scope frame.
var ind = codeStore[cp++];
var val = data.current()
.pop();
data.current()
.setVar(ind, val);
if(debugMode) console.log("STOREL: " + val + " in index " + ind);
break;
case opCodes.LOADL:
//Load a value from the current scope frame onto the current stack, from a given index.
//USE: LOADL index
//e.g. LOADL 0 loads the value in slot 0 of the data stack frame.
var ind = codeStore[cp++];
var val = data.current()
.getVar(ind);
data.current()
.push(val);
if(debugMode) console.log("LOADL: " + val + " from index " + ind);
break;
case opCodes.LOADC:
//Place the next codeword on the top of the stack.
var constant = codeStore[cp++];
data.current()
.push(constant);
if(debugMode) console.log("LOADC: " + constant);
break;
case opCodes.IADD:
//Pop two integers off the stack, add them and push the new result onto the stack.
var i = Math.floor(
data.current()
.pop());
var j = Math.floor(
data.current()
.pop());
var result = j + i;
data.current()
.push(result);
if(debugMode) console.log("IADD: " + j + " + " + i + " = " + result);
break;
case opCodes.ISUB:
//Pop two integers off the stack, subbtract them and push the new result onto the stack.
var i = Math.floor(
data.current()
.pop());
var j = Math.floor(
data.current()
.pop());
var result = j - i;
data.current()
.push(result);
if(debugMode) console.log("ISUB: " + j + " - " + i + " = " + result);
break;
case opCodes.IMUL:
//Pop two integers off the stack, multiply them and push the new result onto the stack.
var i = Math.floor(
data.current()
.pop());
var j = Math.floor(
data.current()
.pop());
var result = j * i;
data.current()
.push(result);
if(debugMode) console.log("IMUL: " + j + " * " + i + " = " + result);
break;
case opCodes.IDIV:
//Pop two integers off the stack, divide them and push the new result onto the stack.
var i = Math.floor(
data.current()
.pop());
var j = Math.floor(
data.current()
.pop());
var result = Math.floor(j / i);
data.current()
.push(result);
if(debugMode) console.log("IDIV: " + j + " / " + i + " = " + result);
break;
case opCodes.IMOD:
//Pop two integers off the stack, take modulus and push the new result onto the stack.
var i = Math.floor(
data.current()
.pop());
var j = Math.floor(
data.current()
.pop());
var result = Math.floor(j % i);
data.current()
.push(result);
if(debugMode) console.log("IMOD: " + j + " % " + i + " = " + result);
break;
case opCodes.FADD:
//Pop two integers off the stack, add them and push the new result onto the stack.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = j + i;
data.current()
.push(result);
if(debugMode) console.log("FADD: " + j + " + " + i + " = " + result);
break;
case opCodes.FSUB:
//Pop two integers off the stack, subtract them and push the new result onto the stack.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = j - i;
data.current()
.push(result);
if(debugMode) console.log("FSUB: " + j + " - " + i + " = " + result);
break;
case opCodes.FMUL:
//Pop two integers off the stack, multiply them and push the new result onto the stack.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = j * i;
data.current()
.push(result);
if(debugMode) console.log("FMUL: " + j + " * " + i + " = " + result);
break;
case opCodes.FDIV:
//Pop two integers off the stack, divide them and push the new result onto the stack.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = j / i;
data.current()
.push(result);
if(debugMode) console.log("FDIV: " + j + " / " + i + " = " + result);
break;
case opCodes.FMOD:
//Pop two integers off the stack, divide them and push the remainder onto the stack.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = j % i;
data.current()
.push(result);
if(debugMode) console.log("FMOD: " + j + " % " + i + " = " + result);
break;
case opCodes.CMPEQ:
//Pop two values off the stack, push true if they equate or false if they do not.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = (j == i);
data.current()
.push(result);
if(debugMode) console.log("CMPEQ: " + j + " == " + i + " = " + result);
break;
case opCodes.CMPLT:
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = (j < i);
data.current()
.push(result);
if(debugMode) console.log("CMPLT: " + j + " < " + i + " = " + result);
break;
case opCodes.CMPGT:
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = (j > i);
data.current()
.push(result);
if(debugMode) console.log("CMPGT: " + j + " > " + i + " = " + result);
break;
case opCodes.JUMP:
//Jump to another part of the program unconditionally.
//USE: JUMP address
var address = codeStore[cp];
cp = address;
if(debugMode) console.log("JUMP: " + address);
break;
case opCodes.JUMPT:
//Jump to another part of the program if the top value on the stack is true.
//USE: JUMPT address
var address = codeStore[cp++];
var i = data.current()
.pop();
if (i) {
cp = address;
}
if(debugMode) console.log("JUMPT: to " + address + ", cond is " + i);
break;
case opCodes.JUMPF:
//Jump to another part of the program if the top value on the stack is false.
//USE: JUMPF address
var address = codeStore[cp++];
var i = data.current()
.pop();
if (!i) {
cp = address;
}
if(debugMode) console.log("JUMPF: to " + address + ", cond is " + i);
break;
case opCodes.CALL:
//Call a function.
//USE: CALL definitionHeight codeAddress numArgs
//e.g. CALL 1 90 3 calls a function starting at code address 90, defined 1 scope frame above the call site with 3 parameters.
var definitionHeight = codeStore[cp++];
var codeAddress = codeStore[cp++];
var numArgs = codeStore[cp++];
var returnAddress = cp;
data.call(numArgs, definitionHeight, returnAddress);
cp = codeAddress;
if(debugMode) console.log("CALL: " + codeAddress + " with " + numArgs + " arguments, return to " + returnAddress);
break;
case opCodes.RETURNVAL:
//Return from a function, taking the top value from the stack from the function scope and placing it onto the resumed scope.
//USE: RETURNVAL
var value = data.current()
.pop();
cp = data.funcreturn(value);
if(debugMode) console.log("RETURNVAL: " + value + " returned, exiting function. New code pointer is "+cp);
break;
case opCodes.RETURN:
//Return from a function, returning no value.
//USE: RETURN
cp = data.funcreturn(null);
if(debugMode) console.log("RETURN: void return, exiting function.");
break;
case opCodes.LNDRAW:
// Get line from top of stack, and then draw it? What more is there to say?
var lineStruct = data.current()
.pop();
var r = lineStruct[0];
var g = lineStruct[1];
var b = lineStruct[2];
var a = lineStruct[3];
var pt1x = lineStruct[4];
var pt1y = lineStruct[5];
var pt2x = lineStruct[6];
var pt2y = lineStruct[7];
var theLine = new Float32Array([pt1x,pt1y,0, pt2x,pt2y,0]);
var theColor = new Float32Array([r,g,b,a]);
var prog = manager.getProgram("square", "square");
var canWidth = glctx.canvas.width;
var canHeight = glctx.canvas.height;
prog.setDrawMode(Palette.Program.LINES);
prog.draw(theLine, {width:[canWidth], height: [canHeight]}, {color: theColor})
if(debugMode) console.log("LNDRAW: " + lineStruct);
break;
case opCodes.PGDRAW:
// Get polygon from top of stack, and then draw it? What more is there to say?
var polygonStruct = data.current()
.pop();
var r = polygonStruct[0];
var g = polygonStruct[1];
var b = polygonStruct[2];
var a = polygonStruct[3];
var theColor = new Float32Array([r,g,b,a]);
var points = polygonStruct.slice(4);
// var i;
// for (i = 4; i < polygonStruct.length; i+=2) {
// var pt = [polygonStruct[i],polygonStruct[i+1]];
// points.push(pt);
// }
var prog = manager.getProgram("square", "square");
prog.setDrawMode(Palette.Program.POLYGON);
var canWidth = glctx.canvas.width;
var canHeight = glctx.canvas.height;
prog.draw(points, {width:[canWidth], height: [canHeight]}, {color: theColor})
if(debugMode) console.log("PGDRAW: " + polygonStruct);
break;
case opCodes.RENDER:
needsUpdate = 1;
if(debugMode) console.log("RENDER");
break;
case opCodes.CLEAR:
//Pop an element off the stack. If it is a colour, set it as the clear colour - if not, use the current clear colour.
var colour = data.current()
.pop();
if(colour){
if(colour.length<4){
colour[3] = 1.0;
}
glctx.clearColor(colour[0],colour[1],colour[2],colour[3]);
}
glctx.clear(glctx.COLOR_BUFFER_BIT|glctx.DEPTH_BUFFER_BIT);
if(debugMode) console.log("CLEAR");
break;
case opCodes.EXIT:
//Ends program operation.
//USE: EXIT
cp = cl;
if(debugMode) console.log("EXIT");
break;
case opCodes.LOADIDX:
var constPoolindex = codeStore[cp];
cp++;
var arrayIndex = codeStore[cp];
cp++;
var arr = constantPool[constPoolindex];
var value = arr[arrayIndex];
dataStore[sp] = value;
sp++;
if(debugMode) console.log("LOADIDX: constant pool index " + constPoolindex + " array index: " + arrayIndex);
break;
case opCodes.SETIDX:
var constPoolindex = codeStore[cp];
cp++;
var arrayIndex = codeStore[cp];
cp++;
var arr = constantPool[constPoolindex];
sp--;
var value = dataStore[sp];
arr[arrayIndex] = value;
if(debugMode) console.log("SETIDX: constant pool index " + constPoolindex + " array index: " + arrayIndex);
break;
case opCodes.LNTOPG:
sp--;
var lineAddress = dataStore[sp];
sp--;
var numSides = dataStore[sp];
var line = constantPool[lineAddress];
var polygon = line.slice(0);
var i = 2;
var angle = 360 / numSides;
var pt1xIdx = 4;
var pt1yIdx = 5;
var pt2xIdx = 6;
var pt2yIdx = 7;
var pt3xIdx = 8;
var pt3yIdx = 9;
while(i < numSides) {
var pivot = [polygon[pt1xIdx],polygon[pt1yIdx]];
var point = [polygon[pt2xIdx],polygon[pt2yIdx]];
var newpt = rotatePoint(pivot,point,angle);
// Shift new point
offSetx = polygon[pt1xIdx] - polygon[pt2xIdx];
offSety = polygon[pt1yIdx] - polygon[pt2yIdx];
newpt[0] -= offSetx;
newpt[1] -= offSety;
// Add new point to polygon
polygon[pt3xIdx] = newpt[0];
polygon[pt3yIdx] = newpt[1];
pt1xIdx += 2;
pt1yIdx += 2;
pt2xIdx += 2;
pt2yIdx += 2;
pt3xIdx += 2;
pt3yIdx += 2;
i++;
}
var targetAddress = codeStore[cp];
cp++;
constantPool[targetAddress] = polygon;
if(debugMode) console.log("LNTOPG " + polygon);
break;
case opCodes.PTADD:
//Pop two points off the stack, then place a white line generated by this back onto the stack.
var pt2 = data.current()
.pop();
var pt1 = data.current()
.pop();
var line = [1,1,1,1,pt1[0],pt1[1],pt2[0],pt2[1]];
data.current()
.push(line);
if(debugMode) console.log("PTADD " + line);
break;
case opCodes.LNMUL:
sp--;
var mulValue = dataStore[sp];
sp--;
var lineAddress = dataStore[sp];
var line = constantPool[lineAddress];
pt1x = line[4];
pt1y = line[5];
pt2x = line[6];
pt2y = line[7];
var xDist = pt2x - pt1x;
var yDist = pt2y - pt1y;
var xLen = (xDist * mulValue) - xDist;
var yLen = (yDist * mulValue) - yDist;
var targetLineAddress = codeStore[cp];
cp++;
var newLine = line.slice(0);
newLine[6] += xLen;
newLine[7] += yLen;
newLine[0] = 0;
newLine[1] = 1;
newLine[2] = 0;
newLine[3] = 1;
constantPool[targetLineAddress] = newLine;
if(debugMode) console.log("LNMUL " + newLine);
break;
//Augmentations to support scoping.
case opCodes.STORER:
//Store a value in a given relative stack frame, in a given index. (Store Relative)
//USE: STORER stack index
//e.g. STORER 1 0 stores the top value on the stack in slot 0 of the data stack frame above the current one.
var rel = codeStore[cp++];
var ind = codeStore[cp++];
var val = data.current()
.pop();
data.relative(rel)
.setVar(ind, val);
if(debugMode) console.log("STORER: placed "+val+" in index "+ind+" of relative frame "+rel+".");
break;
case opCodes.LOADR:
//Load a value from a relative stack frame, from a given index. (Load Relative)
//USE: LOADR stack index
//e.g. LOADR 2 0 loads the value in slot 0 of the data stack frame 2 layers above the current one.
var rel = codeStore[cp++];
var ind = codeStore[cp++];
var val = data.relative(rel)
.getVar(ind);
data.current()
.push(val);
if(debugMode) console.log("LOADR: retrieved "+val+" from index "+ind+" of relative frame "+rel+".");
break;
case opCodes.POPSC:
//Pop off and discard the current stack data frame, equivalent to leaving a code block. (Pop Scope)
//USE: POPSC
if(debugMode){
console.log("POPSC: exiting scope:");
console.log(data.current());
}
data.exit();
if(debugMode) console.log("POPSC: exited current block level.");
break;
case opCodes.PUSHSC:
//Create and push a new stack data frame, equivalent to entering a code block. (Push Scope)
//USE: PUSHSC
data.enter();
if(debugMode) console.log("PUSHSC: entered new block level.");
break;
//BOOLEAN OPERANDS
case opCodes.BAND:
//Pop two values off the stack, push A && B.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = (j && i);
data.current()
.push(result);
if(debugMode) console.log("BAND: " + j + " && " + i + " = " + result);
break;
case opCodes.BOR:
//Pop two values off the stack, push A || B.
var i = data.current()
.pop();
var j = data.current()
.pop();
var result = (j || i);
data.current()
.push(result);
if(debugMode) console.log("BOR: " + j + " || " + i + " = " + result);
break;
case opCodes.BNEG:
//Pop one value off the stack, push !A.
var i = data.current()
.pop();
var result = !i;
data.current()
.push(result);
if(debugMode) console.log("BNEG: !" + i + " = " + result);
break;
case opCodes.AGGR:
//Aggregate a set of elements from the stack into an array.
//USE: AGGR num
//e.g. AGGR 3 pops 3 elements a, b, anc c from the stack and pushes [a,b,c] to the stack.
var num = codeStore[cp++];
var out = [];
while(num-- > 0){
var i = data.current()
.pop();
out.unshift(i);
}
data.current()
.push(out);
if(debugMode) console.log("AGGR: output " + out);
break;
case opCodes.WIDTH:
//Push the width of the canvas onto the stack. Since this can't be gleaned normally.
data.current()
.push(glctx.canvas.width);
if(debugMode) console.log("WIDTH: " + glctx.canvas.width);
break;
case opCodes.HEIGHT:
//Push the height of the canvas onto the stack. Since this can't be gleaned normally.
data.current()
.push(glctx.canvas.height);
if(debugMode) console.log("WIDTH: " + glctx.canvas.height);
break;
case opCodes.AUGPT:
//Pop two structs off the stack, identify which is the point and append it to the line/poly.
var i = data.current()
.pop();
var j = data.current()
.pop();
var out;
if(i.length>j.length){
out = i.concat(j);
} else{
out = j.concat(i);
}
data.current()
.push(out);
if(debugMode) console.log("AUGPT: " +i+ " + " +j+ " = " +out);
break;
case opCodes.SETCOLOUR:
//Pop one line/poly and one point, use the point to define the colour for that shape...
var colour = data.current()
.pop();
var shape = data.current()
.pop();
if(colour.size === 3){
colour[3] = 1;
}
for(var i = 0; i< colour.length; i++){
shape[i] = colour[i];
}
data.current()
.push(shape);
if(debugMode) console.log("SETCOLOUR: " +shape+ " ~ " +colour+ " = " +out);
break;
case opCodes.TRANSLATEPT:
//Pop off twom points, move top of stack by the necessary distance.
var vectr = data.current()
.pop();
var point = data.current()
.pop();
var out = point.slice();
for (var i = 0; i < vectr.length; i++) {
out[i] += vectr[i];
};
data.current()
.push(out);
if(debugMode) console.log("TRASLATEPT: [" +point+ "] -> [" +vectr+ "] = [" +out+"]");
break;
case opCodes.TRANSLATESTRUCT:
//Pop off twom points, move top of stack by the necessary distance.
var vectr = data.current()
.pop();
var strct = data.current()
.pop();
var out = strct.slice();
// alert("["+out+"]");
for (var i = 4; i < strct.length; i++) {
out[i] += vectr[i%2];
};
data.current()
.push(out);
if(debugMode) console.log("TRASLATESTRUCT: [" +strct+ "] -> [" +vectr+ "] = [" +out+"]");
break;
}
// remove garbage from stack
//dataStore.splice(sp,dataStore.length - sp);
//if(debugMode) console.log(JSON.stringify(dataStore));
}
if (needsUpdate) {render();}
return data;
};
// Passes control to the browser to update the canvas and
// requests a call back to start interpreting once the rendering has
// complete
render = function() {
needsUpdate = 0;
window.requestAnimationFrame(window.mvm.interpret);
}
this.call = function(address, args){
var returnAddress = codeStore.length;
for (var i = 0; i < args.length; i++) {
data.current()
.push(args[i]);
};
data.call(args.length, 0, returnAddress);
// data.funcreturn();
cp = address;
return this.interpret();
};
this.kill = function(){
this.dead = true;
};
// angle parameter in deegrees
function rotatePoint(pivot, point, angle) {
// Get origin x, y
var pivx = pivot[0];
var pivy = pivot[1];
// Get point x, y
var ptx = point[0];
var pty = point[1];
// Get sin and cos of angle
var s = Math.sin((angle) * (Math.PI/180));
var c = Math.cos((angle) * (Math.PI/180));
// Translate point back to origin
ptx -= pivx;
pty -= pivy;
// Rotate point
var newx = ptx * c - pty * s;
var newy = ptx * s + pty * c;
// Translate new point back
newx += pivx;
newy += pivy;
// Create new point
var newPt = [newx,newy];
return newPt;
}
}
MVM.opCodes = {
STOREG: 0,
LOADG: 1,
STOREL: 2,
LOADL: 3,
LOADC: 4,
IADD: 5,
ISUB: 6,
IMUL: 7,
IDIV: 8,
IMOD: 9,
FADD: 10,
FSUB: 11,
FMUL: 12,
FDIV: 13,
FMOD: 14,
LOADIDX:15,
SETIDX: 16,
CMPEQ: 17,
CMPLT: 18,
CMPGT: 19,
JUMP: 20,
JUMPT: 21,
JUMPF: 22,
CALL: 23,
RETURN: 24,
LNDRAW: 25,
PGDRAW: 26,
RENDER: 27,
CLEAR: 28,
PTADD: 29,
LNTOPG: 30,
LNMUL: 31,
EXIT: 32,
STORER: 33,
LOADR: 34,
POPSC: 35,
PUSHSC: 36,
RETURNVAL: 37,
BAND: 38,
BOR: 39,
BNEG: 40,
AGGR: 41,
WIDTH: 42,
HEIGHT: 43,
AUGPT: 44,
SETCOLOUR: 45,
TRANSLATEPT: 46,
TRANSLATESTRUCT: 47
};