Skip to main content
GameDev.net gamedev.net
🔒 Locked

OpenGL Check if VBO Upload is Complete

Started by Lolums Mar 26, 2016 at 9:59 PM 9 replies 5.4k views
Original Post
Lolums
Lolums

I've learnt that the upload of VBOs is asynchronous.

How can I check that my VBO is uploaded?

I'd like to do this in order not to try to draw it before the upload is complete, as this halts the program (and a lag spike is felt).

Edit: I've read up on fence syncing but supposing that I have my fence, how do I check for whether it's been used by the GPU without using glWaitSync? I'd like to check and get a boolean response, as opposed to waiting.

Promit
Promit

Because the OpenGL spec doesn't recognize the transfer as asynchronous, per se, there's no way to check. The only option is to force it to wait by issuing a dummy draw call. There are a variety of other things that can also be deferred and lead to hitching in-game, and so it's standard practice to issue dummy draw calls for these things. Getting rid of all this is one of the big improvements in the new APIs (DX12/Vulkan).

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Lolums
Lolums

Because the OpenGL spec doesn't recognize the transfer as asynchronous, per se, there's no way to check. The only option is to force it to wait by issuing a dummy draw call. There are a variety of other things that can also be deferred and lead to hitching in-game, and so it's standard practice to issue dummy draw calls for these things. Getting rid of all this is one of the big improvements in the new APIs (DX12/Vulkan).

Ah, I see! In that case, I may just set a time-out and only try to render after half a second or something; this won't be noticeable. Problem with DX12 is that one can't support Linux, which for me is a crime if one can avoid it. Vulkan looks promising. Thanks for letting me know though.

Promit
Promit

Because the OpenGL spec doesn't recognize the transfer as asynchronous, per se, there's no way to check. The only option is to force it to wait by issuing a dummy draw call. There are a variety of other things that can also be deferred and lead to hitching in-game, and so it's standard practice to issue dummy draw calls for these things. Getting rid of all this is one of the big improvements in the new APIs (DX12/Vulkan).

Ah, I see! In that case, I may just set a time-out and only try to render after half a second or something; this won't be noticeable. Problem with DX12 is that one can't support Linux, which for me is a crime if one can avoid it. Vulkan looks promising. Thanks for letting me know though.

It's not necessarily on a timer. Sometimes the driver simply can't be bothered to finish the upload until you actually issue a draw call.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Hodgman
Hodgman
All GL commands are asynchronous, but act like they're immediate. Drawing commands are async too, so if you update a VBO and then draw, both those commands go into the same queue and are processed by the GPU one after the other.

If you want it to he truely async where the GPU is doing two things at once, you need to create another GL context and share the VBO between both contexts. This gives you a second queue that's independent of your main queue, and then you can use fences to track the progress of this secondary queue.
This is tricky though -- multiple contexts can ruin performance, because GPUs only ever have one hardware queue. However, if you only ever use this second queue for buffer updates, some drivers will detect that usage pattern and create a DMA command queue instead of a graphics command queue, which can simultaneously run alongside the main context.

This is getting into very advanced and vendor-specific territory though, so if you're looking for a solution, some more details on your exact situation and problem would help --- in the common situation of small buffer updates, you just update the buffer, then draw immediately after, and let the driver do it's thing.
cgrant
cgrant

Going with what Hodgman said. If you are NOT using multiple context from multiple threads then your concerns are unfounded.



All GL commands are asynchronous, but act like they're immediate. Drawing commands are async too, so if you update a VBO and then draw, both those commands go into the same queue and are processed by the GPU one after the other.


Meaning that if you update the VBO then call draw, the result will appear to be sequential/serialized..they WILL happen in the order submitted.
Promit
Promit

Meaning that if you update the VBO then call draw, the result will appear to be sequential/serialized..they WILL happen in the order submitted.

We're not talking about logical ordering or things appearing serialized. There's a ton of operations in GL that are deferred internally, which in turn will cause runtime hitches when the driver decides to actually commit operations. Buffer uploads, texture uploads, and especially shader (re)compiles are the prime suspects here. This is not an imaginary problem for anyone who has actually shipped a game on a GL pipeline.

Issuing a dummy draw call - and potentially a fence or glFlush/glFinish/SwapBuffers - is the only way to combat this problem of runtime hitches.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Matias Goldberg
Matias Goldberg

Hold on guys. There is a way to check if the copy has been performed the way OP asked.

apitest shows how to issue a fence and wait for it. The first time it checks if the fence has been signaled. The second time it tries again but flushing the queue since the driver may not have processed the copy yet (thus the GPU hasn't even started the copy, or whatever you're waiting for. If we don't flush, we'll be waiting forever. aka deadlock)

