diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index e1479f8f02..d357ab5ce4 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -29,6 +29,7 @@ REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation) REGISTER_META_OBJECT(SphereRenderer) REGISTER_META_OBJECT(StaticModelRenderer) +static int texturePointerMetaTypeId = qRegisterMetaType(); static int bufferPointVectorMetaTypeId = qRegisterMetaType(); void MetavoxelSystem::init() { @@ -112,6 +113,11 @@ void MetavoxelSystem::render() { guideToAugmented(renderVisitor); } +void MetavoxelSystem::deleteTextures(const TexturePointer& height, const TexturePointer& color) { + delete height; + delete color; +} + MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) { return new MetavoxelSystemClient(node, _updater); } @@ -261,8 +267,19 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, _height(height), _color(color), _clearAfterLoading(clearAfterLoading), - _heightTexture(QOpenGLTexture::Target2D), - _colorTexture(QOpenGLTexture::Target2D) { + _heightTexture(new QOpenGLTexture(QOpenGLTexture::Target2D)), + _colorTexture(new QOpenGLTexture(QOpenGLTexture::Target2D)) { +} + +HeightfieldBuffer::~HeightfieldBuffer() { + // the textures have to be deleted on the main thread (for its opengl context) + if (QThread::currentThread() != Application::getInstance()->thread()) { + QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels(), "deleteTextures", + Q_ARG(const TexturePointer&, _heightTexture), Q_ARG(const TexturePointer&, _colorTexture)); + } else { + delete _heightTexture; + delete _colorTexture; + } } class HeightfieldPoint { @@ -273,39 +290,39 @@ public: void HeightfieldBuffer::render() { // initialize textures, etc. on first render - if (!_heightTexture.isCreated()) { + if (!_heightTexture->isCreated()) { int heightSize = glm::sqrt(_height.size()); - _heightTexture.setSize(heightSize, heightSize); - _heightTexture.setAutoMipMapGenerationEnabled(false); - _heightTexture.setMinificationFilter(QOpenGLTexture::Linear); - _heightTexture.setWrapMode(QOpenGLTexture::ClampToEdge); - _heightTexture.setFormat(QOpenGLTexture::LuminanceFormat); - _heightTexture.allocateStorage(); - _heightTexture.setData(QOpenGLTexture::Luminance, QOpenGLTexture::UInt8, _height.data()); + _heightTexture->setSize(heightSize, heightSize); + _heightTexture->setAutoMipMapGenerationEnabled(false); + _heightTexture->setMinificationFilter(QOpenGLTexture::Linear); + _heightTexture->setWrapMode(QOpenGLTexture::ClampToEdge); + _heightTexture->setFormat(QOpenGLTexture::LuminanceFormat); + _heightTexture->allocateStorage(); + _heightTexture->setData(QOpenGLTexture::Luminance, QOpenGLTexture::UInt8, _height.data()); if (_clearAfterLoading) { _height.clear(); } if (!_color.isEmpty()) { int colorSize = glm::sqrt(_color.size() / 3); - _colorTexture.setSize(colorSize, colorSize); + _colorTexture->setSize(colorSize, colorSize); } - _colorTexture.setAutoMipMapGenerationEnabled(false); - _colorTexture.setMinificationFilter(QOpenGLTexture::Linear); - _colorTexture.setWrapMode(QOpenGLTexture::ClampToEdge); - _colorTexture.setFormat(QOpenGLTexture::RGBFormat); - _colorTexture.allocateStorage(); + _colorTexture->setAutoMipMapGenerationEnabled(false); + _colorTexture->setMinificationFilter(QOpenGLTexture::Linear); + _colorTexture->setWrapMode(QOpenGLTexture::ClampToEdge); + _colorTexture->setFormat(QOpenGLTexture::RGBFormat); + _colorTexture->allocateStorage(); if (!_color.isEmpty()) { - _colorTexture.setData(QOpenGLTexture::RGB, QOpenGLTexture::UInt8, _color.data()); + _colorTexture->setData(QOpenGLTexture::RGB, QOpenGLTexture::UInt8, _color.data()); if (_clearAfterLoading) { _color.clear(); } } else { const quint8 WHITE_COLOR[] = { 255, 255, 255 }; - _colorTexture.setData(QOpenGLTexture::RGB, QOpenGLTexture::UInt8, const_cast(WHITE_COLOR)); + _colorTexture->setData(QOpenGLTexture::RGB, QOpenGLTexture::UInt8, const_cast(WHITE_COLOR)); } } // create the buffer objects lazily - int size = _heightTexture.width(); + int size = _heightTexture->width(); int sizeWithSkirt = size + 2; int vertexCount = sizeWithSkirt * sizeWithSkirt; int rows = sizeWithSkirt - 1; @@ -363,13 +380,13 @@ void HeightfieldBuffer::render() { glTranslatef(_translation.x, _translation.y, _translation.z); glScalef(_scale, _scale, _scale); - _heightTexture.bind(0); - _colorTexture.bind(1); + _heightTexture->bind(0); + _colorTexture->bind(1); glDrawRangeElements(GL_QUADS, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); - _colorTexture.release(1); - _heightTexture.release(0); + _colorTexture->release(1); + _heightTexture->release(0); glPopMatrix(); @@ -678,7 +695,7 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox _pointProgram.release(); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glColor4f(1.0f, 1.0f, 1.0f, 0.0f); _heightfieldProgram.bind(); diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 89ea7dcbda..725ef9ca71 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -26,6 +26,8 @@ class Model; +typedef QOpenGLTexture* TexturePointer; + /// Renders a metavoxel tree. class MetavoxelSystem : public MetavoxelClientManager { Q_OBJECT @@ -42,6 +44,8 @@ public: void simulate(float deltaTime); void render(); + Q_INVOKABLE void deleteTextures(const TexturePointer& height, const TexturePointer& color); + protected: virtual MetavoxelClient* createClient(const SharedNodePointer& node); @@ -57,6 +61,8 @@ private: QReadWriteLock _lodLock; }; +Q_DECLARE_METATYPE(TexturePointer) + /// Describes contents of a point in a point buffer. class BufferPoint { public: @@ -129,6 +135,7 @@ public: /// \param clearAfterLoading if true, clear the data arrays after we load them into textures in order to reclaim the space HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color, bool clearAfterLoading = true); + ~HeightfieldBuffer(); const glm::vec3& getTranslation() const { return _translation; } @@ -144,8 +151,8 @@ private: QByteArray _height; QByteArray _color; bool _clearAfterLoading; - QOpenGLTexture _heightTexture; - QOpenGLTexture _colorTexture; + TexturePointer _heightTexture; + TexturePointer _colorTexture; typedef QPair BufferPair; static QHash _bufferPairs; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index fefbbc3cd8..a255a9f8ef 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -51,9 +51,13 @@ AttributeRegistry::AttributeRegistry() : _heightfieldAttribute(registerAttribute(new HeightfieldAttribute("heightfield"))), _heightfieldColorAttribute(registerAttribute(new HeightfieldColorAttribute("heightfieldColor"))) { - // our baseline LOD threshold is for voxels; spanners are a different story + // our baseline LOD threshold is for voxels; spanners and heightfields are a different story const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f; _spannersAttribute->setLODThresholdMultiplier(SPANNER_LOD_THRESHOLD_MULTIPLIER); + + const float HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER = 32.0f; + _heightfieldAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); + _heightfieldColorAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); } static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) { @@ -461,16 +465,35 @@ HeightfieldData::HeightfieldData(const QByteArray& contents) : _contents(contents) { } +const int BYTES_PER_PIXEL = 3; + +HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) : + _encoded(in.readAligned(bytes)) { + + QImage image = QImage::fromData(_encoded).convertToFormat(QImage::Format_RGB888); + if (color) { + _contents.resize(image.width() * image.height() * BYTES_PER_PIXEL); + memcpy(_contents.data(), image.constBits(), _contents.size()); + + } else { + _contents.resize(image.width() * image.height()); + char* dest = _contents.data(); + for (const uchar* src = image.constBits(), *end = src + _contents.size() * BYTES_PER_PIXEL; + src != end; src += BYTES_PER_PIXEL) { + *dest++ = *src; + } + } +} + void HeightfieldData::write(Bitstream& out, bool color) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { - QImage image; - const int BYTES_PER_PIXEL = 3; + QImage image; if (color) { - int size = glm::sqrt(_contents.size() / (double)BYTES_PER_PIXEL); + int size = glm::sqrt(_contents.size() / (float)BYTES_PER_PIXEL); image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); } else { - int size = glm::sqrt((double)_contents.size()); + int size = glm::sqrt((float)_contents.size()); image = QImage(size, size, QImage::Format_RGB888); uchar* dest = image.bits(); for (const char* src = _contents.constData(), *end = src + _contents.size(); src != end; src++) { @@ -483,7 +506,8 @@ void HeightfieldData::write(Bitstream& out, bool color) { buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPG"); } - + out << _encoded.size(); + out.writeAligned(_encoded); } HeightfieldAttribute::HeightfieldAttribute(const QString& name) : @@ -492,6 +516,13 @@ HeightfieldAttribute::HeightfieldAttribute(const QString& name) : void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { if (isLeaf) { + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); + } } } @@ -500,11 +531,65 @@ void HeightfieldAttribute::write(Bitstream& out, void* value, bool isLeaf) const HeightfieldDataPointer data = decodeInline(value); if (data) { data->write(out, false); + } else { + out << 0; } } } bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) const { + int maxSize = 0; + for (int i = 0; i < MERGE_COUNT; i++) { + HeightfieldDataPointer pointer = decodeInline(children[i]); + if (pointer) { + maxSize = qMax(maxSize, pointer->getContents().size()); + } + } + if (maxSize == 0) { + *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + return true; + } + int size = glm::sqrt((float)maxSize); + QByteArray contents(size * size, 0); + int halfSize = size / 2; + for (int i = 0; i < MERGE_COUNT; i++) { + HeightfieldDataPointer child = decodeInline(children[i]); + if (!child) { + continue; + } + const QByteArray& childContents = child->getContents(); + int childSize = glm::sqrt((float)childContents.size()); + if (childSize != size) { + continue; // TODO: handle differently-sized children + } + const int INDEX_MASK = 1; + int xIndex = i & INDEX_MASK; + const int Y_SHIFT = 1; + int yIndex = (i >> Y_SHIFT) & INDEX_MASK; + if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { + continue; // bottom is overriden by top + } + const int HALF_RANGE = 128; + int yOffset = yIndex * HALF_RANGE; + int Z_SHIFT = 2; + int zIndex = (i >> Z_SHIFT) & INDEX_MASK; + char* dest = contents.data() + (zIndex * halfSize * size) + (xIndex * halfSize); + uchar* src0 = (uchar*)childContents.data(); + uchar* src1 = src0 + 1; + uchar* src2 = src0 + childSize; + uchar* src3 = src2 + 1; + for (int z = 0; z < halfSize; z++) { + for (char* end = dest + halfSize; dest != end; ) { + *dest++ = yOffset + (qMax(qMax(*src0++, *src1++), qMax(*src2++, *src3++)) >> 1); + } + dest += halfSize; + src0 += childSize; + src1 += childSize; + src2 += childSize; + src3 += childSize; + } + } + *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(new HeightfieldData(contents)); return false; } @@ -514,6 +599,13 @@ HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) : void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { if (isLeaf) { + int size; + in >> size; + if (size == 0) { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + } else { + *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); + } } } @@ -522,11 +614,67 @@ void HeightfieldColorAttribute::write(Bitstream& out, void* value, bool isLeaf) HeightfieldDataPointer data = decodeInline(value); if (data) { data->write(out, true); + } else { + out << 0; } } } bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool postRead) const { + int maxSize = 0; + for (int i = 0; i < MERGE_COUNT; i++) { + HeightfieldDataPointer pointer = decodeInline(children[i]); + if (pointer) { + maxSize = qMax(maxSize, pointer->getContents().size()); + } + } + if (maxSize == 0) { + *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + return true; + } + int size = glm::sqrt(maxSize / (float)BYTES_PER_PIXEL); + QByteArray contents(size * size * BYTES_PER_PIXEL, 0); + int halfSize = size / 2; + for (int i = 0; i < MERGE_COUNT; i++) { + HeightfieldDataPointer child = decodeInline(children[i]); + if (!child) { + continue; + } + const QByteArray& childContents = child->getContents(); + int childSize = glm::sqrt(childContents.size() / (float)BYTES_PER_PIXEL); + if (childSize != size) { + continue; // TODO: handle differently-sized children + } + const int INDEX_MASK = 1; + int xIndex = i & INDEX_MASK; + const int Y_SHIFT = 1; + int yIndex = (i >> Y_SHIFT) & INDEX_MASK; + if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { + continue; // bottom is overriden by top + } + int Z_SHIFT = 2; + int zIndex = (i >> Z_SHIFT) & INDEX_MASK; + char* dest = contents.data() + ((zIndex * halfSize * size) + (xIndex * halfSize)) * BYTES_PER_PIXEL; + uchar* src0 = (uchar*)childContents.data(); + uchar* src1 = src0 + BYTES_PER_PIXEL; + int childStride = childSize * BYTES_PER_PIXEL; + uchar* src2 = src0 + childStride; + uchar* src3 = src2 + BYTES_PER_PIXEL; + int halfStride = halfSize * BYTES_PER_PIXEL; + for (int z = 0; z < halfSize; z++) { + for (char* end = dest + halfSize * BYTES_PER_PIXEL; dest != end; ) { + *dest++ = ((int)(*src0++) + (int)(*src1++) + (int)(*src2++) + (int)(*src3++)) >> 2; + *dest++ = ((int)(*src0++) + (int)(*src1++) + (int)(*src2++) + (int)(*src3++)) >> 2; + *dest++ = ((int)(*src0++) + (int)(*src1++) + (int)(*src2++) + (int)(*src3++)) >> 2; + } + dest += halfStride; + src0 += childStride; + src1 += childStride; + src2 += childStride; + src3 += childStride; + } + } + *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(new HeightfieldData(contents)); return false; } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 3ef37646d6..767ebf6527 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -422,6 +422,7 @@ class HeightfieldData : public QSharedData { public: HeightfieldData(const QByteArray& contents); + HeightfieldData(Bitstream& in, int bytes, bool color); const QByteArray& getContents() const { return _contents; } diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index f49ae1c04f..e9ac3d6319 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -639,6 +639,16 @@ void Bitstream::readRawDelta(QScriptValue& value, const QScriptValue& reference) } } +void Bitstream::writeAligned(const QByteArray& data) { + flush(); + _underlying.device()->write(data); +} + +QByteArray Bitstream::readAligned(int bytes) { + reset(); + return _underlying.device()->read(bytes); +} + Bitstream& Bitstream::operator<<(bool value) { if (value) { _byte |= (1 << _position); diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 7602424ded..1fd9205387 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -430,6 +430,11 @@ public: template void writeRawDelta(const QHash& value, const QHash& reference); template void readRawDelta(QHash& value, const QHash& reference); + /// Writes the specified array aligned on byte boundaries to avoid the inefficiency + /// of bit-twiddling (at the cost of up to seven bits of wasted space). + void writeAligned(const QByteArray& data); + QByteArray readAligned(int bytes); + Bitstream& operator<<(bool value); Bitstream& operator>>(bool& value);