{"record":{"id":"bc32224e71797898","repo":"stride3d/stride","slug":"unexpected-error-on-present-device-status-devicestatus","errorCode":null,"errorMessage":"Unexpected error on Present (device status: {deviceStatus})","messagePattern":"Unexpected error on Present \\(device status: (.+?)\\)","errorType":"exception","errorClass":"GraphicsDeviceException","httpStatus":null,"severity":"critical","filePath":"sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs","lineNumber":380,"sourceCode":"            var presentInterval = GraphicsDevice.Tags.Get(ForcedPresentInterval) ?? PresentInterval;\n\n            // From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays\n            //   DXGI_PRESENT_ALLOW_TEARING can only be used with sync interval 0. It is recommended to always pass this\n            //   tearing flag when using sync interval 0 if CheckFeatureSupport reports that tearing is supported and the\n            //   app is in a windowed mode - including border-less fullscreen mode.\n\n            var presentFlags = useFlipModel && tearingSupport && presentInterval == PresentInterval.Immediate && !Description.IsFullScreen\n                ? DXGI.PresentAllowTearing\n                : 0;\n\n            HResult result = swapChain->Present((uint) presentInterval,  presentFlags);\n\n            if (result.IsFailure)\n            {\n                var deviceStatus = GraphicsDevice.GraphicsDeviceStatus;\n\n                var exception = Marshal.GetExceptionForHR(result);\n                throw new GraphicsDeviceException($\"Unexpected error on Present (device status: {deviceStatus})\", exception, deviceStatus);\n            }\n\n#if STRIDE_GRAPHICS_API_DIRECT3D12\n            // Manually swap the Back-Buffers\n\n            // Gets the native Back-Buffer from the Swap-Chain.\n            //   This increments the reference count of the COM object,\n            //   so we need to Release() it when discarding or swapping it.\n            bufferSwapIndex = (uint)((++bufferSwapIndex) % bufferCount);\n            var nextBackBuffer = GetBackBuffer<BackBufferResourceType>(bufferSwapIndex);\n\n            // TODO: Maybe we should have a lighter Texture.SwapImpl method for this?\n            //       InitializeFromImpl() is quite heavy for just swapping the internal resource pointer.\n            //       It recreates the internal description and other things that for presenting should not have changed.\n\n            // Texture.InitializeFromImpl also increments the reference count when storing the COM pointer;\n            // compensate with Release() to return the reference count to its previous value\n            backBuffer.InitializeFromImpl(nextBackBuffer, Description.BackBufferFormat.IsSRgb);","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs#L362-L398","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\npresenter.Present(); // unguarded, crashes app on device removal\n// after\ntry\n{\n    presenter.Present();\n}\ncatch (GraphicsDeviceException ex)\n{\n    if (ex.DeviceStatus == GraphicsDeviceStatus.Removed)\n        RecreateGraphicsDevice();\n    else\n        throw;\n}","handlingStrategy":"try-catch","validationCode":"if (GraphicsDevice.GraphicsDeviceStatus != GraphicsDeviceStatus.Normal)\n{\n    RecreateGraphicsDevice();\n    return; // skip Present this frame\n}","typeGuard":"bool CanPresent(GraphicsDevice d) => d?.GraphicsDeviceStatus == GraphicsDeviceStatus.Normal;","tryCatchPattern":"try { presenter.Present(); }\ncatch (GraphicsDeviceException ex)\n{\n    logger.Warning($\"Present failed: {ex.HResult} status={ex.DeviceStatus}\");\n    if (ex.DeviceStatus != GraphicsDeviceStatus.Normal) RecreateGraphicsDevice();\n}","preventionTips":["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"],"tags":["graphics","direct3d","swapchain","device-lost"],"backgroundTag":"device-lost","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}