Can I pass the data to the GPU through multiple cores with OpenGL?
1 Answer
Because the fundamental foundation of OpenGL makes multi-CPU-core submission impossible.
OpenGL, at its core, is a synchronous API. Every OpenGL command behaves "as if" it were executed and completed immediately. Sure, glFlush
and glFinish
are provided, so that implementations can have some asynchronous execution of commands. But these are essentially fig leaves over the definition of OpenGL as synchronous.
You can attach a texture to an FBO, bind that FBO, render to it, unbind that FBO, bind the texture as a source, and read from it. You can do all of this without any thought as to the fact that you've issued two sets of commands where there can be zero overlap between them. This is because OpenGL implementations are required to issue whatever synchronization mechanisms are needed to make this work. You as the user can be completely oblivious to this.1
Because of that, consider what would happen if you could issue that render-to-texture in one thread and the read-from-texture in another. How exactly would that even work? You would need some way to say "don't execute the read until the write is done". But since the two sets of commands are on different threads, how do you say that?
OpenGL has no mechanism for doing so; the closest it gets would be some form of fence sync. And even that would require that the render-to-texture thread has already finished sending those commands, since glFenceSync
issues the fence actual fence (you cannot create a fence that has not yet been submitted). Which means that the render-to-texture thread may as well send the read-from-texture commands too.
To make OpenGL work asynchronously means making lots of adjustments to its memory model. You'll have to add lots of commands for making things visible to other threads. For allowing thread A to not have its commands execute until thread B is finished. You'd need to make command buffers and queues first-class constructs of the API. And so forth.
And you have to do it in a way that's backwards compatible with the existing memory model. Unless you want to go through another core/compatibility split again.
NV_command_list shows the challenges of trying to build this sort of thing into OpenGL. They force you to use ARB_bindless_texture. They had to invent bindless buffers, so that you can provide vertex arrays, UBOs, and SSBOs.
And even then, it's not something that AMD could even implement. Why? Because of image layouts. NVIDIA's hardware has fewer layouts and handles transitions automatically. AMD's hardware requires explicit control of layouts. Once you break the synchronous memory model, that implementation detail of what layout a texture is in becomes really important. You have to have control over it. You have to explicitly state it in various API, and you have to know to change it when you use a texture in a different way.
This is also almost certainly why AMD doesn't implement D3D11 deferred contexts.
Or you can do what Khronos did: make an API that naturally handles all of this stuff. A clean break is ultimately best for everyone. It allows us to jettison other garbage that OpenGL had, so that we can have a nice, clean, lower-level API.
1: While you can be oblivious, obviously you shouldn't be oblivious if you care about things like performance. You should put some work between the render-to-texture and the read from that texture, if at all possible. That's one of the biggest problems with OpenGL's synchronous-by-default API: it makes it easy to do things the slow way, since the API itself doesn't force you to issue that synchronization. In Vulkan, you cannot be ignorant of that synchronization. Or at least, not without your code invoking UB.