CNA-GAP-071: GraphicsDevice is single-threaded: its resource registry has no lock and only EasyGL and OpenGL4 lease a context, so resource creation from a loading thread is supported only for ContentManager loads on those two

CNA snapshot 009d40f5  ·  Known Issues › Functional gaps  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

GraphicsDevice has no lock: GraphicsResource construction and disposal update its resource registry, and only EasyGL and OpenGL4 return a context lease (taken by frames and ContentManager loads), while XNA 4.0 creates its device D3DCREATE_MULTITHREADED.

Identifier
CNA-GAP-071
Category
Functional gap
Subsystem
Graphics & renderers
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
Microsoft::Xna::Framework::Graphics::GraphicsDevice and GraphicsResource: creating or disposing resources from a thread other than the game thread

Expected behaviour

XNA 4.0 creates its Direct3D 9 device with D3DCREATE_MULTITHREADED (decompiled GraphicsDevice.CreateDevice sets creation flags 38, which is 0x26 and includes 0x4), so a loading thread can create textures and buffers while the game thread runs. IGraphicsRenderer.hpp says renderer families that return no lease 'support concurrent resource creation directly or own a different synchronization boundary'.

Actual behaviour at TARGET

GraphicsDevice.cpp contains no mutex. AddResourceReference and RemoveResourceReference push to and swap-pop a plain std::vector, and GraphicsResource's device constructor and Dispose(bool) call them, outside any renderer call. The context lease is implemented only by EasyGLRenderer and OpenGL4Renderer and is taken by GraphicsDeviceManager::BeginDraw, by ContentReader and by GraphicsDevice's Clear and Present, not by resource constructors; every other renderer returns no lease. Creating or disposing a resource on a second thread while the game thread creates or disposes another therefore races on the registry, and a background ContentManager load on a renderer without a lease has no CNA-level exclusion against frames (the Vulkan, Direct3D, SDL_GPU, WebGPU, SDL_Renderer and Software sources hold no lock for it). CNA's own Vulkan lease test records the consequence: the renderer 'contains no synchronization at all', and 'A game that drives it from two threads races regardless of the lease'. GraphicsDevice.hpp states no threading rule.

Source locations

Evidence

Read at 009d40f5; nothing executed. Read GraphicsDevice.cpp and GraphicsResource.cpp for locking and registry use, searched the modules for every acquirer and implementer of the lease and the non-GL renderers for locks (only unrelated debug-queue and shader-session mutexes), read the lease contract in IGraphicsRenderer.hpp, the header of the Vulkan lease test and XNA's decompiled GraphicsDevice.CreateDevice for its creation flags. The race is established by reading and was not reproduced.

Focused reproduction

No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.

Current tests

The thread-context lease is tested per renderer (EasyGL, OpenGL4, and a null-lease test for Vulkan whose header says the renderer has no synchronization); no test creates or disposes graphics resources from two threads at once.

Regression test

Create and dispose textures and buffers from two threads at once on each renderer under ThreadSanitizer; or document that resource creation is game-thread-only and put a lock around GraphicsDevice::AddResourceReference and RemoveResourceReference.

Blast radius

Games that create or dispose graphics resources on a loading thread, and background ContentManager loads on renderers that return no context lease. Games that touch graphics only from the game thread are unaffected.

Workaround

Create and dispose graphics resources on the game thread, or marshal loads to it; background ContentManager loads are covered only on the renderers that lease a context (the EasyGL family and OPENGL4).

The same subject is explained at several altitudes. These are the neighbouring pages at each one.