File indexing completed on 2024-05-19 05:31:56

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
0006     SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "glvertexbuffer.h"
0012 #include "glframebuffer.h"
0013 #include "glplatform.h"
0014 #include "glshader.h"
0015 #include "glshadermanager.h"
0016 #include "glutils.h"
0017 #include "utils/common.h"
0018 
0019 #include <QVector4D>
0020 #include <bitset>
0021 #include <deque>
0022 
0023 namespace KWin
0024 {
0025 
0026 // Certain GPUs, especially mobile, require the data copied to the GPU to be aligned to a
0027 // certain amount of bytes. For example, the Mali GPU requires data to be aligned to 8 bytes.
0028 // This function helps ensure that the data is aligned.
0029 template<typename T>
0030 T align(T value, int bytes)
0031 {
0032     return (value + bytes - 1) & ~T(bytes - 1);
0033 }
0034 
0035 class IndexBuffer
0036 {
0037 public:
0038     IndexBuffer();
0039     ~IndexBuffer();
0040 
0041     void accommodate(size_t count);
0042     void bind();
0043 
0044 private:
0045     GLuint m_buffer;
0046     size_t m_count = 0;
0047     std::vector<uint16_t> m_data;
0048 };
0049 
0050 IndexBuffer::IndexBuffer()
0051 {
0052     // The maximum number of quads we can render with 16 bit indices is 16,384.
0053     // But we start with 512 and grow the buffer as needed.
0054     glGenBuffers(1, &m_buffer);
0055     accommodate(512);
0056 }
0057 
0058 IndexBuffer::~IndexBuffer()
0059 {
0060     glDeleteBuffers(1, &m_buffer);
0061 }
0062 
0063 void IndexBuffer::accommodate(size_t count)
0064 {
0065     // Check if we need to grow the buffer.
0066     if (count <= m_count) {
0067         return;
0068     }
0069     Q_ASSERT(m_count * 2 < std::numeric_limits<uint16_t>::max() / 4);
0070     const size_t oldCount = m_count;
0071     m_count *= 2;
0072     m_data.reserve(m_count * 6);
0073     for (size_t i = oldCount; i < m_count; i++) {
0074         const uint16_t offset = i * 4;
0075         m_data[i * 6 + 0] = offset + 1;
0076         m_data[i * 6 + 1] = offset + 0;
0077         m_data[i * 6 + 2] = offset + 3;
0078         m_data[i * 6 + 3] = offset + 3;
0079         m_data[i * 6 + 4] = offset + 2;
0080         m_data[i * 6 + 5] = offset + 1;
0081     }
0082     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer);
0083     glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_count * sizeof(uint16_t), m_data.data(), GL_STATIC_DRAW);
0084 }
0085 
0086 void IndexBuffer::bind()
0087 {
0088     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer);
0089 }
0090 
0091 // ------------------------------------------------------------------
0092 
0093 struct VertexAttrib
0094 {
0095     int size;
0096     GLenum type;
0097     int offset;
0098 };
0099 
0100 // ------------------------------------------------------------------
0101 
0102 struct BufferFence
0103 {
0104     GLsync sync;
0105     intptr_t nextEnd;
0106 
0107     bool signaled() const
0108     {
0109         GLint value;
0110         glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value);
0111         return value == GL_SIGNALED;
0112     }
0113 };
0114 
0115 static void deleteAll(std::deque<BufferFence> &fences)
0116 {
0117     for (const BufferFence &fence : fences) {
0118         glDeleteSync(fence.sync);
0119     }
0120 
0121     fences.clear();
0122 }
0123 
0124 // ------------------------------------------------------------------
0125 
0126 template<size_t Count>
0127 struct FrameSizesArray
0128 {
0129 public:
0130     FrameSizesArray()
0131     {
0132         m_array.fill(0);
0133     }
0134 
0135     void push(size_t size)
0136     {
0137         m_array[m_index] = size;
0138         m_index = (m_index + 1) % Count;
0139     }
0140 
0141     size_t average() const
0142     {
0143         size_t sum = 0;
0144         for (size_t size : m_array) {
0145             sum += size;
0146         }
0147         return sum / Count;
0148     }
0149 
0150 private:
0151     std::array<size_t, Count> m_array;
0152     int m_index = 0;
0153 };
0154 
0155 //*********************************
0156 // GLVertexBufferPrivate
0157 //*********************************
0158 class GLVertexBufferPrivate
0159 {
0160 public:
0161     GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint)
0162         : vertexCount(0)
0163         , persistent(false)
0164         , bufferSize(0)
0165         , bufferEnd(0)
0166         , mappedSize(0)
0167         , frameSize(0)
0168         , nextOffset(0)
0169         , baseAddress(0)
0170         , map(nullptr)
0171     {
0172         glGenBuffers(1, &buffer);
0173 
0174         switch (usageHint) {
0175         case GLVertexBuffer::Dynamic:
0176             usage = GL_DYNAMIC_DRAW;
0177             break;
0178         case GLVertexBuffer::Static:
0179             usage = GL_STATIC_DRAW;
0180             break;
0181         default:
0182             usage = GL_STREAM_DRAW;
0183             break;
0184         }
0185     }
0186 
0187     ~GLVertexBufferPrivate()
0188     {
0189         deleteAll(fences);
0190 
0191         if (buffer != 0) {
0192             glDeleteBuffers(1, &buffer);
0193             map = nullptr;
0194         }
0195     }
0196 
0197     void bindArrays();
0198     void unbindArrays();
0199     void reallocateBuffer(size_t size);
0200     GLvoid *mapNextFreeRange(size_t size);
0201     void reallocatePersistentBuffer(size_t size);
0202     bool awaitFence(intptr_t offset);
0203     GLvoid *getIdleRange(size_t size);
0204 
0205     GLuint buffer;
0206     GLenum usage;
0207     int vertexCount;
0208     static std::unique_ptr<GLVertexBuffer> streamingBuffer;
0209     static bool haveBufferStorage;
0210     static bool haveSyncFences;
0211     static bool hasMapBufferRange;
0212     static bool supportsIndexedQuads;
0213     QByteArray dataStore;
0214     bool persistent;
0215     size_t bufferSize;
0216     intptr_t bufferEnd;
0217     size_t mappedSize;
0218     size_t frameSize;
0219     intptr_t nextOffset;
0220     intptr_t baseAddress;
0221     uint8_t *map;
0222     std::deque<BufferFence> fences;
0223     FrameSizesArray<4> frameSizes;
0224     std::array<VertexAttrib, VertexAttributeCount> attrib;
0225     size_t attribStride = 0;
0226     std::bitset<32> enabledArrays;
0227     static std::unique_ptr<IndexBuffer> s_indexBuffer;
0228 };
0229 
0230 bool GLVertexBufferPrivate::hasMapBufferRange = false;
0231 bool GLVertexBufferPrivate::supportsIndexedQuads = false;
0232 std::unique_ptr<GLVertexBuffer> GLVertexBufferPrivate::streamingBuffer;
0233 bool GLVertexBufferPrivate::haveBufferStorage = false;
0234 bool GLVertexBufferPrivate::haveSyncFences = false;
0235 std::unique_ptr<IndexBuffer> GLVertexBufferPrivate::s_indexBuffer;
0236 
0237 void GLVertexBufferPrivate::bindArrays()
0238 {
0239     glBindBuffer(GL_ARRAY_BUFFER, buffer);
0240 
0241     for (size_t i = 0; i < enabledArrays.size(); i++) {
0242         if (enabledArrays[i]) {
0243             glVertexAttribPointer(i, attrib[i].size, attrib[i].type, GL_FALSE, attribStride,
0244                                   (const GLvoid *)(baseAddress + attrib[i].offset));
0245             glEnableVertexAttribArray(i);
0246         }
0247     }
0248 }
0249 
0250 void GLVertexBufferPrivate::unbindArrays()
0251 {
0252     for (size_t i = 0; i < enabledArrays.size(); i++) {
0253         if (enabledArrays[i]) {
0254             glDisableVertexAttribArray(i);
0255         }
0256     }
0257 }
0258 
0259 void GLVertexBufferPrivate::reallocatePersistentBuffer(size_t size)
0260 {
0261     if (buffer != 0) {
0262         // This also unmaps and unbinds the buffer
0263         glDeleteBuffers(1, &buffer);
0264         buffer = 0;
0265 
0266         deleteAll(fences);
0267     }
0268 
0269     if (buffer == 0) {
0270         glGenBuffers(1, &buffer);
0271     }
0272 
0273     // Round the size up to 64 kb
0274     size_t minSize = std::max<size_t>(frameSizes.average() * 3, 128 * 1024);
0275     bufferSize = std::max(size, minSize);
0276 
0277     const GLbitfield storage = GL_DYNAMIC_STORAGE_BIT;
0278     const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
0279 
0280     glBindBuffer(GL_ARRAY_BUFFER, buffer);
0281     glBufferStorage(GL_ARRAY_BUFFER, bufferSize, nullptr, storage | access);
0282 
0283     map = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, access);
0284 
0285     nextOffset = 0;
0286     bufferEnd = bufferSize;
0287 }
0288 
0289 bool GLVertexBufferPrivate::awaitFence(intptr_t end)
0290 {
0291     // Skip fences until we reach the end offset
0292     while (!fences.empty() && fences.front().nextEnd < end) {
0293         glDeleteSync(fences.front().sync);
0294         fences.pop_front();
0295     }
0296 
0297     Q_ASSERT(!fences.empty());
0298 
0299     // Wait on the next fence
0300     const BufferFence &fence = fences.front();
0301 
0302     if (!fence.signaled()) {
0303         qCDebug(KWIN_OPENGL) << "Stalling on VBO fence";
0304         const GLenum ret = glClientWaitSync(fence.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000);
0305 
0306         if (ret == GL_TIMEOUT_EXPIRED || ret == GL_WAIT_FAILED) {
0307             qCCritical(KWIN_OPENGL) << "Wait failed";
0308             return false;
0309         }
0310     }
0311 
0312     glDeleteSync(fence.sync);
0313 
0314     // Update the end pointer
0315     bufferEnd = fence.nextEnd;
0316     fences.pop_front();
0317 
0318     return true;
0319 }
0320 
0321 GLvoid *GLVertexBufferPrivate::getIdleRange(size_t size)
0322 {
0323     if (size > bufferSize) {
0324         reallocatePersistentBuffer(size * 2);
0325     }
0326 
0327     // Handle wrap-around
0328     if ((nextOffset + size > bufferSize)) {
0329         nextOffset = 0;
0330         bufferEnd -= bufferSize;
0331 
0332         for (BufferFence &fence : fences) {
0333             fence.nextEnd -= bufferSize;
0334         }
0335 
0336         // Emit a fence now
0337         if (auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) {
0338             fences.push_back(BufferFence{
0339                 .sync = sync,
0340                 .nextEnd = intptr_t(bufferSize)});
0341         }
0342     }
0343 
0344     if (nextOffset + intptr_t(size) > bufferEnd) {
0345         if (!awaitFence(nextOffset + size)) {
0346             return nullptr;
0347         }
0348     }
0349 
0350     return map + nextOffset;
0351 }
0352 
0353 void GLVertexBufferPrivate::reallocateBuffer(size_t size)
0354 {
0355     // Round the size up to 4 Kb for streaming/dynamic buffers.
0356     const size_t minSize = 32768; // Minimum size for streaming buffers
0357     const size_t alloc = usage != GL_STATIC_DRAW ? std::max(size, minSize) : size;
0358 
0359     glBufferData(GL_ARRAY_BUFFER, alloc, nullptr, usage);
0360 
0361     bufferSize = alloc;
0362 }
0363 
0364 GLvoid *GLVertexBufferPrivate::mapNextFreeRange(size_t size)
0365 {
0366     GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
0367 
0368     if ((nextOffset + size) > bufferSize) {
0369         // Reallocate the data store if it's too small.
0370         if (size > bufferSize) {
0371             reallocateBuffer(size);
0372         } else {
0373             access |= GL_MAP_INVALIDATE_BUFFER_BIT;
0374             access ^= GL_MAP_UNSYNCHRONIZED_BIT;
0375         }
0376 
0377         nextOffset = 0;
0378     }
0379 
0380     return glMapBufferRange(GL_ARRAY_BUFFER, nextOffset, size, access);
0381 }
0382 
0383 GLVertexBuffer::GLVertexBuffer(UsageHint hint)
0384     : d(std::make_unique<GLVertexBufferPrivate>(hint))
0385 {
0386 }
0387 
0388 GLVertexBuffer::~GLVertexBuffer() = default;
0389 
0390 void GLVertexBuffer::setData(const void *data, size_t size)
0391 {
0392     GLvoid *ptr = map(size);
0393     if (!ptr) {
0394         return;
0395     }
0396     memcpy(ptr, data, size);
0397     unmap();
0398 }
0399 
0400 GLvoid *GLVertexBuffer::map(size_t size)
0401 {
0402     d->mappedSize = size;
0403     d->frameSize += size;
0404 
0405     if (d->persistent) {
0406         return d->getIdleRange(size);
0407     }
0408 
0409     glBindBuffer(GL_ARRAY_BUFFER, d->buffer);
0410 
0411     bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData();
0412 
0413     if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) {
0414         return (GLvoid *)d->mapNextFreeRange(size);
0415     }
0416 
0417     // If we can't map the buffer we allocate local memory to hold the
0418     // buffer data and return a pointer to it.  The data will be submitted
0419     // to the actual buffer object when the user calls unmap().
0420     if (size_t(d->dataStore.size()) < size) {
0421         d->dataStore.resize(size);
0422     }
0423 
0424     return (GLvoid *)d->dataStore.data();
0425 }
0426 
0427 void GLVertexBuffer::unmap()
0428 {
0429     if (d->persistent) {
0430         d->baseAddress = d->nextOffset;
0431         d->nextOffset += align(d->mappedSize, 8);
0432         d->mappedSize = 0;
0433         return;
0434     }
0435 
0436     bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData();
0437 
0438     if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) {
0439         glUnmapBuffer(GL_ARRAY_BUFFER);
0440 
0441         d->baseAddress = d->nextOffset;
0442         d->nextOffset += align(d->mappedSize, 8);
0443     } else {
0444         // Upload the data from local memory to the buffer object
0445         if (preferBufferSubData) {
0446             if ((d->nextOffset + d->mappedSize) > d->bufferSize) {
0447                 d->reallocateBuffer(d->mappedSize);
0448                 d->nextOffset = 0;
0449             }
0450 
0451             glBufferSubData(GL_ARRAY_BUFFER, d->nextOffset, d->mappedSize, d->dataStore.constData());
0452 
0453             d->baseAddress = d->nextOffset;
0454             d->nextOffset += align(d->mappedSize, 8);
0455         } else {
0456             glBufferData(GL_ARRAY_BUFFER, d->mappedSize, d->dataStore.data(), d->usage);
0457             d->baseAddress = 0;
0458         }
0459 
0460         // Free the local memory buffer if it's unlikely to be used again
0461         if (d->usage == GL_STATIC_DRAW) {
0462             d->dataStore = QByteArray();
0463         }
0464     }
0465 
0466     d->mappedSize = 0;
0467 }
0468 
0469 void GLVertexBuffer::setVertexCount(int count)
0470 {
0471     d->vertexCount = count;
0472 }
0473 
0474 void GLVertexBuffer::setAttribLayout(std::span<const GLVertexAttrib> attribs, size_t stride)
0475 {
0476     d->enabledArrays.reset();
0477     for (const auto &attrib : attribs) {
0478         Q_ASSERT(attrib.attributeIndex < d->attrib.size());
0479         d->attrib[attrib.attributeIndex].size = attrib.componentCount;
0480         d->attrib[attrib.attributeIndex].type = attrib.type;
0481         d->attrib[attrib.attributeIndex].offset = attrib.relativeOffset;
0482         d->enabledArrays[attrib.attributeIndex] = true;
0483     }
0484     d->attribStride = stride;
0485 }
0486 
0487 void GLVertexBuffer::render(GLenum primitiveMode)
0488 {
0489     render(infiniteRegion(), primitiveMode, false);
0490 }
0491 
0492 void GLVertexBuffer::render(const QRegion &region, GLenum primitiveMode, bool hardwareClipping)
0493 {
0494     d->bindArrays();
0495     draw(region, primitiveMode, 0, d->vertexCount, hardwareClipping);
0496     d->unbindArrays();
0497 }
0498 
0499 void GLVertexBuffer::bindArrays()
0500 {
0501     d->bindArrays();
0502 }
0503 
0504 void GLVertexBuffer::unbindArrays()
0505 {
0506     d->unbindArrays();
0507 }
0508 
0509 void GLVertexBuffer::draw(GLenum primitiveMode, int first, int count)
0510 {
0511     draw(infiniteRegion(), primitiveMode, first, count, false);
0512 }
0513 
0514 void GLVertexBuffer::draw(const QRegion &region, GLenum primitiveMode, int first, int count, bool hardwareClipping)
0515 {
0516     if (primitiveMode == GL_QUADS) {
0517         if (!GLVertexBufferPrivate::s_indexBuffer) {
0518             GLVertexBufferPrivate::s_indexBuffer = std::make_unique<IndexBuffer>();
0519         }
0520 
0521         GLVertexBufferPrivate::s_indexBuffer->bind();
0522         GLVertexBufferPrivate::s_indexBuffer->accommodate(count / 4);
0523 
0524         count = count * 6 / 4;
0525 
0526         if (!hardwareClipping) {
0527             glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first);
0528         } else {
0529             // Clip using scissoring
0530             const GLFramebuffer *current = GLFramebuffer::currentFramebuffer();
0531             for (const QRect &r : region) {
0532                 glScissor(r.x(), current->size().height() - (r.y() + r.height()), r.width(), r.height());
0533                 glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first);
0534             }
0535         }
0536         return;
0537     }
0538 
0539     if (!hardwareClipping) {
0540         glDrawArrays(primitiveMode, first, count);
0541     } else {
0542         // Clip using scissoring
0543         const GLFramebuffer *current = GLFramebuffer::currentFramebuffer();
0544         for (const QRect &r : region) {
0545             glScissor(r.x(), current->size().height() - (r.y() + r.height()), r.width(), r.height());
0546             glDrawArrays(primitiveMode, first, count);
0547         }
0548     }
0549 }
0550 
0551 bool GLVertexBuffer::supportsIndexedQuads()
0552 {
0553     return GLVertexBufferPrivate::supportsIndexedQuads;
0554 }
0555 
0556 void GLVertexBuffer::reset()
0557 {
0558     d->vertexCount = 0;
0559 }
0560 
0561 void GLVertexBuffer::endOfFrame()
0562 {
0563     if (!d->persistent) {
0564         return;
0565     }
0566 
0567     // Emit a fence if we have uploaded data
0568     if (d->frameSize > 0) {
0569         d->frameSizes.push(d->frameSize);
0570         d->frameSize = 0;
0571 
0572         // Force the buffer to be reallocated at the beginning of the next frame
0573         // if the average frame size is greater than half the size of the buffer
0574         if (d->frameSizes.average() > d->bufferSize / 2) {
0575             deleteAll(d->fences);
0576             glDeleteBuffers(1, &d->buffer);
0577 
0578             d->buffer = 0;
0579             d->bufferSize = 0;
0580             d->nextOffset = 0;
0581             d->map = nullptr;
0582         } else {
0583             if (auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) {
0584                 d->fences.push_back(BufferFence{
0585                     .sync = sync,
0586                     .nextEnd = intptr_t(d->nextOffset + d->bufferSize)});
0587             }
0588         }
0589     }
0590 }
0591 
0592 void GLVertexBuffer::beginFrame()
0593 {
0594     if (!d->persistent) {
0595         return;
0596     }
0597 
0598     // Remove finished fences from the list and update the bufferEnd offset
0599     while (d->fences.size() > 1 && d->fences.front().signaled()) {
0600         const BufferFence &fence = d->fences.front();
0601         glDeleteSync(fence.sync);
0602 
0603         d->bufferEnd = fence.nextEnd;
0604         d->fences.pop_front();
0605     }
0606 }
0607 
0608 void GLVertexBuffer::initStatic()
0609 {
0610     if (GLPlatform::instance()->isGLES()) {
0611         bool haveBaseVertex = hasGLExtension(QByteArrayLiteral("GL_OES_draw_elements_base_vertex"));
0612         bool haveCopyBuffer = hasGLVersion(3, 0);
0613         bool haveMapBufferRange = hasGLExtension(QByteArrayLiteral("GL_EXT_map_buffer_range"));
0614 
0615         GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange;
0616         GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange;
0617         GLVertexBufferPrivate::haveBufferStorage = hasGLExtension("GL_EXT_buffer_storage");
0618         GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 0);
0619     } else {
0620         bool haveBaseVertex = hasGLVersion(3, 2) || hasGLExtension(QByteArrayLiteral("GL_ARB_draw_elements_base_vertex"));
0621         bool haveCopyBuffer = hasGLVersion(3, 1) || hasGLExtension(QByteArrayLiteral("GL_ARB_copy_buffer"));
0622         bool haveMapBufferRange = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_map_buffer_range"));
0623 
0624         GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange;
0625         GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange;
0626         GLVertexBufferPrivate::haveBufferStorage = hasGLVersion(4, 4) || hasGLExtension("GL_ARB_buffer_storage");
0627         GLVertexBufferPrivate::haveSyncFences = hasGLVersion(3, 2) || hasGLExtension("GL_ARB_sync");
0628     }
0629     GLVertexBufferPrivate::s_indexBuffer.reset();
0630     GLVertexBufferPrivate::streamingBuffer = std::make_unique<GLVertexBuffer>(GLVertexBuffer::Stream);
0631 
0632     if (GLVertexBufferPrivate::haveBufferStorage && GLVertexBufferPrivate::haveSyncFences) {
0633         if (qgetenv("KWIN_PERSISTENT_VBO") != QByteArrayLiteral("0")) {
0634             GLVertexBufferPrivate::streamingBuffer->d->persistent = true;
0635         }
0636     }
0637 }
0638 
0639 void GLVertexBuffer::cleanup()
0640 {
0641     GLVertexBufferPrivate::s_indexBuffer.reset();
0642     GLVertexBufferPrivate::hasMapBufferRange = false;
0643     GLVertexBufferPrivate::supportsIndexedQuads = false;
0644     GLVertexBufferPrivate::streamingBuffer.reset();
0645 }
0646 
0647 GLVertexBuffer *GLVertexBuffer::streamingBuffer()
0648 {
0649     return GLVertexBufferPrivate::streamingBuffer.get();
0650 }
0651 
0652 }