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(); };