Solved Updating data in a QOpenGLBuffer
-
What's the proper way to update data in a QOpenGLBuffer? Do you destroy() it and create() the QOpenGLBuffer again, or can you just use the allocate(data,count) function again? If it is safe (e.g. no memory leaks) to use the latter approach, what would be the difference/advantage compared to destroying and creating it?
Based on some related questions I'd say it is Ok to just reallocate with a new call to allocate(data,count). Another argument that supports this is the use of setUsagePattern(QOpenGLBuffer::DynamicDraw) — according to the documentation that's the right setting to use for data that will (or might) be modified repeatedly.
To conclude, a bit of context — in my case I regularly switch from displaying n vertices to 2n, 4n, 8n, and also back to n/2, n/4, n/8 vertices (though not necessarily in that order).
-
It is safe to call allocate again but it's not very efficient. It's a little (not much) better then destroying and creating the object, as you're just realocating the memory for the data in the GPU memory and not the managing object in RAM, but the difference would be marginal I suspect. In any of these cases the flags you give it don't matter that much, as you're recreating the buffer anyway.
If you don't mind keeping some extra memory allocated the much better approach would be to allocate the buffer once with enough room to hold the biggest data and then update the contents using map/unmap. That's really how these buffers are intended to be used and that's what the usage pattern flags are for.
-
Thanks!
@Chris-Kawa said:
If you don't mind keeping some extra memory allocated the much better approach would be to allocate the buffer once with enough room to hold the biggest data and then update the contents using map/unmap. That's really how these buffers are intended to be used and that's what the usage pattern flags are for.
Ah, I see. Do you know where I could find a simple example using map() or mapRange()?
-
Sorry, I don't know of any example using Qt, but if you don't find any you might look for bare OpenGL exmples like this one. Qt's API wraps pretty close to the underlying OpenGL calls. It's just a little more convenient to use.
-
Ok, but how do I actually update the data? As I understand,
map()
ormapRange()
just returns a pointer.I've found two bits of code (here and here) using the
map()
function. They both usememcpy()
to update the data, but I can't seem to get it to work.Starting from a
QOpenGLBuffer m_vertex
, the relevant parts of my code arem_vertex.create(); m_vertex.setUsagePattern(QOpenGLBuffer::DynamicDraw); m_vertex.bind();
followed by
m_vertex.allocate(sizeof(Verts[0]) * Verts.size()); vertexPointer = (GLubyte*)m_vertex.map(QOpenGLBuffer::ReadWrite); m_vertex.release();
with
QVector<QVector2D> Verts
. Subsequently usingmemcpy(vertexPointer, Verts.constData(), sizeof(Verts[0]) * Verts.size());
does not seem to have any effect, with or without using
m_vertex.unmap()
. Simply usingm_vertex.allocate(Pts.data(), Pts.size() * sizeof(Pts[0]));
instead of the earlier
allocate()
andmap()
functions works fine though.In addition, there seems to be a fair bit of discussion about whether to use
glMapBuffer(Range)()
orglBufferSubData()
, see e.g. here. Consider me slightly confused :). -
Assuming you have code similar to this:
m_vertex.bind(); m_vertex.allocate(Pts.data(), Pts.size() * sizeof(Pts[0])); //do stuff and draw m_vertex.release();
you would change it to something like this:
//where you create the buffer: m_vertex.allocate(enoughSizeToHoldTheData); //where you update it: m_vertex.bind(); auto ptr = m_vertex.map(QOpenGLBuffer::WriteOnly); memcpy(ptr, Pts.data(), Pts.size() * sizeof(Pts[0])); m_vertex.unmap(); //do stuff and draw m_vertex.release();
or better yet, instead of map() use mapRange():
auto ptr = m_vertex.mapRange(0, Pts.size() * sizeof(Pts[0]), QOpenGLBuffer::RangeInvalidateBuffer | QOpenGLBuffer::RangeWrite);
Couple of pointers:
- pay close attention to the flags. If you are only gonna update the buffer use QOpenGLBuffer::WriteOnly or QOpenGLBuffer::RangeWrite. Also, if you're replacing the whole buffer anyway, use QOpenGLBuffer::RangeInvalidateBuffer. This allows the driver to skip an expensive readback from the GPU memory to the RAM.
- if you're gonna update the buffer constantly you might want to map the buffer once in the initialization and unmap it in shutdown. Combining this with some fencing and QOpenGLBuffer::RangeUnsynchronized / QOpenGLBuffer::RangeFlushExplicit flags, can help you squeeze some more performance if you need it.
- remember that map/mapRange does not replace allocate. It merely maps the allocated memory to the cpu address space. You still need to allocate the buffer first. It just saves you the cost of reallocating the memory every time.
Consider me slightly confused :)
All I can say is welcome to OpenGL world. Home sweet hell ;) The "rules of thumb" change every couple of months with new drivers bringing new optimizations and bugs (yes, OpenGL drivers are infested with them) and wildly vary from vendor to vendor and from platform to platform. A trick that speeds up your OpenGL code 2x on one platform can cause a 5x performance hit on another. There are also usually at least 3 to 5 ways you can do any given thing and they have very different performance characteristics depending on how and where you observe it. These are the problems that made vendors collectively decide to move to lower level APIs like Metal, Mantle or upcoming Vulcan (aka OpenGL Next).
The only reliable rules in OpenGL world I found are: trust no one, measure everything, consider everything you learned obsolete by the time you think you have a handle on it :) -
Thanks, much appreciated!
@Chris-Kawa said:
- if you're gonna update the buffer constantly you might want to map the buffer once in the initialization and unmap it in shutdown. Combining this with some fencing and QOpenGLBuffer::RangeUnsynchronized / QOpenGLBuffer::RangeFlushExplicit flags, can help you squeeze some more performance if you need it.
I had something like this in mind, but it seems that unless
unmap()
is called, the data copied usingmemcpy()
won't be displayed. In other words, every update of the data should be preceded bymap(Range)()
and followed byunmap()
. Correct? -
every update of the data should be preceded by map(Range)() and followed by unmap(). Correct?
memcpy only writes to the mapped memory. It's not immediately synced to the video memory. By default unmapping flushes the changes to video memory and by that makes them visible to the GPU.
You can map/unmap once, but you are then responsible for manual syncing the changes to the GPU. This is done via glFlushMappedBufferRange or glMemoryBarrier. It's more verbose but gives you very precise control over what happens when, which is nice when fighting for performance.
-
Hi
To update data in the buffer I usually use write http://doc.qt.io/qt-5/qopenglbuffer.html#write -
@johngod said: To update data in the buffer I usually use write
Worth mentioning is that write() uses glBufferSubData(), so is the easier, but, as mentioned earlier, possibly slower option than mapping/fencing.