stride3d/stride · critical · GraphicsDeviceException
Unexpected error on Present
Error message
Unexpected error on Present (device status: {deviceStatus}) What it means
SwapChainGraphicsPresenter.Present throws GraphicsDeviceException when the DXGI Present call returns a failure HRESULT that is not a recognized device-removed/reset case. The message includes GraphicsDevice.GraphicsDeviceStatus at the moment of failure, and the original COM exception is attached as InnerException. It signals an unexpected presentation failure distinct from the handled device-lost flows.
Solutions
- Read the InnerException's HRESULT and deviceStatus in the message to identify the actual failure (e.g. DXGI_ERROR_DEVICE_REMOVED reason)
- Handle GraphicsDeviceException in the frame loop, check GraphicsDeviceStatus, and recreate the GraphicsDevice/swap chain on device removal
- Update GPU drivers and DirectX runtime; test without overclocking
- Check that the window handle is still valid and Present is not called after window/device teardown begins
Example fix
// before
presenter.Present(); // unguarded, crashes app on device removal
// after
try
{
presenter.Present();
}
catch (GraphicsDeviceException ex)
{
if (ex.DeviceStatus == GraphicsDeviceStatus.Removed)
RecreateGraphicsDevice();
else
throw;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (GraphicsDevice.GraphicsDeviceStatus != GraphicsDeviceStatus.Normal)
{
RecreateGraphicsDevice();
return; // skip Present this frame
} Type guard
bool CanPresent(GraphicsDevice d) => d?.GraphicsDeviceStatus == GraphicsDeviceStatus.Normal;
Try / catch
try { presenter.Present(); }
catch (GraphicsDeviceException ex)
{
logger.Warning($"Present failed: {ex.HResult} status={ex.DeviceStatus}");
if (ex.DeviceStatus != GraphicsDeviceStatus.Normal) RecreateGraphicsDevice();
} Prevention
- Check GraphicsDeviceStatus before presenting each frame
- Log and inspect InnerException HRESULT (DXGI_ERROR_*) to diagnose root cause
- Keep GPU drivers updated; avoid unstable overclocks
- Handle window destruction before the frame loop presents
When it happens
Trigger: Calling Present (each frame, or indirectly via Game/RenderContext loop) when IDXGISwapChain.Present returns an unexpected failing HRESULT — e.g. device removed in an unhandled form, DXGI_STATUS/ERROR codes, or driver errors not matching the expected device-status branches.
Common situations: GPU driver crashes/TDR mid-frame; presenting after the device was removed but before Stride's device-lost handling kicked in; multi-GPU/remote-desktop sessions where the swap chain is invalidated; overclocked/unstable GPUs; window being destroyed while presenting.
Related errors
- DeviceWindowHandle cannot be null
- SurfaceRotation ' ' is not supported on Direct3D presenters.
- Window context [ ] not supported while creating SwapChain
- The . must not be zero.
- Format ' ' is not supported when using a flip model…
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/bc32224e71797898.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs:380
var presentInterval = GraphicsDevice.Tags.Get(ForcedPresentInterval) ?? PresentInterval;
// From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays
// DXGI_PRESENT_ALLOW_TEARING can only be used with sync interval 0. It is recommended to always pass this
// tearing flag when using sync interval 0 if CheckFeatureSupport reports that tearing is supported and the
// app is in a windowed mode - including border-less fullscreen mode.
var presentFlags = useFlipModel && tearingSupport && presentInterval == PresentInterval.Immediate && !Description.IsFullScreen
? DXGI.PresentAllowTearing
: 0;
HResult result = swapChain->Present((uint) presentInterval, presentFlags);
if (result.IsFailure)
{
var deviceStatus = GraphicsDevice.GraphicsDeviceStatus;
var exception = Marshal.GetExceptionForHR(result);
throw new GraphicsDeviceException($"Unexpected error on Present (device status: {deviceStatus})", exception, deviceStatus);
}
#if STRIDE_GRAPHICS_API_DIRECT3D12
// Manually swap the Back-Buffers
// Gets the native Back-Buffer from the Swap-Chain.
// This increments the reference count of the COM object,
// so we need to Release() it when discarding or swapping it.
bufferSwapIndex = (uint)((++bufferSwapIndex) % bufferCount);
var nextBackBuffer = GetBackBuffer<BackBufferResourceType>(bufferSwapIndex);
// TODO: Maybe we should have a lighter Texture.SwapImpl method for this?
// InitializeFromImpl() is quite heavy for just swapping the internal resource pointer.
// It recreates the internal description and other things that for presenting should not have changed.
// Texture.InitializeFromImpl also increments the reference count when storing the COM pointer;
// compensate with Release() to return the reference count to its previous value
backBuffer.InitializeFromImpl(nextBackBuffer, Description.BackBufferFormat.IsSRgb);View on GitHub (pinned to 96fad776d2)