Device reset, disposal, adapters and format queries
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. Checked by reading the device, resource, adapter and manager sources at 009d40f5; no test was executed, and the manager hazards listed on the page have no test at this snapshot.
A GraphicsDevice lives as long as the game, but it is reconfigured, observed and finally torn down through a small set of ordered operations: Reset, the manager's event pipeline, the device-status callback, the resource registry and Dispose. This page gives their exact order and failure boundaries at this snapshot, and the adapter, format and profile queries a game makes before or around them. It complements the ownership map on GraphicsDevice internals and the call-level contract on GraphicsDevice: the shared device contract.
Reset: order and rollback boundary
All four public overloads — Reset(), Reset(parameters), Reset(parameters, adapter&) and the CNAEXT Reset(parameters, adapter*) — end in the pointer form in GraphicsDevice.cpp. The order is public behaviour, because event handlers observe it:
- Refuse a disposed device, then snapshot the current presentation parameters, adapter and virtual size.
- Raise
DeviceResetting. Handlers still see the old adapter and parameters. - Unbind every render target (also when a bound target was destroyed), as Microsoft XNA does before resetting the back buffer; the binding is not restored afterwards.
- Clone the request, normalise its back-buffer and depth formats to what the renderer says it would apply, store it with the adapter (a null adapter keeps the old one) and the new virtual size, and publish that size to
TouchPanel. - Inside a rollback guard: apply the parameters to the window (size, fullscreen) and call the renderer's
SetVirtualResolution. - Outside the guard:
UpdatePresentationFormatEXT(formats and fullscreen flag, first, so the MSAA clamp sees the new formats),ApplyMultiSampleCountwith the applied count written back into the stored parameters (which also invalidates the cached capability profile), andSetSwapIntervalfromPresentationInterval. UpdateViewportFromWindow(), then raiseDeviceReset.
What the rollback covers, and what it does not
If step 5 throws, the device restores the stored parameters, adapter, virtual size and touch dimensions, tries to re-apply the former window state (a secondary failure there is swallowed so the original diagnostic survives) and rethrows the original exception. Only the presentation bookkeeping is rolled back: DeviceResetting has already been raised, and the render-target unbind of step 3 is not undone — when a target was bound it has already reset the viewport and scissor to the back buffer's size. The guard ends there. An exception from the format hook, the MSAA application or the swap interval leaves the new parameters stored, steps 2 and 3 already done (the resetting event raised, render targets unbound) and DeviceReset never raised. A handler that pairs DeviceResetting with DeviceReset — to release and recreate resources, for example — must therefore tolerate a missing second half.
A successful reset proves that the shared route ran, not that a window manager accepted fullscreen or that a present interval took effect. The applied values the device writes back (formats normalised by GetAppliedBackBufferFormatEXT/GetAppliedDepthStencilFormatEXT, the clamped multisample count) are stronger than the original request, but they are only as truthful as each family's reporting; which families override the format hook, and how fullscreen is really applied, is on format and fullscreen preferences, and the Vulkan swap-chain side on Vulkan applied formats.
Reset keeps the renderer
Reset reconfigures the existing renderer in place: resources, their renderer objects and every bound state survive. It is not FNA's recreation of a lost device, and it is not the CNAEXT RecreateRendererForMultiSampleCount, which destroys the renderer outright and is safe only before resources exist (see CNA extensions). A source comment in the reset path describes the shape it mirrors: FNA carries formats and the multisample count into one native back-buffer reset through a single presentation structure, which is why CNA applies the formats before the MSAA count. The FNA source itself is not part of this snapshot, so the comparison rests on that comment. The reset events are pinned by ResetRaisesItsTwoEventsInOrder, ResetAppliesTheNewBackbufferSizeAndLeavesTheDeviceUsable and TwoResetsInOneRunBothApply in PresentationLifecycleTests.cpp, and the unbinding by GraphicsDeviceLifecycleTest.ResetUnbindsActiveRenderTargets.
GraphicsDeviceManager on top of Reset
A game-attached GraphicsDeviceManager registers itself as IGraphicsDeviceManager and IGraphicsDeviceService but does not own the device; its CreateDevice() raises PreparingDeviceSettings, resets the Game-owned device, subscribes to the device's reset events only after that settle-in reset, and raises DeviceCreated. Later ApplyChanges() calls reach GraphicsDevice::Reset and the manager forwards the device's own events once each. That pipeline, the five events and their senders are described on GraphicsDeviceManager: CreateDevice; the rules below are the ones that matter when a game hooks into it (GraphicsDeviceManager.cpp).
- Edits in
PreparingDeviceSettingsapply to that candidate only. The handler receives aGraphicsDeviceInformationbuilt from the manager's preferences; whatever it changes is applied by this pass and never copied back into the preferred fields, so the nextApplyChanges()starts from the preferences again. - The candidate must keep a non-null adapter. The apply step dereferences it to call
Reset(parameters, adapter&); clearing it is undefined behaviour, not an error message. - Do not call
ApplyChanges()from the handler. The “preferences changed” flag is cleared only at the end of the pass, so a nested call rebuilds the information, raises the event again and recurses. - Listener exceptions propagate. Nothing in the pipeline catches them, so a throwing handler interrupts the pass wherever it is — before or after the reset.
- Disposal. The manager's
Disposeunregisters its services, releases the frame's renderer-context lease, unsubscribes from the device and raisesDeviceDisposingeven though it does not delete a Game-owned device —GamesubscribesUnloadContent()to that event, so this is how unloading is triggered. The manager's ownDisposedevent is raised before its disposed flag is set, so a handler that disposes the manager again re-enters the method. - The resize subscription outlives disposal. The constructor subscribes a lambda capturing the manager's
thistoGameWindow::ClientSizeChangedand keeps no removal token; disposal removes only the service registrations. As a member of the game class the manager is destroyed together with the game, before the window is; code that destroys or replaces a manager while the game window keeps running and can still be resized must not rely on that ordering.
Device status and device loss
GraphicsDeviceStatus has three values: Normal, Lost and NotReset. The device installs one deviceEventCallback in every renderer it creates; a family reports Lost (status Lost, event DeviceLost), Resetting (status NotReset, event DeviceResetting) and Reset (status Normal, every content-losable resource is told its content was lost, then DeviceReset). At this snapshot six families call it: DIRECTX9, DIRECTX11, DIRECTX12 and DIRECT2D report loss and reset (from a lost or removed device they detect, and from the debug hooks), WEBGPU reports Lost, Resetting and Reset only from its DebugSimulateContextLoss() and DebugRestoreContext() hooks (the handler it registers for a real driver-reported loss only writes to stderr), while VULKAN reports Lost once and then throws from the failing call, because it does not recreate a lost VkDevice underneath live wrappers. Every other family never calls it, so its status stays Normal. The header comments that say only Direct3D 9 drives the status and that DeviceLost is “never raised on desktop” are older than this wiring. A browser WebGL context loss is handled separately: GraphicsDeviceManager::BeginDraw() returns false while the renderer's CanBeginDrawEXT() says a frame cannot start, which skips Draw for that tick. The family-by-family view is on Device loss/reset.
Resource registration and disposal order
GraphicsDevice keeps a non-owning vector of GraphicsResource*. The base constructor of a resource (GraphicsResource.cpp) appends this and raises the device's ResourceCreated before the derived constructor has created its renderer object, so a ResourceCreated handler must not assume the native handle exists. A resource's own Dispose(bool) returns if it is already disposed, raises the resource's Disposing event only for an explicit disposal (the destructor path, Dispose(false), suppresses it), and — only while the device's lifetime token has not expired — raises the device's ResourceDestroyed and unregisters; derived classes release their renderer objects around that call.
Device disposal runs in the opposite direction, and at this snapshot in this order:
- Return if already disposed; otherwise set the disposed flag first, so a re-entrant
Disposeis a no-op and aDisposinghandler cannot issue work against objects being torn down. - Raise
Disposing, only for an explicit disposal. - Move the registry into a local vector, clear it, and call each resource's public
Dispose(); their unregister calls hit an empty list and do nothing. - Destroy native resources: renderer, surface presenter, the
Mouse/TextInputEXThandles that point at this window, then the window wrapper. - Release the video-subsystem reference, after the window it backs.
A still-bound RenderTarget2D no longer breaks this sequence: its Dispose refuses only while its device is alive and still lists it as bound, and the device is already marked disposed when step 3 reaches it. Outside device teardown the refusal stands — dispose a bound target explicitly and it throws InvalidOperationException; let it be destroyed while bound and the destructor path detaches it and leaves Present() refusing until the game calls SetRenderTargets (details on bound target disposal). The tests are TheDeviceIsAlreadyDisposedWhenDisposingIsRaised, TheHookRaisesDisposingOnlyForAnExplicitDisposal and OwnedResourcesAreStillDisposedThroughTheHook in GraphicsDeviceDisposalHookTests.cpp and GraphicsDeviceLifecycleTest.DisposalIsReentrantAndReleasesABoundRenderTarget. Earlier revisions set the disposed flag last and let a bound target throw halfway through device disposal, leaving the renderer alive; the current order has neither problem.
Copies, moves and address stability
The registry stores addresses, so relocation matters. Moves transfer the registry entry (TransferResourceReference) and, for textures and buffers, the device's bindings that pointed at the old object. Copies differ by type: a Texture2D copy — the form ContentManager hands out from its cache — shares the renderer object and CPU shadow and adds no registry entry; the value-style state objects (BlendState, DepthStencilState, RasterizerState, SamplerState, VertexDeclaration) are different again: copy assignment (and, for VertexDeclaration only, the copy constructor) makes the target an alias that shares one identity, so disposing any alias disposes all of them, while the copy constructors of the four state classes make an independent, unbound, mutable copy that is not an alias. The rules and their tests are on Why registration is not ownership. The practical guidance is unchanged: keep live resources inside the device's lifetime, move rather than copy when a resource changes owner, and do not rely on a copy being tracked or disposed by the device.
Adapters and display modes
GraphicsAdapter (GraphicsAdapter.cpp) exposes the current and supported display modes, Description (the platform's display name), DeviceName (a synthetic Windows-style \\.\DISPLAYn, kept on every platform as XNA and FNA do), MonitorHandle (the platform display id), IsDefaultAdapter, IsWideScreen and the identity fields. VendorId and DeviceId are read on Linux from the first readable /sys/class/drm/card0–card3 entry and are 0 elsewhere; all adapters share them. Revision and SubSystemId are always 0. IsWideScreen uses XNA's rule — aspect ratio strictly greater than 1.6 — not FNA's 4:3 limit, so 16:10 is not widescreen. DisplayMode holds width, height, format, the computed aspect ratio and a title-safe area. The three device exceptions — DeviceLostException, DeviceNotResetException and NoSuitableGraphicsDeviceException — derive from System::Exception; the last is what UseNullDevice/UseReferenceDevice throw when the HEADLESS/SOFTWARE renderer they require is not available (see Renderer selection internals).
Enumeration and AdaptersChanged
getAdaptersProperty() lazily builds one adapter per display that the current platform's display service reports and caches the owning unique_ptr vector. If the platform reports no display (no display server, a headless platform), the cache holds one synthetic adapter named “Default Display” with a single 800 × 480 Color mode. DefaultAdapter is simply the first entry, not a separate GPU-selection service: the renderer, not the adapter, decides which GPU is used. AdaptersChanged() clears and rebuilds the cache, destroying every adapter object, so references held across the call dangle; getDefaultAdapterProperty() must be re-read afterwards (DefaultAdapterRemainsValidAcrossAdaptersChanged). Because a display id is only meaningful inside the video session that issued it, enumeration keeps one video-subsystem reference pinned for as long as the cache describes real displays, and an adapter whose id went stale (a new video session issued new ids) rebinds to a display with the same name when one exists, rather than being rebuilt under a live device.
Profile and format queries are not hardware probes
The adapter queries run before any device exists, so they ask the build's default renderer descriptor for optional hooks (RendererAdapterQueries) instead of a live renderer:
| Query | Answer at this snapshot |
|---|---|
IsProfileSupported(profile) | Only DIRECTX9 answers from native device capabilities; every other build returns true for both profiles. |
QueryRenderTargetFormat(profile, format, depth, samples, …) | The profile's render-target format rule applies on every renderer (a Reach request for a HiDef-only format falls back to Color). Then a renderer hook, where one exists: DIRECTX9, DIRECTX11 and DIRECTX12 ask the device; the GLES and WebGL identities of EasyGL guarantee only Color; otherwise a shared list of eleven render-target formats. The sample count is clamped by DIRECTX9, DIRECTX11, DIRECTX12 and VULKAN and reported as 0 everywhere else; the depth format is passed through. |
QueryBackBufferFormat(…) | Color unless a hook accepts the format (the Direct3D families); depth is passed through unless a hook selects the family's real back-buffer depth format. |
So these methods answer “what would XNA's rules and this family's adapter hook accept”, not “what does this GPU support”. After a device exists, prefer the device's own answers — SupportsSurfaceFormatAsRenderTargetEXT, SupportsCapability and the RendererCapabilityProfile (see Tutorial 101: renderer capabilities). The tests are the QueryRenderTargetFormat…/QueryBackBufferFormat… cases in GraphicsAdapterTests.cpp, including QueryRenderTargetFormatDoesNotPromiseRgba64OnGles.
The profile the device enforces
The default GraphicsDevice constructor, and therefore every Game before its manager applies preferences, uses GraphicsProfile::Reach, and the profile is enforced on every renderer, not only on Direct3D 9. At device level that means: more than one render target is refused (the renderer contract's GetMaxRenderTargetsForProfileEXT defaults to one under Reach and four under HiDef, and SupportsCapability(MultipleRenderTargets) reports false under Reach); 32-bit index buffers and 32-bit user indices throw; the primitive ceiling is 65,535 per draw; separate alpha blending and SourceAlphaSaturation as a destination factor are refused; vertex textures have no slots; GetBackBufferData throws. Resources add their own ceilings (texture size and power-of-two rules, float and HDR formats, volume textures, occlusion queries). The manager's GraphicsProfile preference reaches the device through SetGraphicsProfileEXT just before its reset; that setter only stores the value and re-validates nothing already created. The full table, with the exact exception messages, is Tutorial 152: Choosing a GraphicsProfile; the user guide summary is 3D rendering: graphics profile.
Evidence and limits
Checked by reading GraphicsDevice.cpp (Reset, Dispose, createRenderer), GraphicsResource.cpp, RenderTarget2D.cpp, GraphicsAdapter.cpp, the renderer descriptors' adapter hooks and GraphicsDeviceManager.cpp at 009d40f5; nothing was built or executed. The named tests exist; which renderer a neutral test runs against depends on the configuration, and none of the manager hazards listed above (re-entrant disposal, a nested ApplyChanges, a cleared adapter, a resize after the manager is gone) is covered by a test found at this snapshot. Device-loss behaviour is stated from each family's source, not from a run.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Architecture
- Graphics architecture: device loss/reset
- Maintainer workflow
- Debug shutdown and lifetime
- Tests and validation
- Test architecture