Of course if you want to just check if the copy has finished, and if not finished then do something else: you just need to do the 'wait' like the first time (i.e. without flushing), but using waiting period of zero (so that you don't wait, and get a boolean-like response like OP wants). We do this in Ogre to check for async transfer's status.

As with all APIs that offer fences (D3D12, Vulkan, OpenGL), the more fences you add, the worse it becomes for performance (due to the added hardware and driver overhead of communicating results, synchronizing, and keeping atomicity). Use them wisely. Don't add fences "just in case" you think you'll want to query for the transfer status. If you have multiple copies to perform, batch them together and then issue the fence.

I'd like to do this in order not to try to draw it before the upload is complete, as this halts the program (and a lag spike is felt).

Calling glDraw* family of functions won't stall because it's also asynchronous. I can't think of a scenario where the API will stall because an upload isn't complete yet. You usually need to check if a download (i.e. GPU->CPU) is completed before you map the buffer to avoid stalling (unless you use unsynchronized or persistent mapping flags; in such case it won't stall but you still need to check if the copy is complete to avoid a hazard)

Lolums
Lolums

Thank you! I've literally learnt so much from your responses. This made me think about where the problem actually lies, since I was not mapping any buffers or performing any sort of expensive GPU downloads. After running my profiler in "Telemetry" mode for the first time, I've discovered (within two minutes) that actually, the garbage collector usage correlates perfectly with lag spikes and I happen to be wasting a lot of memory in a certain place.

Promit
Promit

apitest shows how to issue a fence and wait for it. The first time it checks if the fence has been signaled. The second time it tries again but flushing the queue since the driver may not have processed the copy yet (thus the GPU hasn't even started the copy, or whatever you're waiting for. If we don't flush, we'll be waiting forever. aka deadlock)

Of course if you want to just check if the copy has finished, and if not finished then do something else: you just need to do the 'wait' like the first time (i.e. without flushing), but using waiting period of zero (so that you don't wait, and get a boolean-like response like OP wants). We do this in Ogre to check for async transfer's status.

So can you use a fence to test for the actual status of a BufferSubData call uploading to server? And that works consistently across platforms without issuing a draw call against that buffer? After all the driver must do a full copy of the buffer to somewhere in-core at the point of call, but what the rube goldberg machine does after that is anyone's guess.


Calling glDraw* family of functions won't stall because it's also asynchronous. I can't think of a scenario where the API will stall because an upload isn't complete yet.

It'll stall if it gets caught in a situation where it can't continue dispatching frames without finishing a pending operation. I don't remember seeing this happen with buffer uploads, but I've seen it many, many times with shader recompiles. Whether this is because shader recompiles are a long goddamn process, or they have to happen in the main thread, or it just runs up against the limit of buffered frames, or some combination thereof, I'm not sure. It seems conceivable that a large upload followed by lots of dispatched frames could conceivably trigger the same effect.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Matias Goldberg
Matias Goldberg

So can you use a fence to test for the actual status of a BufferSubData call uploading to server? And that works (...) without issuing a draw call against that buffer?

Yes, but no:
1. In one hand, you can reliable test that the copy has ended by calling glClientWaitSync with GL_SYNC_FLUSH_COMMANDS_BIT set. No need to issue a draw call. An implementation that doesn't behave this way could cause hazards or deadlocks and would be therefore considered broken. However...

2. On the other hand, flushing is something you should avoid unless you're prepared to stall. So we normally query these kind of things without the flush bit set. Some platforms may have already began and eventually finish the copy by the time we query for 2nd or 3rd time (since the 1st time we decided to do something else. Like audio processing). While other platforms/drivers may wait forever to even start the upload because it's waiting for you to issue that glDraw call and decide uploading the buffer will be worth it. Thus the query will always return 'not done yet' until something relevant happens.

So the answer is yes, you can make it work without having to call draw. But no, you should avoid this and hope drivers don't try to get too smart (or profile overly smart drivers).

...and that works consistently across platforms...

If you're using fences and unsynchronized access you're targeting pretty much modern desktop drivers (likely GL 4.x; but works on GL 3.3 drivers too), whether Linux or Windows. It works fine there (unless you're using 3-year-old drivers which had a couple fence bugs)
Few android devices support GL_ARB_Sync. It's not available on iOS afaik either. It's available on OSX but OSX lives in a different world of instability.

Does it work reliably across platforms? Yes (except on OSX where I don't know). Is it available widespread in many platforms? No.

If you're using fences and thus targeting modern GL, this brings me my next point: Just don't use BufferSubData. BufferSubData can stall if the driver ran out of its internal memory to perform the copy.

Instead, map an unsynchronized/persistent mapped region to use as a stash between CPU<->GPU (i.e. what D3D11 knows as Staging Buffers); and then perform a glCopyBufferSubdata to copy from GPU Stash to final GPU data. Just as fast, less stall surprises (you **know** when you've run out of stash space; and fences tell you when older stash regions can be reused again), and gives you tighter control. You can even perform the copy from CPU -> GPU stash in a worker thread, and perform the glCopyBufferSubdata call in the main thread to do the GPU Stash->GPU copy.
This is essentially what you would do in D3D11 and D3D12 (except the GPU->GPU copy doesn't have to be routed to the main thread).

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.