You can guarantee safety without fences, if you constrain your access patterns to use a “write once” model - write batches using an ascending cursor/offset, and orphan the buffer once you hit the end (glBufferData(…, NULL)) - at which point you wind the cursor back to zero and proceed again. This lets you pack many disjoint variable sized batches of data into one healthy sized VBO without any un needed blocking/waiting.
If you have repeated partial update needs then you have to use something like an explicit fence + unsynchronized, or just go back to BufferSubData. The value of MapBufferRange combined with fences is higher if you have many more things crammed into the buffer object.
I agree, but I’d also throw in the sabot of SMP systems. As I see it, it would require lock, effectively “starve” every CPU, switch, and finally flush. AFAICT that would be both a performance and logistic disaster (waiting to happen).
So instead of this, can we try to find alternatives? Could a model where the actual mapping is abstracted from the app work? I’m just brainstorming a bit now…
Imagine a “multimapped” (just invented word) buffer, where the user instead of using it as a ring buffer (as suggested previously) that would have quite serious implications wrt page- and mapping-granularity get two (or more, as requested by the client) memory areas to play with - but using a single mapping (section). Each of them would only be (“legally”) usable by the server after an “unmap” call (that wouldn’t be a heavy unmap at all, but simply a synchronization flush and possibly an additional revocation of writable on the memory). Then the client could continue write data in another of the “segments” it got, while the server could process the previous data confident it’s not modified under its feet.
Again, I was just brainstorming. I don’t know how much it could save in time (if it’s at all feasible), but at least it could, if possible, allow a single mapping (kmode<->umode) to be “reused” and effectively persistemt (from the client’s POV).
But then, if the system changed screen setup and the physical VRAM got reallocated/invalidated… Damn, but I still think it was a possibly neat idea.
I’ve thought about this issue some more this weekend, and think I might have found a better alternative when taking into consideration threading. And it turns out that it would NOT require my previous suggestion. With the ascending cursor scheme you have say a VBO which is allocated at a size which can hold at least 3 frames worth of dynamic geometry. This buffer must be unmapped to use for drawing, which I see as a problem for threading.
However what if one was to instead break this VBO into at least 3 (and perhaps more if necessary) separate VBOs and use the following scheme instead. Initialize all VBOs, and use MapBufferRange() to map the entire range of all VBOs using MAP_WRITE_BIT | MAP_INVALIDATE_BUFFER_BIT | MAP_FLUSH_EXPLICIT_BIT | MAP_UNSYNCHRONIZED_BIT. Then add these mapped VBOs to a FIFO queue. Draw frames using the following steps in the primary GL context thread,
When the first worker thread finishes working on the current frame, remove a new mapped VBO from the FIFO queue, and begin finished worker threads on the next frame. No delay is necessary because the VBOs in the FIFO are already mapped, worker threads can begin to insert new dynamic geometry into the next VBO regardless of what GL or the GPU is doing.
Block, or do something else and poll for completion, for all worker threads to complete for the current frame.
Use FlushMappedBufferRange() on the range(s) of the current VBO which got written by the worker threads. Then unmap the VBO.
Do all drawing for the frame.
Now remap the VBO using MapBufferRange() and the same access bits used in initialization. Add this mapped VBO back into the FIFO so that after another say 2 (or more if necessary) frames, it is used again.
Michael, seems to me that this model would provide the best performance for an application which wanted to generate dynamic geometry using multiple threads (at the expense of giving the driver less flexibility to move memory).
When you use the invalidate whole buffer bit, this is meant to detach/orphan the current storage so you do not need to do any of your own juggling of old buffers. You could be running 12 buffer-fulls ahead of the driver, the driver gets to keep track of which ones have actually been fully consumed and can make that storage available again for the next invalidated buffer. If the driver and hardware are infinitely fast, then you won’t get that far ahead. If they are much slower, then at some point the driver will enforce command queue depth (or storage footprint) limiting and calls will start to take more time as perceived by the issuing thread.
I should remove the invalidate bit and rely on only the unsynchronized bit instead?
Is my example a case of the programmer trying to outsmart the driver? One primary goal here is to try and force the driver to do nearly all buffer overhead at creation time instead of at map time. Also to try and force the driver to allocate VBOs together at one time to limit GPU memory fragmentation.
Also I should have noted in my previous post that one could modify the construct above to work in under a single frame delay by breaking up into many more than 3 subframes/VBO buffers.
Well that’s an interesting question. If you can actually calculate the upper bounds output in terms of bytes per frame of data needing to be written, then you might be able to put something together where you never have to block.
However in a more general sense this is difficult to do (i.e. if you have no idea how much data will come down per frame) - unless you have fences. So usually you find out after a frame or two what the real demand is, so if the driver is managing orphaned buffers efficiently it should converge on a working set size that eliminates need for new allocations, and the application can remain oblivious to how many real buffers the driver had to juggle.
wglShareLists
The wglShareLists function enables multiple OpenGL rendering contexts to share a single display-list space.
Display lists are be gone in OpenGL 3.0+
Any plans to rename this function or to introduce a new one?
Imagine a newbie learning opengl 3.1. “Share what? Display lists? whats that?”
I dont know who “owns” glx/wglShareLists. But Yes, it is deprecated by extension WGL_ARB_create_context.
I still think this method is more “OpenGL” then “Microsoft” and probably implemented by IHV.
Probably this is the first time we have a deprecated WGL “core” function.
WGL is important, developers should know what not to use.
There is no OGL without WGL/GLX/AGL/… .
The notion of changing a shareList after context creation is troubling. What happens to any objects which have been created between context creation and joining a shareList? No, setting the shareList for a context should happen as part of creation, not separately. GLX had it right, MS got it wrong and we’re fixing it.
What does that mean? We use one rendering context for “normal” interactive viewing and for example for creating a “large scale” snapshot of the current scene we create an additional pbuffer and call shareLists() with the “normal” rendering context in order to reuse the already uploaded textures, VBOs, … No problems with this yet. Will this scenario not be possible in the future then?
Like, either you want to share resource between contexts or you don’t. Seems like maybe wglCreateContextAttribs simplifies things a bit, in that this question is answered right up front - no changing your mind later on and throwing everything out of whack. I think we’re all for anything that makes the driver writer’s life a bit easier…
This should work fine Hampel. When you create the pbuffer context, add it to the share group during creation instead of separately later. Here’s the problem we’re trying to avoid:
Create and bind context A
create some objects (A)
Create and bind context B
create some objects (B)
Join context B to context A’s shareList
There are two possibilities here. You can either fail the call to wglShareLists() since context B already has some objects, or you can implicitly delete all of B’s objects since context B now shares a namespace with context A. You cannot merge the namespaces (e.g. both A and B may have a texture named “3”).
In practice its a solved problem and not really a burden - but its messy and annoying, and WGL should have just followed the GLX model from day one.