File indexing completed on 2024-11-10 04:56:55
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 ®ion, 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 ®ion, 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 }