KWin
Loading...
Searching...
No Matches
glvertexbuffer.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
6 SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
7 SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11#include "glvertexbuffer.h"
12#include "glframebuffer.h"
13#include "glplatform.h"
14#include "glshader.h"
15#include "glshadermanager.h"
16#include "glutils.h"
17#include "utils/common.h"
18
19#include <QVector4D>
20#include <bitset>
21#include <deque>
22
23namespace KWin
24{
25
26// Certain GPUs, especially mobile, require the data copied to the GPU to be aligned to a
27// certain amount of bytes. For example, the Mali GPU requires data to be aligned to 8 bytes.
28// This function helps ensure that the data is aligned.
29template<typename T>
30T align(T value, int bytes)
31{
32 return (value + bytes - 1) & ~T(bytes - 1);
33}
34
36{
37public:
40
41 void accommodate(size_t count);
42 void bind();
43
44private:
45 GLuint m_buffer;
46 size_t m_count = 0;
47 std::vector<uint16_t> m_data;
48};
49
51{
52 // The maximum number of quads we can render with 16 bit indices is 16,384.
53 // But we start with 512 and grow the buffer as needed.
54 glGenBuffers(1, &m_buffer);
55 accommodate(512);
56}
57
59{
60 glDeleteBuffers(1, &m_buffer);
61}
62
63void IndexBuffer::accommodate(size_t count)
64{
65 // Check if we need to grow the buffer.
66 if (count <= m_count) {
67 return;
68 }
69 Q_ASSERT(m_count * 2 < std::numeric_limits<uint16_t>::max() / 4);
70 const size_t oldCount = m_count;
71 m_count *= 2;
72 m_data.reserve(m_count * 6);
73 for (size_t i = oldCount; i < m_count; i++) {
74 const uint16_t offset = i * 4;
75 m_data[i * 6 + 0] = offset + 1;
76 m_data[i * 6 + 1] = offset + 0;
77 m_data[i * 6 + 2] = offset + 3;
78 m_data[i * 6 + 3] = offset + 3;
79 m_data[i * 6 + 4] = offset + 2;
80 m_data[i * 6 + 5] = offset + 1;
81 }
82 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer);
83 glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_count * sizeof(uint16_t), m_data.data(), GL_STATIC_DRAW);
84}
85
87{
88 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer);
89}
90
91// ------------------------------------------------------------------
92
94{
95 int size;
96 GLenum type;
97 int offset;
98};
99
100// ------------------------------------------------------------------
101
103{
104 GLsync sync;
105 intptr_t nextEnd;
106
107 bool signaled() const
108 {
109 GLint value;
110 glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value);
111 return value == GL_SIGNALED;
112 }
113};
114
115static void deleteAll(std::deque<BufferFence> &fences)
116{
117 for (const BufferFence &fence : fences) {
118 glDeleteSync(fence.sync);
119 }
120
121 fences.clear();
122}
123
124// ------------------------------------------------------------------
125
126template<size_t Count>
128{
129public:
131 {
132 m_array.fill(0);
133 }
134
135 void push(size_t size)
136 {
137 m_array[m_index] = size;
138 m_index = (m_index + 1) % Count;
139 }
140
141 size_t average() const
142 {
143 size_t sum = 0;
144 for (size_t size : m_array) {
145 sum += size;
146 }
147 return sum / Count;
148 }
149
150private:
151 std::array<size_t, Count> m_array;
152 int m_index = 0;
153};
154
155//*********************************
156// GLVertexBufferPrivate
157//*********************************
159{
160public:
162 : vertexCount(0)
163 , persistent(false)
164 , bufferSize(0)
165 , bufferEnd(0)
166 , mappedSize(0)
167 , frameSize(0)
168 , nextOffset(0)
169 , baseAddress(0)
170 , map(nullptr)
171 {
172 glGenBuffers(1, &buffer);
173
174 switch (usageHint) {
176 usage = GL_DYNAMIC_DRAW;
177 break;
179 usage = GL_STATIC_DRAW;
180 break;
181 default:
182 usage = GL_STREAM_DRAW;
183 break;
184 }
185 }
186
188 {
189 deleteAll(fences);
190
191 if (buffer != 0) {
192 glDeleteBuffers(1, &buffer);
193 map = nullptr;
194 }
195 }
196
197 void bindArrays();
198 void unbindArrays();
199 void reallocateBuffer(size_t size);
200 GLvoid *mapNextFreeRange(size_t size);
201 void reallocatePersistentBuffer(size_t size);
202 bool awaitFence(intptr_t offset);
203 GLvoid *getIdleRange(size_t size);
204
205 GLuint buffer;
206 GLenum usage;
208 static std::unique_ptr<GLVertexBuffer> streamingBuffer;
209 static bool haveBufferStorage;
210 static bool haveSyncFences;
211 static bool hasMapBufferRange;
213 QByteArray dataStore;
216 intptr_t bufferEnd;
218 size_t frameSize;
219 intptr_t nextOffset;
220 intptr_t baseAddress;
221 uint8_t *map;
222 std::deque<BufferFence> fences;
224 std::array<VertexAttrib, VertexAttributeCount> attrib;
225 size_t attribStride = 0;
226 std::bitset<32> enabledArrays;
227 static std::unique_ptr<IndexBuffer> s_indexBuffer;
228};
229
232std::unique_ptr<GLVertexBuffer> GLVertexBufferPrivate::streamingBuffer;
235std::unique_ptr<IndexBuffer> GLVertexBufferPrivate::s_indexBuffer;
236
238{
239 glBindBuffer(GL_ARRAY_BUFFER, buffer);
240
241 for (size_t i = 0; i < enabledArrays.size(); i++) {
242 if (enabledArrays[i]) {
243 glVertexAttribPointer(i, attrib[i].size, attrib[i].type, GL_FALSE, attribStride,
244 (const GLvoid *)(baseAddress + attrib[i].offset));
245 glEnableVertexAttribArray(i);
246 }
247 }
248}
249
251{
252 for (size_t i = 0; i < enabledArrays.size(); i++) {
253 if (enabledArrays[i]) {
254 glDisableVertexAttribArray(i);
255 }
256 }
257}
258
260{
261 if (buffer != 0) {
262 // This also unmaps and unbinds the buffer
263 glDeleteBuffers(1, &buffer);
264 buffer = 0;
265
266 deleteAll(fences);
267 }
268
269 if (buffer == 0) {
270 glGenBuffers(1, &buffer);
271 }
272
273 // Round the size up to 64 kb
274 size_t minSize = std::max<size_t>(frameSizes.average() * 3, 128 * 1024);
275 bufferSize = std::max(size, minSize);
276
277 const GLbitfield storage = GL_DYNAMIC_STORAGE_BIT;
278 const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
279
280 glBindBuffer(GL_ARRAY_BUFFER, buffer);
281 glBufferStorage(GL_ARRAY_BUFFER, bufferSize, nullptr, storage | access);
282
283 map = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, access);
284
285 nextOffset = 0;
287}
288
290{
291 // Skip fences until we reach the end offset
292 while (!fences.empty() && fences.front().nextEnd < end) {
293 glDeleteSync(fences.front().sync);
294 fences.pop_front();
295 }
296
297 Q_ASSERT(!fences.empty());
298
299 // Wait on the next fence
300 const BufferFence &fence = fences.front();
301
302 if (!fence.signaled()) {
303 qCDebug(KWIN_OPENGL) << "Stalling on VBO fence";
304 const GLenum ret = glClientWaitSync(fence.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000);
305
306 if (ret == GL_TIMEOUT_EXPIRED || ret == GL_WAIT_FAILED) {
307 qCCritical(KWIN_OPENGL) << "Wait failed";
308 return false;
309 }
310 }
311
312 glDeleteSync(fence.sync);
313
314 // Update the end pointer
315 bufferEnd = fence.nextEnd;
316 fences.pop_front();
317
318 return true;
319}
320
322{
323 if (size > bufferSize) {
325 }
326
327 // Handle wrap-around
328 if ((nextOffset + size > bufferSize)) {
329 nextOffset = 0;
331
332 for (BufferFence &fence : fences) {
333 fence.nextEnd -= bufferSize;
334 }
335
336 // Emit a fence now
337 if (auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) {
338 fences.push_back(BufferFence{
339 .sync = sync,
340 .nextEnd = intptr_t(bufferSize)});
341 }
342 }
343
344 if (nextOffset + intptr_t(size) > bufferEnd) {
345 if (!awaitFence(nextOffset + size)) {
346 return nullptr;
347 }
348 }
349
350 return map + nextOffset;
351}
352
354{
355 // Round the size up to 4 Kb for streaming/dynamic buffers.
356 const size_t minSize = 32768; // Minimum size for streaming buffers
357 const size_t alloc = usage != GL_STATIC_DRAW ? std::max(size, minSize) : size;
358
359 glBufferData(GL_ARRAY_BUFFER, alloc, nullptr, usage);
360
361 bufferSize = alloc;
362}
363
365{
366 GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
367
368 if ((nextOffset + size) > bufferSize) {
369 // Reallocate the data store if it's too small.
370 if (size > bufferSize) {
371 reallocateBuffer(size);
372 } else {
373 access |= GL_MAP_INVALIDATE_BUFFER_BIT;
374 access ^= GL_MAP_UNSYNCHRONIZED_BIT;
375 }
376
377 nextOffset = 0;
378 }
379
380 return glMapBufferRange(GL_ARRAY_BUFFER, nextOffset, size, access);
381}
382
384 : d(std::make_unique<GLVertexBufferPrivate>(hint))
385{
386}
387
389
390void GLVertexBuffer::setData(const void *data, size_t size)
391{
392 GLvoid *ptr = map(size);
393 if (!ptr) {
394 return;
395 }
396 memcpy(ptr, data, size);
397 unmap();
398}
399
400GLvoid *GLVertexBuffer::map(size_t size)
401{
402 d->mappedSize = size;
403 d->frameSize += size;
404
405 if (d->persistent) {
406 return d->getIdleRange(size);
407 }
408
409 glBindBuffer(GL_ARRAY_BUFFER, d->buffer);
410
411 bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData();
412
413 if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) {
414 return (GLvoid *)d->mapNextFreeRange(size);
415 }
416
417 // If we can't map the buffer we allocate local memory to hold the
418 // buffer data and return a pointer to it. The data will be submitted
419 // to the actual buffer object when the user calls unmap().
420 if (size_t(d->dataStore.size()) < size) {
421 d->dataStore.resize(size);
422 }
423
424 return (GLvoid *)d->dataStore.data();
425}
426
428{
429 if (d->persistent) {
430 d->baseAddress = d->nextOffset;
431 d->nextOffset += align(d->mappedSize, 8);
432 d->mappedSize = 0;
433 return;
434 }
435
436 bool preferBufferSubData = GLPlatform::instance()->preferBufferSubData();
437
438 if (GLVertexBufferPrivate::hasMapBufferRange && !preferBufferSubData) {
439 glUnmapBuffer(GL_ARRAY_BUFFER);
440
441 d->baseAddress = d->nextOffset;
442 d->nextOffset += align(d->mappedSize, 8);
443 } else {
444 // Upload the data from local memory to the buffer object
445 if (preferBufferSubData) {
446 if ((d->nextOffset + d->mappedSize) > d->bufferSize) {
447 d->reallocateBuffer(d->mappedSize);
448 d->nextOffset = 0;
449 }
450
451 glBufferSubData(GL_ARRAY_BUFFER, d->nextOffset, d->mappedSize, d->dataStore.constData());
452
453 d->baseAddress = d->nextOffset;
454 d->nextOffset += align(d->mappedSize, 8);
455 } else {
456 glBufferData(GL_ARRAY_BUFFER, d->mappedSize, d->dataStore.data(), d->usage);
457 d->baseAddress = 0;
458 }
459
460 // Free the local memory buffer if it's unlikely to be used again
461 if (d->usage == GL_STATIC_DRAW) {
462 d->dataStore = QByteArray();
463 }
464 }
465
466 d->mappedSize = 0;
467}
468
470{
471 d->vertexCount = count;
472}
473
474void GLVertexBuffer::setAttribLayout(std::span<const GLVertexAttrib> attribs, size_t stride)
475{
476 d->enabledArrays.reset();
477 for (const auto &attrib : attribs) {
478 Q_ASSERT(attrib.attributeIndex < d->attrib.size());
479 d->attrib[attrib.attributeIndex].size = attrib.componentCount;
480 d->attrib[attrib.attributeIndex].type = attrib.type;
481 d->attrib[attrib.attributeIndex].offset = attrib.relativeOffset;
482 d->enabledArrays[attrib.attributeIndex] = true;
483 }
484 d->attribStride = stride;
485}
486
487void GLVertexBuffer::render(GLenum primitiveMode)
488{
489 render(infiniteRegion(), primitiveMode, false);
490}
491
492void GLVertexBuffer::render(const QRegion &region, GLenum primitiveMode, bool hardwareClipping)
493{
494 d->bindArrays();
495 draw(region, primitiveMode, 0, d->vertexCount, hardwareClipping);
496 d->unbindArrays();
497}
498
500{
501 d->bindArrays();
502}
503
505{
506 d->unbindArrays();
507}
508
509void GLVertexBuffer::draw(GLenum primitiveMode, int first, int count)
510{
511 draw(infiniteRegion(), primitiveMode, first, count, false);
512}
513
514void GLVertexBuffer::draw(const QRegion &region, GLenum primitiveMode, int first, int count, bool hardwareClipping)
515{
516 if (primitiveMode == GL_QUADS) {
518 GLVertexBufferPrivate::s_indexBuffer = std::make_unique<IndexBuffer>();
519 }
520
522 GLVertexBufferPrivate::s_indexBuffer->accommodate(count / 4);
523
524 count = count * 6 / 4;
525
526 if (!hardwareClipping) {
527 glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first);
528 } else {
529 // Clip using scissoring
531 for (const QRect &r : region) {
532 glScissor(r.x(), current->size().height() - (r.y() + r.height()), r.width(), r.height());
533 glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first);
534 }
535 }
536 return;
537 }
538
539 if (!hardwareClipping) {
540 glDrawArrays(primitiveMode, first, count);
541 } else {
542 // Clip using scissoring
544 for (const QRect &r : region) {
545 glScissor(r.x(), current->size().height() - (r.y() + r.height()), r.width(), r.height());
546 glDrawArrays(primitiveMode, first, count);
547 }
548 }
549}
550
555
557{
558 d->vertexCount = 0;
559}
560
562{
563 if (!d->persistent) {
564 return;
565 }
566
567 // Emit a fence if we have uploaded data
568 if (d->frameSize > 0) {
569 d->frameSizes.push(d->frameSize);
570 d->frameSize = 0;
571
572 // Force the buffer to be reallocated at the beginning of the next frame
573 // if the average frame size is greater than half the size of the buffer
574 if (d->frameSizes.average() > d->bufferSize / 2) {
575 deleteAll(d->fences);
576 glDeleteBuffers(1, &d->buffer);
577
578 d->buffer = 0;
579 d->bufferSize = 0;
580 d->nextOffset = 0;
581 d->map = nullptr;
582 } else {
583 if (auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) {
584 d->fences.push_back(BufferFence{
585 .sync = sync,
586 .nextEnd = intptr_t(d->nextOffset + d->bufferSize)});
587 }
588 }
589 }
590}
591
593{
594 if (!d->persistent) {
595 return;
596 }
597
598 // Remove finished fences from the list and update the bufferEnd offset
599 while (d->fences.size() > 1 && d->fences.front().signaled()) {
600 const BufferFence &fence = d->fences.front();
601 glDeleteSync(fence.sync);
602
603 d->bufferEnd = fence.nextEnd;
604 d->fences.pop_front();
605 }
606}
607
609{
610 if (GLPlatform::instance()->isGLES()) {
611 bool haveBaseVertex = hasGLExtension(QByteArrayLiteral("GL_OES_draw_elements_base_vertex"));
612 bool haveCopyBuffer = hasGLVersion(3, 0);
613 bool haveMapBufferRange = hasGLExtension(QByteArrayLiteral("GL_EXT_map_buffer_range"));
614
615 GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange;
616 GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange;
619 } else {
620 bool haveBaseVertex = hasGLVersion(3, 2) || hasGLExtension(QByteArrayLiteral("GL_ARB_draw_elements_base_vertex"));
621 bool haveCopyBuffer = hasGLVersion(3, 1) || hasGLExtension(QByteArrayLiteral("GL_ARB_copy_buffer"));
622 bool haveMapBufferRange = hasGLVersion(3, 0) || hasGLExtension(QByteArrayLiteral("GL_ARB_map_buffer_range"));
623
624 GLVertexBufferPrivate::hasMapBufferRange = haveMapBufferRange;
625 GLVertexBufferPrivate::supportsIndexedQuads = haveBaseVertex && haveCopyBuffer && haveMapBufferRange;
626 GLVertexBufferPrivate::haveBufferStorage = hasGLVersion(4, 4) || hasGLExtension("GL_ARB_buffer_storage");
628 }
630 GLVertexBufferPrivate::streamingBuffer = std::make_unique<GLVertexBuffer>(GLVertexBuffer::Stream);
631
633 if (qgetenv("KWIN_PERSISTENT_VBO") != QByteArrayLiteral("0")) {
634 GLVertexBufferPrivate::streamingBuffer->d->persistent = true;
635 }
636 }
637}
638
646
651
652}
OpenGL framebuffer object.
QSize size() const
static GLFramebuffer * currentFramebuffer()
static GLPlatform * instance()
Definition glplatform.h:394
bool preferBufferSubData() const
Vertex Buffer Object.
void draw(GLenum primitiveMode, int first, int count)
static bool supportsIndexedQuads()
void setAttribLayout(std::span< const GLVertexAttrib > attribs, size_t stride)
void setData(const void *data, size_t sizeInBytes)
static GLVertexBuffer * streamingBuffer()
GLVertexBuffer(UsageHint hint)
void render(GLenum primitiveMode)
std::optional< std::span< T > > map(size_t count)
@ Static
No changes to data.
@ Stream
Data only used once for rendering, updated very frequently.
@ Dynamic
frequent changes, but used several times for rendering
void setVertexCount(int count)
static std::unique_ptr< IndexBuffer > s_indexBuffer
GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint)
FrameSizesArray< 4 > frameSizes
std::deque< BufferFence > fences
static std::unique_ptr< GLVertexBuffer > streamingBuffer
std::array< VertexAttrib, VertexAttributeCount > attrib
bool awaitFence(intptr_t offset)
void reallocatePersistentBuffer(size_t size)
GLvoid * mapNextFreeRange(size_t size)
GLvoid * getIdleRange(size_t size)
void reallocateBuffer(size_t size)
void accommodate(size_t count)
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
Session::Type type
Definition session.cpp:17
T align(T value, int bytes)
bool hasGLVersion(int major, int minor, int release)
Definition glutils.cpp:133
bool hasGLExtension(const QByteArray &extension)
Definition glutils.cpp:138
void push(size_t size)