diff --git a/js/x11/core/ext/glxrender.js b/js/x11/core/ext/glxrender.js new file mode 100644 index 0000000..a6a1710 --- /dev/null +++ b/js/x11/core/ext/glxrender.js @@ -0,0 +1,383 @@ +// see http://cgit.freedesktop.org/mesa/mesa/tree/src/mapi/glapi/gen/gl_API.xml + +var constants = Require('x11/core/ext/glxconstants'); + +var MAX_SMALL_RENDER=65536-16; + +module.exports = function(GLX, ctx) { + buffers = []; + var currentLength = 0; + + function commandBuffer(opcode, len) { + if (currentLength + len > MAX_SMALL_RENDER) { + render(); + } + if (len > MAX_SMALL_RENDER) + throw Error('Buffer too big. Make sure you are using RenderLarge for large commands'); + + currentLength += len; + var res = Buffer(len); + res.writeUInt16LE(len, 0); + res.writeUInt16LE(opcode, 2); + return res; + } + + function serialize0(opcode) { + buffers.push(commandBuffer(opcode, 4)); + } + + function serialize3fv(opcode, c1, c2, c3) { + var res = commandBuffer(opcode, 16); + res.writeFloatLE(c1, 4); + res.writeFloatLE(c2, 8); + res.writeFloatLE(c3, 12); + buffers.push(res); + } + + function serialize4fv(opcode, c1, c2, c3, c4) { + var res = commandBuffer(opcode, 20); + res.writeFloatLE(c1, 4); + res.writeFloatLE(c2, 8); + res.writeFloatLE(c3, 12); + res.writeFloatLE(c4, 16); + buffers.push(res); + } + + function serialize4i(opcode, c1, c2, c3, c4) { + var res = commandBuffer(opcode, 20); + res.writeInt32LE(c1, 4); + res.writeInt32LE(c2, 8); + res.writeInt32LE(c3, 12); + res.writeInt32LE(c4, 16); + buffers.push(res); + } + + function serialize6d(opcode, d1, d2, d3, d4, d5, d6) + { + var res = commandBuffer(opcode, 52); + res.writeDoubleLE(d1, 4); + res.writeDoubleLE(d2, 12); + res.writeDoubleLE(d3, 20); + res.writeDoubleLE(d4, 28); + res.writeDoubleLE(d5, 36); + res.writeDoubleLE(d6, 44); + buffers.push(res); + }; + + function serialize2i(opcode, value1, value2) { + var res = commandBuffer(opcode, 12); + res.writeUInt32LE(value1, 4); + res.writeUInt32LE(value2, 8); + buffers.push(res); + } + + function serialize1i(opcode, value) { + var res = commandBuffer(opcode, 8); + res.writeUInt32LE(value, 4); + buffers.push(res); + } + + function serialize1f(opcode, value) { + var res = commandBuffer(opcode, 8); + res.writeFloatLE(value, 4); + buffers.push(res); + } + + function serialize2f(opcode, f1, f2) { + var res = commandBuffer(opcode, 12); + res.writeFloatLE(f1, 4); + res.writeFloatLE(f2, 8); + buffers.push(res); + } + + function serialize2i(opcode, i1, i2) { + var res = commandBuffer(opcode, 12); + res.writeUInt32LE(i1, 4); + res.writeUInt32LE(i2, 8); + buffers.push(res); + } + + function serialize3i(opcode, i1, i2, i3) { + var res = commandBuffer(opcode, 16); + res.writeUInt32LE(i1, 4); + res.writeUInt32LE(i2, 8); + res.writeUInt32LE(i3, 12); + buffers.push(res); + } + + function serialize2i1f(opcode, i1, i2, f1) { + var res = commandBuffer(opcode, 16); + res.writeUInt32LE(i1, 4); + res.writeUInt32LE(i2, 8); + res.writeFloatLE(f1, 12); + buffers.push(res); + } + + function serialize2ifv(opcode, i1, i2, fv) { + var res = commandBuffer(opcode, 12 + fv.length*4); + res.writeUInt32LE(i1, 4); + res.writeUInt32LE(i2, 8); + for (var i=0; i < fv.length; ++i) + res.writeFloatLE(fv[i], 12+i*4); + buffers.push(res); + } + + function serialize2i4f(opcode, i1, i2, f1, f2, f3, f4) { + var res = commandBuffer(opcode, 28); + res.writeUInt32LE(i1, 4); + res.writeUInt32LE(i2, 8); + res.writeFloatLE(f1, 12); + res.writeFloatLE(f2, 16); + res.writeFloatLE(f3, 20); + res.writeFloatLE(f4, 24); + buffers.push(res); + } + + function render(ctxLocal) { + + if (!ctxLocal) // ctxLocal overrides ctx passed during creation of renderContext + ctxLocal = ctx; + + if (buffers.length == 0) { + buffers = []; + currentLength = 0; + return; + } + + GLX.Render(ctxLocal, buffers); + buffers = []; + currentLength = 0; + } + + var renderContext = { + Render: render, + Begin: function(what) { + serialize1i(4, what); + }, + End: function() { + serialize0(23); + }, + Ortho: function(left, right, bottom, top, znear, zfar) { + serialize6d(182, left, right, bottom, top, znear, zfar); + }, + Frustum: function(left, right, bottom, top, znear, zfar) { + serialize6d(182, left, right, bottom, top, znear, zfar); + }, + PopMatrix: function() { + serialize0(183); + + }, + PushMatrix: function() { + serialize0(184); + }, + LoadIdentity: function() { + serialize0(176); + }, + Rotatef: function(a, x, y, z) { + serialize4fv(186, a, x, y, z); + }, + CallList: function(list) { + serialize1i(1, list); + }, + Viewport: function(x, y, w, h) { + serialize4i(191, x, y, w, h); // TODO: x,y - signed, w,h - unsigned (currently all 4 unsigned) + }, + Vertex3f: function(x, y, z) { + serialize3fv(70, x, y, z); + }, + Vertex3fv: function(v) { + serialize3fv(70, v[0], v[1], v[2]); + }, + Color3f: function(r, g, b) { + serialize3fv(8, r, g, b); + }, + Normal3f: function(x, y, z) { + serialize3fv(30, x, y, z); + }, + Normal3fv: function(v) { + serialize3fv(70, v[0], v[1], v[2]); + }, + Color4f: function(r, g, b, a) { + serialize4fv(16, r, g, b, a); + }, + Scalef: function(x, y, z) { + serialize3fv(188, x, y, z); + }, + Translatef: function(x, y, z) { + serialize3fv(190, x, y, z); + }, + ClearColor: function(r, g, b, a) { + serialize4fv(0x82, r, g, b, a); + }, + MatrixMode: function(mode) { + serialize1i(179, mode); + }, + Enable: function(value) { + serialize1i(139, value); + }, + Lightfv: function(light, name, p1, p2, p3, p4) { + if (p1.length) + serialize2i4f(87, light, name, p1[0], p1[1], p1[2], p1[3]); + else + serialize2i4f(87, light, name, p1, p2, p3, p4); + }, + Materialfv: function(light, name, p1, p2, p3, p4) { + if (p1.length) + serialize2i4f(97, light, name, p1[0], p1[1], p1[2], p1[3]); + else + serialize2i4f(97, light, name, p1, p2, p3, p4); + }, + Clear: function(mask) { + serialize1i(0x7f, mask); + }, + ShadeModel: function(model) { + serialize1i(104, model); + }, + BlendFunc: function(sfactor, dfactor) { + serialize2i(160, sfactor, dfactor); + }, + PointSize: function(r) { + serialize1f(100, r); + }, + Hint: function(target, mode) { + serialize2i(85, target, mode); + }, + BindTexture: function(target, texture) { + serialize2i(4117, target, texture); + }, + TexEnvf: function(target, pname, param) { + serialize2i1f(112, target, pname, param); + }, + TexParameterf: function(target, pname, param) { + serialize2i1f(105, target, pname, param); + }, + TexParameterfv: function(target, pname, param) { + serialize2ifv(106, target, pname, param); + }, + TexParameteri: function(target, pname, param) { + serialize3i(107, target, pname, param); + }, + TexImage2D: function(target, level, internalFormat, width, height, border, format, type, data) { + + render(); + + var typeSize = []; + typeSize[constants.FLOAT] = 4; + typeSize[constants.BYTE] = 1; + typeSize[constants.UNSIGNED_BYTE] = 1; + + var res = new Buffer(60 + data.length*typeSize[type]); + res.writeUInt32LE(res.length, 0); + res.writeUInt32LE(110, 4); + + res[8] = 0; // swapbytes + res[9] = 0; // lsbfirst + res.writeUInt16LE(0, 10); // unused + + /* + defaults: (from http://stackoverflow.com/questions/21563590/glteximage2d-protocol-arguments?noredirect=1#comment32577251_21563590 ) + + GL_UNPACK_SWAP_BYTES boolean false true or false + GL_UNPACK_LSB_FIRST boolean false true or false + GL_UNPACK_ROW_LENGTH integer 0 [0,oo) + GL_UNPACK_SKIP_ROWS integer 0 [0,oo) + GL_UNPACK_SKIP_PIXELS integer 0 [0,oo) + GL_UNPACK_ALIGNMENT integer 4 1, 2, 4, or 8 + + */ + + res.writeUInt32LE(0, 12); // rowlength + res.writeUInt32LE(0, 16); // skiprows + res.writeUInt32LE(0, 20); // skippixels + res.writeUInt32LE(4, 24); // alignment + + res.writeUInt32LE(target, 28); + res.writeUInt32LE(level, 32); + res.writeUInt32LE(internalFormat, 36); + res.writeUInt32LE(width, 40); + res.writeUInt32LE(height, 44); + res.writeUInt32LE(border, 48); + res.writeUInt32LE(format, 52); + res.writeUInt32LE(type, 56); + + switch(type) { + case constants.FLOAT: + for (var i=0; i < data.length; ++i) + res.writeFloatLE(data[i], 60+i*typeSize[type]); + break; + case constants.BYTE: + case constants.UNSIGNED_BYTE: + for (var i=0; i < data.length; ++i) + res[60+i] = data[i]; + break; + default: + throw new Error('unsupported texture type:' + type); + } + + // bake sure buffer for glxRender request is emptied first + render(); + + var dataLen = res.length; + var maxSize = 262124; + var totalRequests = 1 + parseInt(dataLen / maxSize) - 1; + if (dataLen % maxSize) + totalRequests++; + + // for some reason RenderLarge does not like everything to be sent in one go + // add one extra buffer request for small requests + if (dataLen < maxSize) { + GLX.RenderLarge(ctx, 1, 2, res); + GLX.RenderLarge(ctx, 2, 2, Buffer(0)); + return; + } + + var pos = 0; + var reqNum = 1; + while(dataLen > 0) { + if (dataLen < maxSize) { + GLX.RenderLarge(ctx, reqNum, totalRequests, res.slice(pos)); + break; + } else { + GLX.RenderLarge(ctx, reqNum, totalRequests, res.slice(pos, pos + maxSize)); + pos += maxSize; + dataLen -= maxSize; + reqNum++; + } + } + + }, + + ProgramString: function(target, format, src) { + serialize3i(target, format, src); + buffers.push(Buffer(src)); + }, + + BindProgram: function(target, program) { + serialize2i(target, format, src); + }, + + + + TexCoord2f: function(x, y) { + serialize2f(54, x, y); + } + }; + + // import all constants + for (var c in constants) + renderContext[c] = constants[c]; + + // bind some glx functions + 'NewList EndList GenLists GenTextures IsTexture SwapBuffers Finish'.split(' ').forEach(function(name) { + // todo: small camelCase ? to be consistent with webgl api + //renderContext[name] = GLX[name].bind(GLX, ctx); + + // flush render buffer before glx requests + renderContext[name] = function(p1, p2, p3, p4, p5, p6, p7, p8) { + render(); + GLX[name](ctx, p1, p2, p3, p4, p5, p6, p7, p8); + } + }); + + return renderContext; +}