blob: 02f93a5c42685bc98bd52f52a5a8f9bbbe7482d1 [file] [log] [blame]
function getBrowserSize() {
var actualWidth = window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth ||
document.body.offsetWidth;
var actualHeight = window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight ||
document.body.offsetHeight;
return { "width": actualWidth, "height": actualHeight };
}
function getContext(canvas) {
var settings = { preserveDrawingBuffer: true };
gl = canvas.getContext("webgl", settings) ||
canvas.getContext("experimental-webgl", settings);
return gl;
}
function physicalSizeRatio() {
var div = document.createElement("div");
div.style.width = "1mm";
div.style.height = "1mm";
var body = document.getElementsByTagName("body")[0];
body.appendChild(div);
var physicalWidth = document.defaultView.getComputedStyle(div, null).getPropertyValue('width');
var physicalHeight = document.defaultView.getComputedStyle(div, null).getPropertyValue('height');
body.removeChild(div);
return {
"width": parseFloat(physicalWidth),
"height": parseFloat(physicalHeight)
};
}
window.onload = function () {
var DEBUG = 0;
var MOUSETRACKING = 0;
var LOADINGSCREEN = 1;
var canvas;
var socket = new WebSocket("ws://" + host + ":" + port);
socket.binaryType = "arraybuffer";
var CONNECT_SERIAL = 666;
var gl;
var startTime = new Date();
// There is no way to get proper vsync since we have no idea when the real
// swap happens under the hood. What we can do is to delay the response for
// the eglSwapBuffer call, i.e. block the client for the given number of
// milliseconds on each swap.
var SWAP_DELAY = 16; //${swap_delay};
var contextData = { }; // context -> { shaderMap, programMap, ... }
var currentContext = 0;
var currentWindowId = "";
var windowData = {};
var currentZIndex = 1;
var textDecoder;
var initialLoadingCanvas;
var supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints;
if (typeof TextDecoder !== 'undefined') {
textDecoder = new TextDecoder("utf8");
} else {
textDecoder = {
"decode": function (buffer)
{
var string = String.fromCharCode.apply(String, buffer);
return string;
}
};
}
var supportedFunctions;
var sendObject = function (obj) { socket.send(JSON.stringify(obj)); };
var connect = function () {
var size = getBrowserSize();
var width = size.width;
var height = size.height;
var physicalSize = physicalSizeRatio();
var object = { "type": "connect",
"width": width, "height": height,
"physicalWidth": width / physicalSize.width,
"physicalHeight": height / physicalSize.height
};
sendObject(object);
initialLoadingCanvas = createLoadingCanvas('loadingCanvas', 0, 0, width, height);
};
var sendResponse = function (id, value) {
if (DEBUG)
console.log("Response to " + id + " = " + value);
sendObject({ "type": "gl_response", "id": id, "value": value });
};
var createLoadingCanvas = function(name, x, y, width, height) {
var canvas = document.createElement("canvas");
canvas.id = "loading_" + name;
canvas.style.position = "absolute";
canvas.style.left = x + "px";
canvas.style.top = y + "px";
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.style.zIndex = currentZIndex++;
canvas.style.background = "black";
canvas.width = width;
canvas.height = height;
var body = document.getElementsByTagName("body")[0];
body.appendChild(canvas);
if (!LOADINGSCREEN)
return canvas;
var gl = canvas.getContext("webgl");
var loadingVertexShaderSource =
"attribute vec2 a_position; void main(){ gl_Position = vec4(a_position, 0, 1); }";
var loadingFragmentShaderSource =
"precision mediump float;\n"+
"#define PI 3.14159265\n"+
"#define QT_COLOR vec4(0.255,0.804,0.321,1.0)\n"+
"#define GRAY vec4(0.953,0.953,0.957,1.0)\n"+
"uniform float u_time;\n"+
"uniform vec2 u_size;\n"+
"vec4 loadingIndicator(vec2 uv, float time)\n"+
"{\n"+
" float l = length(uv);\n"+
" float theta = atan(uv.y,uv.x)/PI/2.0 + 0.5;\n"+
" float radius = 0.2;\n"+
" float thickness = 0.02;\n"+
" float edge = 3.0 / length(u_size.xy);\n"+
" float t = fract(-time*2.0);\n"+
" float circleVal = smoothstep(radius - edge - thickness, radius - thickness, l) * \n"+
" (1.0 - smoothstep(radius + thickness, radius + edge + thickness, l));\n"+
" float indicatorVal = smoothstep(0.245,0.25,fract(theta - t)) * (1.0 - smoothstep(0.995,1.0,fract(theta - t)));\n"+
" return (1.0 - indicatorVal) * QT_COLOR + indicatorVal * circleVal * GRAY + vec4(1.0,1.0,1.0,1.0) * (1.0 - circleVal);\n"+
"}\n"+
"void main()\n" +
"{\n"+
" vec2 aspect = vec2(u_size.x/u_size.y, 1.0);\n"+
" vec2 origin = aspect*vec2(0.5, 0.5);\n"+
" vec2 uv = aspect*gl_FragCoord.xy;\n"+
" gl_FragColor = loadingIndicator(uv/u_size - origin,u_time); \n"+
"}\n";
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, loadingVertexShaderSource);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, loadingFragmentShaderSource);
gl.compileShader(fragmentShader);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var timeLocation = gl.getUniformLocation(program, "u_time");
var sizeLocation = gl.getUniformLocation(program, "u_size");
// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.uniform2fv(sizeLocation, new Float32Array([canvas.width, canvas.height]));
var time = 0.0;
function draw() {
gl.uniform1f(timeLocation, time);
time += 0.01;
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
canvas.timerId = setInterval(draw, 16);
return canvas;
};
var createCanvas = function (name, x, y, width, height, title) {
var body = document.getElementsByTagName("body")[0];
if (initialLoadingCanvas) {
clearInterval(initialLoadingCanvas.timerId);
body.removeChild(initialLoadingCanvas);
initialLoadingCanvas = undefined;
}
var canvas = document.createElement("canvas");
canvas.id = name;
canvas.style.position = "absolute";
canvas.style.left = x + "px";
canvas.style.top = y + "px";
canvas.style.width = width + "px";
canvas.style.height = height + "px";
canvas.style.zIndex = currentZIndex++;
canvas.width = width;
canvas.height = height;
body.appendChild(canvas);
var qtButtons = 0;
var sendMouseEvent = function (buttons, layerX, layerY, clientX, clientY, name) {
var object = { "type": "mouse",
"buttons": buttons,
"layerX": layerX, "layerY": layerY, "clientX": clientX, "clientY": clientY,
"time": new Date().getTime().toString(),
"name": name
};
sendObject(object);
};
var mapButton = function (b) {
var qtb = 1;
if (b === 1)
qtb = 4;
else if (b === 2)
qtb = 2;
return qtb;
};
canvas.onmousedown = function (event) {
/* jslint bitwise: true */
if (supportsTouch && event.mozInputSource == MOZ_SOURCE_TOUCH)
return;
qtButtons |= mapButton(event.button);
sendMouseEvent(qtButtons, event.layerX, event.layerY, event.clientX, event.clientY,
name);
};
canvas.onmousemove = function (event) {
if (supportsTouch && event.mozInputSource == MOZ_SOURCE_TOUCH)
return;
if (MOUSETRACKING || event.buttons > 0)
sendMouseEvent(qtButtons, event.layerX, event.layerY, event.clientX, event.clientY,
name);
};
canvas.onmouseup = function (event) {
/* jslint bitwise: true */
if (supportsTouch && event.mozInputSource == MOZ_SOURCE_TOUCH)
return;
qtButtons &= ~mapButton(event.button);
sendMouseEvent(qtButtons, event.layerX, event.layerY, event.clientX, event.clientY,
name);
};
function handleMouseWheel(event) {
var deltaY = 0;
if (!event)
deltaY = window.event;
if (event.deltaY)
deltaY = event.deltaY;
else if (event.detail)
deltaY = event.detail * 40;
if (deltaY) {
var object = { "type": "wheel",
"layerX": event.layerX, "layerY": event.layerY,
"clientX": event.clientX, "clientY": event.clientY,
"deltaX": event.deltaX, "deltaY": deltaY, "deltaZ": event.deltaZ,
"time": new Date().getTime(),
"name": name
};
sendObject(object);
}
if (event.preventDefault)
event.preventDefault();
event.returnValue = false;
}
// Internet Explorer, Opera, Chrome and Safari
canvas.addEventListener('mousewheel', handleMouseWheel, { passive: true });
// Firefox
canvas.addEventListener('DOMMouseScroll', handleMouseWheel, { passive: true });
function handleTouch(event) {
var object = {
"type": "touch",
"name": name,
"time": new Date().getTime().toString(),
"event": event.type,
"changedTouches": [],
"stationaryTouches": [],
};
var addTouch = function(changedTouch, isChanged) {
var touch = {
"clientX": changedTouch.clientX,
"clientY": changedTouch.clientY,
"force": changedTouch.force,
"identifier": changedTouch.identifier,
"pageX": changedTouch.pageX,
"pageY": changedTouch.pageY,
"radiusX": changedTouch.radiusX,
"radiusY": changedTouch.radiusY,
"screenX": changedTouch.screenX,
"screenY": changedTouch.screenY,
"normalPositionX": changedTouch.screenX / screen.width,
"normalPositionY": changedTouch.screenY / screen.height,
};
if (isChanged)
object.changedTouches.push(touch);
else
object.stationaryTouches.push(touch);
};
for (var i = 0; i < event.changedTouches.length; ++i) {
var changedTouch = event.changedTouches[i];
addTouch(changedTouch, true);
}
for (var i = 0; i < event.targetTouches.length; ++i) {
var targetTouch = event.targetTouches[i];
if (object.changedTouches.findIndex(function(touch){
return touch.identifier === targetTouch.identifier;
}) === -1) {
addTouch(targetTouch, false);
}
}
sendObject(object);
if (event.preventDefault && event.cancelable)
event.preventDefault();
event.returnValue = false;
}
canvas.addEventListener("touchstart", handleTouch, { passive: true });
canvas.addEventListener("touchend", handleTouch, { passive: true });
canvas.addEventListener("touchcancel", handleTouch, { passive: true });
canvas.addEventListener("touchmove", handleTouch, { passive: true });
canvas.oncontextmenu = function (event) {
event.preventDefault();
};
var gl = getContext(canvas);
/* jslint bitwise: true */
gl.clear([ gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT]);
windowData[name] = {
"canvas": canvas,
"gl": gl,
"loadingCanvas": createLoadingCanvas(name, x, y, width, height)
};
var defaultValuesObject = { "type": "default_context_parameters", "name": name,
"7939": "GL_OES_element_index_uint GL_OES_standard_derivatives " + // GL_EXTENSIONS
"GL_OES_depth_texture GL_OES_packed_depth_stencil" };
[
gl.BLEND,
gl.DEPTH_TEST,
gl.MAX_TEXTURE_SIZE,
gl.MAX_VERTEX_ATTRIBS,
gl.RENDERER,
gl.SCISSOR_TEST,
gl.STENCIL_TEST,
gl.UNPACK_ALIGNMENT,
gl.VENDOR,
gl.VERSION,
gl.VIEWPORT
].forEach(function (value) {
defaultValuesObject[value] = gl.getParameter(value);
});
sendObject(defaultValuesObject);
gl._attachShader = gl.attachShader;
gl.attachShader = function(program, shader) {
var d = contextData[currentContext];
gl._attachShader(d.programMap[program], d.shaderMap[shader].shader);
};
gl._bindAttribLocation = gl.bindAttribLocation;
gl.bindAttribLocation = function(program, index, name) {
var d = contextData[currentContext];
gl._bindAttribLocation(d.programMap[program], index, name);
};
gl._bindBuffer = gl.bindBuffer;
gl.bindBuffer = function(target, buffer) {
var d = contextData[currentContext];
gl._bindBuffer(target, buffer ? d.bufferMap[buffer] : null);
};
gl._bindFramebuffer = gl.bindFramebuffer;
gl.bindFramebuffer = function(target, framebuffer) {
var d = contextData[currentContext];
gl._bindFramebuffer(target, framebuffer ? d.framebufferMap[framebuffer] : null);
};
gl._bindRenderbuffer = gl.bindRenderbuffer;
gl.bindRenderbuffer = function(target, renderbuffer) {
var d = contextData[currentContext];
gl._bindRenderbuffer(target, renderbuffer ? d.renderbufferMap[renderbuffer] : null);
d.boundRenderbuffer = renderbuffer;
};
gl._bindTexture = gl.bindTexture;
gl.bindTexture = function(target, texture) {
gl._bindTexture(target, texture ? mapTexture(currentContext, texture) : null);
};
gl._bufferData = gl.bufferData;
gl.bufferData = function(target, usage, size, data) {
gl._bufferData(target, data === null || data.length === 0 ? size : data, usage);
};
gl._clearColor = gl.clearColor;
gl.clearColor = function (red, green, blue, alpha) {
gl._clearColor(red, green, blue, alpha);
};
gl.clearDepthf = function(depth) {
gl.clearDepth(depth);
};
gl._compileShader = gl.compileShader;
gl.compileShader = function(remoteShader) {
var d = contextData[currentContext];
gl._compileShader(d.shaderMap[remoteShader].shader);
};
gl._createProgram = gl.createProgram;
gl.createProgram = function() {
var d = contextData[currentContext];
var remoteProgram = d.nextProgramId++;
var localProgram = gl._createProgram();
d.programMap[remoteProgram] = localProgram;
return remoteProgram;
};
gl._createShader = gl.createShader;
gl.createShader = function(type) {
var d = contextData[currentContext];
var remoteShader = d.nextShaderId++;
var localShader = gl._createShader(type);
d.shaderMap[remoteShader] = { };
d.shaderMap[remoteShader].shader = localShader;
d.shaderMap[remoteShader].source = "";
return remoteShader;
};
gl.deleteBuffers = function(n) {
var d = contextData[currentContext];
for (var i = 0; i < n; ++i)
gl.deleteBuffer(d.bufferMap[arguments[1 +i]]);
};
gl.deleteFramebuffers = function(framebuffers) {
var d = contextData[currentContext];
for (var i in framebuffers)
gl.deleteFramebuffer(d.framebufferMap[framebuffers[i]]);
};
gl._deleteProgram = gl.deleteProgram;
gl.deleteProgram = function(program) {
var d = contextData[currentContext];
gl._deleteProgram(d.programMap[program]);
};
gl.deleteRenderbuffers = function(renderbuffers) {
var d = contextData[currentContext];
for (var i in renderbuffers)
gl.deleteRenderbuffer(d.renderbufferMap[renderbuffers[i]]);
};
gl._deleteShader = gl.deleteShader;
gl.deleteShader = function(remoteShader) {
var d = contextData[currentContext];
gl._deleteShader(d.shaderMap[remoteShader].shader);
};
gl.deleteTextures = function(textures) {
for (var i in textures)
gl.deleteTexture(mapTexture(currentContext, textures[i]));
};
gl._drawElements = gl.drawElements;
gl.drawElements = function(mode, count, type, n, indices, offset) {
if (!arguments[3].length)
gl._drawElements(mode, count, type, offset);
else
console.error("fixme: Client-side drawElements not supported");
};
gl._framebufferRenderbuffer = gl.framebufferRenderbuffer;
gl.framebufferRenderbuffer = function(target, attachment, renderbuffertarget, renderbuffer)
{
var d = contextData[currentContext];
// With packed depth-stencil Quick tries to attach the same renderbuffer to both
// the depth and stencil attachment points. WebGL does not allow this. Instead,
// we need to attach to the DEPTH_STENCIL attachment point.
if (d.renderbufferFormat[d.boundRenderbuffer] === gl.DEPTH_STENCIL) {
if (attachment === gl.STENCIL_ATTACHMENT) {
attachment = gl.DEPTH_STENCIL_ATTACHMENT;
} else {
// Ignore this call. Qt Quick will send a new call with STENCIL_ATTACHMENT
// parameter, it will be replaced by DEPTH_STENCIL_ATTACHMENT to work-around the
// browser limitation.
return;
}
}
gl._framebufferRenderbuffer(target, attachment, renderbuffertarget,
d.renderbufferMap[renderbuffer]);
};
gl.genBuffers = function(n) {
var d = contextData[currentContext];
var data = [];
for (var i = 0; i < n; ++i) {
var remoteBuf = d.nextBufferId++;
var localBuf = gl.createBuffer();
data.push(remoteBuf);
d.bufferMap[remoteBuf] = localBuf;
}
return data;
};
gl.genFramebuffers = function(n) {
var d = contextData[currentContext];
var data = [];
for (var i = 0; i < n; ++i) {
var remoteFramebuffer = d.nextFramebufferId++;
var localFramebuffer = gl.createFramebuffer();
d.framebufferMap[remoteFramebuffer] = localFramebuffer;
data.push(remoteFramebuffer);
}
return data;
};
gl.genRenderbuffers = function(n) {
var d = contextData[currentContext];
var data = [];
for (var i = 0; i < n; ++i) {
var remoteRenderBuffer = d.nextRenderBufferId++;
var localRenderBuffer = gl.createRenderbuffer();
d.renderbufferMap[remoteRenderBuffer] = localRenderBuffer;
data.push(remoteRenderBuffer);
}
return data;
};
gl.getAttachedShaders = function(program, maxCount) {
var d = contextData[currentContext];
var shaders = d.attachedShaderMap[program];
var data = [];
for (var shader in shaders) {
for (var remoteShaderId in shaderMap) {
if (shaderMap[remoteShaderId].shader === shader)
data.push(remoteShaderId);
}
}
return data;
};
gl._getAttribLocation = gl.getAttribLocation;
gl.getAttribLocation = function(program, name) {
if (typeof program === "object")
return gl._getAttribLocation(program, name);
var d = contextData[currentContext];
return gl._getAttribLocation(d.programMap[program], name);
};
gl.getBooleanv = gl.getParameter;
gl.getIntegerv = gl.getParameter;
gl.getProgramiv = function(program, pname) {
var d = contextData[currentContext];
if (pname === 0x8B84) // INFO_LOG_LENGTH
return gl._getProgramInfoLog(d.programMap[program]).length;
else
return gl.getProgramParameter(d.programMap[program], pname);
};
gl._getShaderInfoLog = gl.getShaderInfoLog;
gl.getShaderInfoLog = function(shader) {
if (typeof shader === "object")
return gl._getShaderInfoLog(shader);
var d = contextData[currentContext];
return gl._getShaderInfoLog(d.shaderMap[shader].shader);
};
gl.getShaderiv = function(shader, pname) {
var d = contextData[currentContext];
if (pname === 0x8B88)
return d.shaderMap[shader].source.length;
else
return gl.getShaderParameter(d.shaderMap[shader].shader, pname);
};
gl._getShaderSource = gl.getShaderSource;
gl.getShaderSource = function(remoteShader) {
var d = contextData[currentContext];
return gl._getShaderSource(d.shaderMap[remoteShader].shader);
};
gl.getString = function(pname) {
return gl.getParameter(pname);
};
gl._getProgramInfoLog = gl.getProgramInfoLog;
gl.getProgramInfoLog = function(remoteProgram) {
var d = contextData[currentContext];
var localProgram = d.programMap[remoteProgram];
return gl._getProgramInfoLog(localProgram);
};
gl._getUniformLocation = gl.getUniformLocation;
gl.getUniformLocation = function(program, name) {
if (typeof program === "object") {
return gl._getUniformLocation(program, name);
} else {
var d = contextData[currentContext];
var p = gl._getUniformLocation(d.programMap[program], name);
var location = -1;
if (p) {
location = d.nextLocation++;
d.uniformLocationMap[location] = p;
}
return location;
}
};
gl.genTextures = function(n) {
var d = contextData[currentContext];
var data = [];
for (var i = 0; i < n; ++i) {
var remoteTexture = d.nextTextureId++;
var localTexture = gl.createTexture();
d.textureMap[remoteTexture] = localTexture;
data.push(remoteTexture);
}
return data;
};
gl._framebufferTexture2D = gl.framebufferTexture2D;
gl.framebufferTexture2D = function(target, attachment, texTarget, texture, level) {
var d = contextData[currentContext];
gl._framebufferTexture2D(target, attachment, texTarget, d.textureMap[texture], level);
};
gl._isRenderbuffer = gl.isRenderbuffer;
gl.isRenderbuffer = function(renderbuffer) {
if (typeof renderbuffer === "object")
return gl._isRenderBuffer(renderbuffer);
var d = contextData[currentContext];
return gl._isRenderbuffer(d.renderbufferMap[renderbuffer]);
};
gl._linkProgram = gl.linkProgram;
gl.linkProgram = function(program) {
var d = contextData[currentContext];
gl._linkProgram(d.programMap[program]);
};
gl._renderbufferStorage = gl.renderbufferStorage;
gl.renderbufferStorage = function(target, internalFormat, width, height) {
var d = contextData[currentContext];
if (internalFormat === 0x88F0) // GL_DEPTH24_STENCIL8_OES
internalFormat = 0x84F9; // GL_DEPTH_STENCIL_OES
d.renderbufferFormat[d.boundRenderbuffer] = internalFormat;
gl._renderbufferStorage(target, internalFormat, width, height);
};
gl._texImage2D = gl.texImage2D;
gl.texImage2D = function(target, level, internalFormat, width, height, border, format, type,
data) {
var dataSize = data ? data.byteLength : 0;
var pixels;
if (data === null || dataSize === 0)
pixels = null;
else if (type === gl.UNSIGNED_BYTE)
pixels = data;
else if (type === g.UNSIGNED_SHORT_5_6_5 || type === UNSIGNED_SHORT_4_4_4_4 ||
type === UNSIGNED_SHORT_5_5_5_1)
pixels = new DataView(new Uint16Array(data));
else
console.error("gl.texImage2D: Unsupported type");
gl._texImage2D(target, level, internalFormat, width, height, border, format, type,
pixels);
};
gl._texSubImage2D = gl.texSubImage2D;
gl.texSubImage2D = function(target, level, xoffset, yoffset, width, height, format, type, data) {
var dataSize = data ? data.byteLength : 0;
var pixels;
if (data === null || dataSize === 0)
pixels = null;
if (type === gl.UNSIGNED_BYTE)
pixels = data;
else if (type === gl.UNSIGNED_SHORT_5_6_5 || type === gl.UNSIGNED_SHORT_4_4_4_4 ||
type === gl.UNSIGNED_SHORT_5_5_5_1 || type === ext.HALF_FLOAT_OES)
pixels = new Uint16Array(pixels);
else if (type === gl.FLOAT)
pixels = new Float32Array(pixels);
else
console.error("gl.texSubImage2D: Unsupported type");
gl._texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
};
gl._shaderSource = gl.shaderSource;
gl.shaderSource = function(shader, source) {
var d = contextData[currentContext];
d.shaderMap[shader].source = source;
gl._shaderSource(d.shaderMap[shader].shader, d.shaderMap[shader].source);
};
gl._uniform1f = gl.uniform1f;
gl.uniform1f = function(location, x) {
var d = contextData[currentContext];
gl._uniform1f(d.uniformLocationMap[location], x);
};
gl._uniform1fv = gl.uniform1fv;
gl.uniform1fv = function(location, value) {
var d = contextData[currentContext];
gl._uniform1fv(d.uniformLocationMap[location], value);
};
gl._uniform2fv = gl.uniform2fv;
gl.uniform2fv = function(location, value) {
var d = contextData[currentContext];
gl._uniform2fv(d.uniformLocationMap[location], value);
};
gl._uniform3fv = gl.uniform3fv;
gl.uniform3fv = function(location, value) {
var d = contextData[currentContext];
gl._uniform3fv(d.uniformLocationMap[location], value);
};
gl._uniform1i = gl.uniform1i;
gl.uniform1i = function(location, value) {
var d = contextData[currentContext];
gl._uniform1i(d.uniformLocationMap[location], value);
};
gl._uniform4fv = gl.uniform4fv;
gl.uniform4fv = function(location, value) {
var d = contextData[currentContext];
gl._uniform4fv(d.uniformLocationMap[location], value);
};
gl._uniformMatrix3fv = gl.uniformMatrix3fv;
gl.uniformMatrix3fv = function(location, transpose, data) {
var d = contextData[currentContext];
gl._uniformMatrix3fv(d.uniformLocationMap[location], transpose, data);
};
gl._uniformMatrix4fv = gl.uniformMatrix4fv;
gl.uniformMatrix4fv = function(location, transpose, data) {
var d = contextData[currentContext];
gl._uniformMatrix4fv(d.uniformLocationMap[location], transpose, data);
};
gl._useProgram = gl.useProgram;
gl.useProgram = function(program) {
var d = contextData[currentContext];
gl._useProgram(program !== 0 ? d.programMap[program] : null);
};
gl._vertexAttrib1fv = gl.vertexAttrib1fv;
gl.vertexAttrib1fv = function (index, v0) {
var values = new Float32Array([v0]);
gl._vertexAttrib1fv(index, values);
};
gl._vertexAttrib2fv = gl.vertexAttrib2fv;
gl.vertexAttrib2fv = function (index, v0, v1) {
var values = new Float32Array([v0, v1]);
gl._vertexAttrib2fv(index, values);
};
gl._vertexAttrib3fv = gl.vertexAttrib3fv;
gl.vertexAttrib3fv = function (index, v0, v1, v2) {
var values = new Float32Array([v0, v1, v2]);
gl._vertexAttrib3fv(index, values);
};
gl._drawArrays = gl.drawArrays;
gl.drawArrays = function (mode, first, count/*, size*/) {
var d = contextData[currentContext];
var subDataParts = [];
var bufferSize = 0;
var offset = 0;
if (!d.drawArrayBuf)
d.drawArrayBuf = gl.createBuffer();
gl._bindBuffer(gl.ARRAY_BUFFER, d.drawArrayBuf);
for (var i = 4; i < arguments.length; i += 6) {
var subData = {
"index": arguments[i + 0],
"size": arguments[i + 1],
"type": arguments[i + 2],
"normalized": arguments[i + 3],
"stride": arguments[i + 4],
"offset": 0,
"data": arguments[i + 5],
};
subDataParts.push(subData);
bufferSize += subData.data.length;
}
gl._bufferData(gl.ARRAY_BUFFER, bufferSize, gl.STATIC_DRAW);
for (var part in subDataParts) {
gl.bufferSubData(gl.ARRAY_BUFFER, offset, subDataParts[part].data);
gl._vertexAttribPointer(subDataParts[part].index,
subDataParts[part].size,
subDataParts[part].type,
subDataParts[part].normalized,
subDataParts[part].stride,
offset);
offset += subDataParts[part].data.length;
}
gl._drawArrays(mode, first, count);
};
gl._vertexAttribPointer = gl.vertexAttribPointer;
gl.vertexAttribPointer = function (index, size, type, normalized, stride, pointer) {
gl._vertexAttribPointer(index, size, type, normalized, stride, pointer);
};
};
var commandsNeedingResponse = {
"swapBuffers": undefined,
"checkFramebufferStatus": undefined,
"createProgram": undefined,
"createShader": undefined,
"genBuffers": undefined,
"genFramebuffers": undefined,
"genRenderbuffers": undefined,
"genTextures": undefined,
"getAttachedShaders": undefined,
"getAttribLocation": undefined,
"getBooleanv": undefined,
"getError": undefined,
"getFramebufferAttachmentParameteriv": undefined,
"getIntegerv": undefined,
"getParameter": undefined,
"getProgramInfoLog": undefined,
"getProgramiv": undefined,
"getRenderbufferParameteriv": undefined,
"getShaderiv": undefined,
"getShaderPrecisionFormat": undefined,
"getString": undefined,
"getTexParameterfv": undefined,
"getTexParameteriv": undefined,
"getUniformfv": undefined,
"getUniformLocation": undefined,
"getUniformiv": undefined,
"getVertexAttribfv": undefined,
"getVertexAttribiv": undefined,
"getShaderSource": undefined,
"getShaderInfoLog": undefined,
"isRenderbuffer": undefined
};
var ensureContextData = function (context) {
if (!(context in contextData)) {
contextData[context] = {
shaderMap: { },
programMap: { },
textureMap: { },
framebufferMap: { },
renderbufferMap: { },
renderbufferFormat: { },
boundRenderbuffer: 0,
bufferMap: { },
uniformLocationMap: { },
nextLocation: 1,
nextBufferId: 1,
nextProgramId: 1,
nextShaderId: 1,
nextFramebufferId: 1,
nextRenderBufferId: 1,
nextTextureId: 1,
pendingBinary: [],
drawArrayBuf: null,
drawArrayBufSize: 0,
glCommands: [],
attribData: []
};
}
};
var mapTexture = function (context, localId) {
var d = contextData[context];
if (localId in d.textureMap)
return d.textureMap[localId];
// ### do we need sharing?
console.error("Texture " + localId + " is not found in context " + context);
return 0;
};
var execGL = function (context) {
var d = contextData[context];
if (DEBUG)
console.log("executing " + d.glCommands.length + " commands");
while (d.glCommands.length) {
var obj = d.glCommands.shift();
if (DEBUG) {
var parameters = [];
for (var key in obj.parameters) {
if (typeof obj.parameters[key] === 'number' &&
d.glConstants[obj.parameters[key]] !== undefined) {
parameters[key] = d.glConstants[obj.parameters[key]] + ' (' +
obj.parameters[key] + ')';
} else {
parameters[key] = obj.parameters[key];
}
}
console.log("Calling: gl." + obj.function, parameters);
}
var response = gl[obj.function].apply(gl, obj.parameters);
if (response !== undefined)
sendResponse(obj.id, response);
}
};
var injectGL = function (context, funcName, parameters) {
contextData[context].glCommands.push({ "function": funcName, "parameters": parameters });
};
var handleBinaryMessage = function (event) {
var view = new DataView(event.data);
var offset = 0;
var obj = { "parameters": [] };
obj.function = supportedFunctions[view.getUint8(offset)];
offset += 1;
if (obj.function in commandsNeedingResponse) {
obj.id = view.getUint32(offset);
offset += 4;
}
if (obj.function === "makeCurrent")
obj.parameterCount = 4;
else if (obj.function === "swapBuffers")
obj.parameterCount = 0;
else if (obj.function == "drawArrays")
obj.parameterCount = null; // glDrawArrays has a variable number of arguments
else
obj.parameterCount = gl[obj.function].length;
function deserialize(container, count) {
for (var i = 0; count != null ? i < count : offset + 4 < event.data.byteLength; ++i) {
var character = view.getUint8(offset);
offset += 1;
var parameterType = String.fromCharCode(character);
if (parameterType === 'i') {
container.push(view.getInt32(offset));
offset += 4;
} else if (parameterType === 'u') {
container.push(view.getUint32(offset));
offset += 4;
} else if (parameterType === 'd') {
container.push(view.getFloat64(offset));
offset += 8;
} else if (parameterType === 'b') {
container.push(view.getUint8(offset) === 1);
offset += 1;
break;
} else if (parameterType === 's') {
var stringSize = view.getUint32(offset);
offset += 4;
var string = textDecoder.decode(new Uint8Array(event.data, offset, stringSize));
container.push(string);
offset += stringSize;
} else if (parameterType === 'x') {
var dataSize = view.getUint32(offset);
offset += 4;
var data = new Uint8Array(event.data, offset, dataSize);
var bytesRead = data.byteLength;
if (bytesRead !== dataSize)
console.error("invalid data");
container.push(data);
offset += dataSize;
} else if (parameterType === 'n') {
container.push(null);
} else if (parameterType === 'a') {
var dataSize = view.getUint8(offset);
offset += 1;
deserialize(container[container.push([]) - 1], dataSize);
} else {
console.error("Unsupported type :" + character);
}
}
}
deserialize(obj.parameters, obj.parameterCount);
var magic = view.getUint32(offset);
if (magic !== 0xbaadf00d) // sentinel expected at end of buffer
console.error('Invalid magic');
offset += 4;
if (offset !== event.data.byteLength)
console.error("Invalid buffer");
if (!("function" in obj)) {
console.error("Function not found");
} else if (obj.function === "makeCurrent") {
var winId = obj.parameters[3];
if (winId in windowData) {
canvas = windowData[winId].canvas;
canvas.width = canvas.style.width = obj.parameters[1];
canvas.height = canvas.style.height = obj.parameters[2];
gl = windowData[winId].gl;
currentWindowId = winId;
currentContext = obj.parameters[0];
if (currentContext)
ensureContextData(currentContext);
if (DEBUG) {
console.log("Current context is now " + currentContext);
var d = contextData[currentContext];
if (d.glConstants === undefined) {
d.glConstants = {};
for (var key in gl) {
if (typeof gl[key] === 'number') {
d.glConstants[gl[key]] = 'gl.' + key;
}
}
}
}
}
} else if (obj.function === "swapBuffers") {
var data = windowData[currentWindowId];
if (data.loadingCanvas) {
var body = document.getElementsByTagName("body")[0];
clearInterval(data.loadingCanvas.timerId);
body.removeChild(data.loadingCanvas);
data.loadingCanvas = undefined;
}
if (DEBUG)
var t0 = performance.now();
execGL(currentContext);
if (startTime) {
console.log((new Date() - startTime) + "ms to first frame.");
startTime = undefined;
}
var frameTime = performance.now() - t0;
if (DEBUG)
console.log("Swap time: " + frameTime + " ms.");
setTimeout((function () { sendResponse(obj.id, 1); }),
Math.max(SWAP_DELAY - frameTime, 0));
// have preserved swap and now we need to clear for real
} else {
handleGlesMessage(obj);
}
};
var handleGlesMessage = function (obj) {
// A GLES call. Unfortunately WebGL swaps when the control gets back to
// the event loop. This is totally retarded. So we queue the commands up
// and issue them when receiving a function that needs a response. This
// does not solve the problem and still needs preserved swap to be safe
// since eglSwapBuffers is not the only command that expects a response,
// but is more efficient than issuing everything from here.
var d = contextData[currentContext];
if (d)
d.glCommands.push(obj);
if (obj.function in commandsNeedingResponse)
execGL(currentContext);
};
socket.onopen = function (event) {
console.log("Socket Open");
(function(){
var doCheck = true;
var check = function(){
var size = getBrowserSize();
var width = size.width;
var height = size.height;
var physicalSize = physicalSizeRatio();
if (DEBUG)
console.log("Resizing canvas to " + width + " x " + height);
sendObject({ "type": "canvas_resize",
"width": width, "height": height,
"physicalWidth": width / physicalSize.width,
"physicalHeight": height / physicalSize.height
});
};
window.addEventListener("resize",(function(){
if(doCheck){
check();
doCheck = false;
setTimeout((function(){
doCheck = true;
check();
}), 1000);
}
}));
})();
connect();
};
socket.onclose = function (event) {
console.log("Socket Closed (" + event.code + "): " + event.reason);
window.location.reload();
};
socket.onerror = function (error) {
console.log("Socket error: " + error.toString());
};
socket.onmessage = function (event) {
if (event.data instanceof ArrayBuffer) {
handleBinaryMessage(event);
return;
}
var obj;
try {
obj = JSON.parse(event.data);
} catch (e) {
console.error("Failed to parse " + event.data + ": " + e.toString());
return;
}
if (!("type" in obj)) {
console.error("Message type not found");
} else if (obj.type === "create_canvas") {
createCanvas(obj.winId, obj.x, obj.y, obj.width, obj.height, obj.title);
if (obj.title && obj.title.length)
document.title = obj.title;
} else if (obj.type === "destroy_canvas") {
var canvas = document.getElementById(obj.winId);
var body = document.getElementsByTagName("body")[0];
body.removeChild(canvas);
} else if (obj.type === "clipboard_updated") {
// Opens a new window/tab and shows the current remote clipboard. There is no way to
// copy some text to the local clipboard without user interaction.
window.open("/clipboard", "clipboard");
} else if (obj.type === "open_url") {
window.open(obj.url);
} else if (obj.type === "change_title") {
document.title = obj.text;
} else if (obj.type === "connect") {
supportedFunctions = obj.supportedFunctions;
var sysinfo = obj.sysinfo;
if (obj.debug)
DEBUG = 1;
if (obj.mouseTracking)
MOUSETRACKING = 1;
if (obj.loadingScreen === "0")
LOADINGSCREEN = 0;
console.log(sysinfo);
} else {
console.error("Unknown message type");
}
};
var setupInput = function () {
var keyHandler = function (event) {
var object = { "type": event.type,
"char": event.char,
"key": event.key,
"which": event.which,
"location": event.location,
"repeat": event.repeat,
"locale": event.locale,
"ctrlKey": event.ctrlKey, "shiftKey": event.shiftKey, "altKey": event.altKey,
"metaKey": event.metaKey,
"string": String.fromCharCode(event.which ||
event.keyCode),
"keyCode": event.keyCode, "charCode": event.charCode, "code": event.code,
"time": new Date().getTime(),
};
sendObject(object);
}
document.addEventListener('keypress', keyHandler, true);
document.addEventListener('keydown', keyHandler, true);
document.addEventListener('keyup', keyHandler, true);
};
setupInput();
};