diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index b72f52351f..18f39cd3df 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -647,12 +647,13 @@ void OpenGLDisplayPlugin::withMainThreadContext(std::function f) const { } QImage OpenGLDisplayPlugin::getScreenshot() const { - QImage result; + using namespace oglplus; + QImage screenshot(_compositeFramebuffer->size.x, _compositeFramebuffer->size.y, QImage::Format_RGBA8888); withMainThreadContext([&] { - static auto widget = _container->getPrimaryWidget(); - result = widget->grabFrameBuffer(); + Framebuffer::Bind(Framebuffer::Target::Read, _compositeFramebuffer->fbo); + Context::ReadPixels(0, 0, _compositeFramebuffer->size.x, _compositeFramebuffer->size.y, enums::PixelDataFormat::RGBA, enums::PixelDataType::UnsignedByte, screenshot.bits()); }); - return result; + return screenshot.mirrored(false, true); } uint32_t OpenGLDisplayPlugin::getSceneTextureId() const { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 21e5865c09..ef0401ceaf 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -904,7 +904,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endDecode = usecTimestampNow(); const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec - if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) { + if ((message.getType() == PacketType::EntityAdd || + (message.getType() == PacketType::EntityEdit && properties.lifetimeChanged())) && + !senderNode->getCanRez() && senderNode->getCanRezTmp()) { // this node is only allowed to rez temporary entities. if need be, cap the lifetime. if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME || properties.getLifetime() > _maxTmpEntityLifetime) { diff --git a/libraries/script-engine/src/TypedArrayPrototype.cpp b/libraries/script-engine/src/TypedArrayPrototype.cpp index bb612b393f..4de948e806 100644 --- a/libraries/script-engine/src/TypedArrayPrototype.cpp +++ b/libraries/script-engine/src/TypedArrayPrototype.cpp @@ -71,8 +71,10 @@ QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { end = (end < 0) ? length + end : end; // here we clamp the indices to fit the array + // note: begin offset is *inclusive* while end offset is *exclusive* + // (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray#Parameters) begin = glm::clamp(begin, 0, (length - 1)); - end = glm::clamp(end, 0, (length - 1)); + end = glm::clamp(end, 0, length); byteOffset += begin * bytesPerElement; length = (end - begin > 0) ? end - begin : 0; diff --git a/libraries/script-engine/src/TypedArrays.cpp b/libraries/script-engine/src/TypedArrays.cpp index c1c4117f76..4d5181ff33 100644 --- a/libraries/script-engine/src/TypedArrays.cpp +++ b/libraries/script-engine/src/TypedArrays.cpp @@ -88,7 +88,11 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin if (arrayBuffer) { if (context->argumentCount() == 1) { // Case for entire ArrayBuffer - newObject = cls->newInstance(bufferArg, 0, arrayBuffer->size()); + if (arrayBuffer->size() % cls->_bytesPerElement != 0) { + engine->evaluate("throw \"RangeError: byteLength is not a multiple of BYTES_PER_ELEMENT\""); + } + quint32 length = arrayBuffer->size() / cls->_bytesPerElement; + newObject = cls->newInstance(bufferArg, 0, length); } else { QScriptValue byteOffsetArg = context->argument(1); if (!byteOffsetArg.isNumber()) { @@ -206,6 +210,7 @@ QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& QDataStream stream(*arrayBuffer); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); T result; stream >> result; return result; @@ -218,6 +223,7 @@ void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint if (arrayBuffer && value.isNumber()) { QDataStream stream(arrayBuffer, QIODevice::ReadWrite); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream << (T)value.toNumber(); } @@ -357,6 +363,7 @@ QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScri if (ok && arrayBuffer) { QDataStream stream(*arrayBuffer); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::SinglePrecision); float result; @@ -375,6 +382,7 @@ void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& n if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::SinglePrecision); stream << (float)value.toNumber(); @@ -392,6 +400,7 @@ QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScri if (ok && arrayBuffer) { QDataStream stream(*arrayBuffer); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::DoublePrecision); double result; @@ -410,6 +419,7 @@ void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& n if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); stream.skipRawData(id); + stream.setByteOrder(QDataStream::LittleEndian); stream.setFloatingPointPrecision(QDataStream::DoublePrecision); stream << (double)value.toNumber(); diff --git a/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js b/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js index a50a40e43e..c61865c8d6 100644 --- a/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js +++ b/scripts/developer/utilities/diagnostics/typedArraysUnitTest.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/unitTest.js"); +Script.include("../../../../script-archive/libraries/unitTest.js"); // e.g. extractbits([0xff, 0x80, 0x00, 0x00], 23, 30); inclusive function extractbits(bytes, lo, hi) { @@ -551,6 +551,20 @@ test('TypedArray.subarray', function () { this.arrayEqual(a.subarray(-1, -4), []); this.arrayEqual(a.subarray(1).subarray(1), [3, 4, 5]); this.arrayEqual(a.subarray(1, 4).subarray(1, 2), [3]); + + var a = new Float32Array(16); + a[0] = -1; + a[15] = 1/8; + this.assertEquals(a.length, 16); + this.assertEquals(a.byteLength, a.length * a.BYTES_PER_ELEMENT); + + this.assertEquals(a.subarray(-12).length, 12, '[-12,)'); + this.arrayEqual(a.subarray(-16), a, '[-16,)'); + this.arrayEqual(a.subarray(12, 16), [0,0,0,1/8], '[12,16)'); + this.arrayEqual(a.subarray(0, 4), [-1,0,0,0],'[0,4)'); + this.arrayEqual(a.subarray(-16, -12), [-1,0,0,0],'[-16,-12)'); + this.assertEquals(a.subarray(0, -12).length, 4,'[0,-12)'); + this.arrayEqual(a.subarray(-10), a.subarray(6),'[-10,)'); }); @@ -706,3 +720,45 @@ test('Regression Tests', function() { this.assertEquals(truncated, -minFloat32, 'smallest 32 bit float should not truncate to zero'); }); +test('new TypedArray(ArrayBuffer).length Tests', function() { + var uint8s = new Uint8Array([0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff]), + buffer = uint8s.buffer; + + this.assertEquals(buffer.byteLength, 8, 'buffer.length'); + + var _this = this; + [ + 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array', + 'Float32Array', 'Float64Array', 'Uint8ClampedArray' + ].forEach(function(typeArrayName) { + var typeArray = eval(typeArrayName); + var a = new typeArray(buffer); + _this.assertEquals(a.BYTES_PER_ELEMENT, typeArrayName.match(/\d+/)[0]/8, typeArrayName+'.BYTES_PER_ELEMENT'); + _this.assertEquals(a.byteLength, buffer.byteLength, typeArrayName+'.byteLength'); + _this.assertEquals(a.length, buffer.byteLength / typeArray.BYTES_PER_ELEMENT, typeArrayName+'.length'); + }); +}); + +test('Native endianness check', function() { + var buffer = ArrayBuffer(4); + new Uint8Array(buffer).set([0xaa, 0xbb, 0xcc, 0xdd]); + var endian = { aabbccdd: 'big', ddccbbaa: 'little' }[ + new Uint32Array(buffer)[0].toString(16) + ]; + this.assertEquals(endian, 'little'); +}); + +test('TypeArray byte order tests', function() { + var uint8s = new Uint8Array([0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff]), + buffer = uint8s.buffer; + + this.arrayEqual(new Uint8Array(buffer), [0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff], "Uint8Array"); + this.arrayEqual(new Uint16Array(buffer), [0x00ff, 0x0000, 0x0000, 0xff00], "Uint16Array"); + this.arrayEqual(new Uint32Array(buffer), [0x000000ff, 0xff000000], "Uint32Array"); + + this.arrayEqual(new Int8Array(buffer), [-1,0,0,0,0,0,0,-1], "Int8Array"); + this.arrayEqual(new Int16Array(buffer), [255, 0, 0, -256], "Int16Array"); + + this.arrayEqual(new Float32Array(buffer), [3.5733110840282835e-43, -1.7014118346046923e+38], "Float32Array"); + this.arrayEqual(new Float64Array(buffer), [-5.486124068793999e+303], "Float64Array"); +}); diff --git a/scripts/system/users.js b/scripts/system/users.js index 2ff2689bad..853067b90b 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -344,6 +344,7 @@ var usersWindow = (function () { windowTextHeight, windowLineSpacing, windowLineHeight, // = windowTextHeight + windowLineSpacing + windowMinimumHeight, usersOnline, // Raw users data linesOfUsers = [], // Array of indexes pointing into usersOnline @@ -435,6 +436,11 @@ var usersWindow = (function () { } } + function saturateWindowPosition() { + windowPosition.x = Math.max(0, Math.min(viewport.x - WINDOW_WIDTH, windowPosition.x)); + windowPosition.y = Math.max(windowMinimumHeight, Math.min(viewport.y, windowPosition.y)); + } + function updateOverlayPositions() { // Overlay positions are all relative to windowPosition; windowPosition is the position of the windowPane overlay. var windowLeft = windowPosition.x, @@ -854,6 +860,8 @@ var usersWindow = (function () { x: event.x - movingClickOffset.x, y: event.y - movingClickOffset.y }; + + saturateWindowPosition(); calculateWindowHeight(); updateOverlayPositions(); updateUsersDisplay(); @@ -947,6 +955,7 @@ var usersWindow = (function () { windowTextHeight = Math.floor(Overlays.textSize(textSizeOverlay, "1").height); windowLineSpacing = Math.floor(Overlays.textSize(textSizeOverlay, "1\n2").height - 2 * windowTextHeight); windowLineHeight = windowTextHeight + windowLineSpacing; + windowMinimumHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN; Overlays.deleteOverlay(textSizeOverlay); viewport = Controller.getViewportDimensions(); @@ -958,7 +967,6 @@ var usersWindow = (function () { if (offset.hasOwnProperty("x") && offset.hasOwnProperty("y")) { windowPosition.x = offset.x < 0 ? viewport.x + offset.x : offset.x; windowPosition.y = offset.y <= 0 ? viewport.y + offset.y : offset.y; - } else { hmdViewport = Controller.getRecommendedOverlayRect(); windowPosition = { @@ -967,6 +975,7 @@ var usersWindow = (function () { }; } + saturateWindowPosition(); calculateWindowHeight(); windowBorder = Overlays.addOverlay("rectangle", {