Articles
OpenGL Interview Questions: Context Sharing, Synchronization, and Buffer Mapping
Practice OpenGL interview questions with context sharing, GPU fences, buffer mapping, visibility rules, and safe ring-buffer region reuse.
A background thread uploads vertices, the render thread receives the buffer name, and the next frame occasionally uses old data. Adding a mutex protects the handoff, but the glitch remains. What did the mutex order: CPU access to a handle, GPU execution, or visibility of the updated object in another context?
These OpenGL interview questions focus on that distinction. A strong explanation identifies the current context, the command stream that produced the data, the operation that consumes it, and the evidence permitting reuse. The same ownership reasoning applies when a persistently mapped buffer returns a convenient pointer that is still unsafe to overwrite.
Evidence boundary: Official facts use desktop OpenGL 4.6 core-profile specifications and Khronos reference pages, checked October 6, 2026. Sync objects are core from 3.2; immutable storage and persistent mapping require 4.4 or relevant extension support. These are not automatically WebGL or OpenGL ES contracts. All handoffs and slot states are original interview exercises. No candidate reports, GPU runs or measured performance results are used.
For a related practice discussion, Explain a graphics testing project in depth helps turn an intermittent rendering symptom into a controlled experiment.
Can two threads use the same context simultaneously?
Official fact: The GLFW context guide states that a context can be current on one thread at a time, and a thread can have one current context at a time. Moving a context requires making it non-current on the old thread before making it current on the new thread.
A mutex around an application queue does not make the upload context current on the worker. Likewise, creating two threads does not create two contexts. Ask how contexts are created, where they are made current, and which version and profile each actually supports.
Original setup: Thread U owns an upload context; thread R owns a render context. The contexts are created in the same share group. Each stays current on its own thread. A host queue transfers resource metadata, and the worker stops modifying the handed-off version until the application permits another update.
The exercise requires two valid current contexts in one share group and a synchronized host handoff.
If U and R instead attempt to use the same context concurrently, the proposed shared-context protocol is answering the wrong problem. Establish valid context ownership before investigating buffer fences.
For a single-thread variant, switching between two contexts does not eliminate the need to reason about separate context state. It removes a host-thread race, not every object-propagation requirement.
Which objects are shared, and which state remains local?
Official fact: OpenGL 4.6 chapter 5 permits sharing buffers, textures, shader/program objects and sync objects. Container objects, including vertex array objects and framebuffer objects, are not shared.
In our exercise, the vertex buffer name can identify the shared buffer in R. A VAO created in U cannot simply serve as R's VAO. R needs its own vertex-input setup referencing the shared data. An integer handle does not encode which contexts can use the object or reproduce their local bindings.
Configure the render-side VAO explicitly instead of passing the upload-side VAO name.
Inference for diagnosis: If an upload succeeds but drawing is wrong, inspect R's vertex layout, offsets and bound objects separately from U's data update. A fence cannot repair the wrong stride or an incorrectly configured render-side VAO. Our local ledger explicitly distinguishes a shared buffer from a render-local container.
Sharing therefore answers an identity question: can both contexts access this object? It does not by itself answer when the latest contents may be used.
What does a producer fence actually cover?
Official fact: glFenceSync inserts a fence into the current command stream. With GL_SYNC_GPU_COMMANDS_COMPLETE, signaling follows completion of the fence and preceding commands in that stream. It does not certify every future operation or every other context's stream.
Here is an original two-context handoff for a buffer updated through ordinary GL commands. Assume the upload succeeds, host publication is synchronized, and no later producer update races with consumption:
| Step | Thread/context | Required action | What it establishes |
|---|---|---|---|
| 1 | U / upload | Issue the buffer update | The intended update precedes the next fence |
| 2 | U / upload | Insert producer fence F | F represents that earlier upload stream work |
| 3 | U / upload | Call glFlush | Submit producer commands without proving completion |
| 4 | Host queue | Publish buffer, version and F safely | R receives consistent CPU metadata |
| 5 | R / render | Queue glWaitSync on F | Later dependent GL work waits in R's stream |
| 6 | R / render | Bind or rebind shared object, then draw | Establish the consumption path after synchronization |
Official fact: glFlush does not wait for all previously issued commands to finish. Submission and completion are different milestones. In this protocol, U explicitly flushes its own stream before R depends on F.
Official fact: Chapter 5's propagation rules require attachment or reattachment to guarantee updated shared contents are visible in the consuming context after the required completion ordering. An existing binding alone is not sufficient evidence that another context's latest update is visible. The table deliberately includes the render-side bind/rebind step.
The queue and the fence solve different problems. The queue orders host metadata access; F orders the relevant GL work. Removing either leaves a different gap in the original exercise.
How do glWaitSync and glClientWaitSync differ?
Official fact: glWaitSync establishes a server-side wait. Its flags must be zero and timeout GL_TIMEOUT_IGNORED. It is not a CPU polling function returning a completed-versus-pending result.
By contrast, glClientWaitSync waits on the client and returns a status. GL_ALREADY_SIGNALED and GL_CONDITION_SATISFIED indicate the relevant completion condition; GL_TIMEOUT_EXPIRED does not. GL_WAIT_FAILED indicates an error and requires diagnosis.
Reclaim a slot only when its last-use completion evidence permits a CPU write.
Consider an original counterexample: F was inserted before the vertex update rather than after it. Even if F signals, the evidence covers the wrong portion of U's stream. Waiting longer on that fence does not change its position.
Another counterexample is using a wait in R as though it submitted unflushed commands from U. Our protocol avoids that dependency by explicitly flushing U. Do not assume a consumer-side call automatically flushes every context that shares the sync object.
Finally, a producer fence proving upload readiness does not prove that R has finished reading the buffer. If U wants to overwrite the same storage later, the application needs completion evidence covering the consumer's last use. Name both directions of the handoff.
Does persistent coherent mapping make overwrites safe?
Official fact: glBufferStorage establishes immutable storage permissions. GL_MAP_PERSISTENT_BIT requires read or write access, and GL_MAP_COHERENT_BIT requires persistence. The glMapBufferRange access flags must be compatible with the allocated store.
A persistent mapping lets the application retain a mapped pointer while GL uses the buffer. “Immutable” describes the storage allocation contract; it does not mean its permitted contents can never change.
For a coherent CPU-write mapping, appropriately configured coherence supplies the visibility relationship for subsequent GL commands. It does not prove earlier GPU reads have finished. Visibility is not ownership.
A coherent pointer can make an overwrite visible without making that overwrite safe.
Original hazard: R submits a draw reading slot 0. Before its last-use fence signals, the CPU writes next-frame vertices into slot 0 through a coherent pointer. The new bytes may become visible while the older draw still needs that region. Coherence did not make the overwrite safe.
For a noncoherent CPU-write design using GL_MAP_FLUSH_EXPLICIT_BIT, glFlushMappedBufferRange makes the selected written range visible as specified. The explicit-flush bit belongs to mapping access, not the storage flag list. Flush offsets are relative to the mapped range.
For example, map a buffer region starting at byte 1,024, then modify sixteen bytes at mapped-relative offset 32. The relevant flush uses offset 32, not 1,056. The corresponding buffer range begins at 1,056. This original arithmetic exercise catches a common coordinate-space mistake without claiming a driver result.
Do not use CPU-write rules to answer a GPU-write-to-CPU-read question unchanged. That direction also needs completion and the visibility rules appropriate to its mapping. State access direction before selecting synchronization.
How would you keep a three-slot upload ring safe?
Use this original ownership ledger. Each slot tracks a byte range, data version and fence after its last GPU use. Initially, a never-submitted slot is available by application invariant.
| Slot | Last-use evidence | CPU action |
|---|---|---|
| 0 | Client wait returns TIMEOUT_EXPIRED | Keep the bytes unchanged; choose another slot or wait |
| 1 | Client wait returns ALREADY_SIGNALED | Reclaim only after the matching version's last use is covered |
| 2 | Never submitted | Write within its assigned range |
Track slot 0 as busy, slot 1 as reclaimable and slot 2 as never submitted before choosing the next write.
After writing an available slot, perform the visibility operation required by that mapping, submit the draw referencing that range, and insert a new last-use fence after the final consuming command. Mark the slot unavailable until completion evidence permits reclamation.
If another context also consumes the slot, one render-context fence is insufficient unless the protocol orders or otherwise accounts for those consumers. Our three-slot exercise assumes one consuming stream. A production design must document all readers.
A timeout is a scheduling result, not permission to reuse memory. Depending on the application's latency policy, choose another available slot, wait within a deadline, or skip an update. Increasing ring size can reduce pressure; it does not prove the GPU can never fall behind.
Likewise, GL_MAP_UNSYNCHRONIZED_BIT tells mapping not to perform the usual synchronization attempt. According to the mapping reference, overlapping pending accesses can have undefined results. It transfers responsibility to the application; it is not a correctness repair or a guaranteed faster path.
What should your debugging evidence contain?
Collect the actual version/profile, context ownership, share-group relationship, object names and types, relevant byte ranges, update versions and command order. Then record the observed fence status rather than “we waited.” A successful client wait and a timed-out poll support different conclusions.
A trace should show which update, fence, publication and consuming command belong to the same data version.
For our intermittent upload case, independently check producer submission, fence placement, host publication, render-side object attachment, correct VAO configuration and last-use ownership. Repairing one condition does not establish all the others.
Twelve local checks accompany this article. They validate an original ordering ledger, reject missing flush/publication/wait/rebind steps, distinguish completion statuses, preserve busy slots and verify mapped-relative arithmetic. No contexts were created and no GL commands were executed. These controls make the reasoning reviewable; a real implementation still needs error checks and a version-pinned GPU reproduction.
Keep teardown ownership explicit too: which context performs cleanup, which host readers still hold handles, and whether outstanding consumers have been accounted for. Treat deleting a handle as a lifecycle operation, not completion evidence authorizing a mapped overwrite.
Sources and Further Reading
- OpenGL 4.6 core specification, especially chapters 4–6
- GLFW context ownership and object sharing
- glFenceSync: the covered command stream
- glFlush: submission without completion
- glWaitSync: server-side ordering
- glClientWaitSync: completion and timeout statuses
- glBufferStorage: immutable storage permissions
- glMapBufferRange: mapping permissions and hazards
- glFlushMappedBufferRange: mapped-relative visibility