Repository navigation
Keep native renderer E_OUTOFMEMORY from crashing Wisp and restart the HUD renderer - #134
Merged
Merged
Conversation
… HUD renderer Wisp.NativeRenderer.dll reports std::bad_alloc and failed Direct3D 11 / DirectComposition allocations as E_OUTOFMEMORY. DirectCompositionDevice raised every failed HRESULT through Marshal.ThrowExceptionForHR, which maps that code to the runtime's OutOfMemoryException. Both render workers and the crash handler treat OutOfMemoryException as fatal, so a transient allocation failure while the game started ended the process (Wisp Tools, 2026-10-17: 2.6.12, AnalogHudRenderWorker.Run, 0x8007000E, 0.46 s after the game became active). - Raise every failed native HRESULT as NativeRendererException (a COMException carrying the HRESULT and the failing operation), so the workers' existing catch records the stage as an error, records the health breadcrumb and reports the status to their host. - HudNativeHost and DriftGaugeWindow retry renderer creation for E_OUTOFMEMORY up to three times (1 s, 2 s, 4 s) before reporting the renderer unavailable; a presented frame restores the budget. Other HRESULTs keep the established unavailable state. A retired worker's late status is ignored. - The drift gauge worker now records a health breadcrumb for its failure. - Tests: a native stub returning E_OUTOFMEMORY from create and from texture upload produces a recorded, reported failure through both workers; the recovery policy is unit tested; the shared HUD lifecycle test covers the restart after a transient failure.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Wisp Tools recorded a crash on 2026-10-17 (Wisp 2.6.12, native-analogue gauge, GPU render mode):
OutOfMemoryExceptionwith HRESULT 0x8007000E (E_OUTOFMEMORY) on the background thread inAnalogHudRenderWorker.Run, 0.46 s after the game became active. The working set was 389 MB and the managed heap 68 MB, so this was not managed heap exhaustion; the same PC relaunched Wisp three minutes later and has run normally since.The fatal code path:
Wisp.NativeRenderer.dllmapsstd::bad_allocand failed Direct3D 11 / DirectComposition allocations (texture creation, composition surfaces) toE_OUTOFMEMORY, andCHECK_HRpasses that code through.DirectCompositionDeviceraised every failed HRESULT throughMarshal.ThrowExceptionForHR, which maps 0x8007000E to the runtime'sOutOfMemoryException.ExceptionexceptOutOfMemoryExceptionandStackOverflowException, so the native failure escaped the render thread, reachedAppDomain.UnhandledException, and ended the process. Every other native HRESULT (for exampleDXGI_ERROR_DEVICE_HUNG) was recorded and reported through the existing status path.The trigger is transient GPU/compositor allocation pressure while the game claims the GPU at launch.
Change
NativeRendererException(aCOMExceptioncarrying the HRESULT and the failing device operation) replacesMarshal.ThrowExceptionForHRfor every native result inDirectCompositionDevice, including the motion channel.HResultis intact, so health breadcrumbs and_status(false, hr)reporting are unchanged. A nativeE_OUTOFMEMORYcan no longer appear asOutOfMemoryException.AnalogHudRenderWorkerrecords the stage aserrorwith the HRESULT and failure phase and reports the status;DriftGaugeRenderWorkerreports the status and now also records a health breadcrumb.NativeRendererRecovery):HudNativeHostandDriftGaugeWindowpreviously marked themselves failed permanently on any status failure, leaving the HUD blank until Wisp restarted. ForE_OUTOFMEMORYonly, they now release the renderer (and, for the HUD, its composition target) and create it again after 1 s, 2 s and 4 s, up to three attempts; a presented frame restores the budget. Any other HRESULT keeps the established unavailable state. A retired worker's late status callback is ignored (per-worker revision), so it cannot fail or restart its replacement.nativeFailureForTestcallback onDirectCompositionDevice.Create(threaded through both worker constructors like the existingbeforeDrawForTest) stands in for the native library oncreate,motion_channelandupload_texture. It is not consulted on the per-frame draw, present or wait paths.Not changed: the legacy
DirectCompositionAnalogHostpath (dormant wheneverHudNativeHostis attached, which every HUD window does), the crash-report exception-name allowlists, the native library, and the behaviour for non-transient HRESULTs.Verification
DirectCompositionDeviceFailureTests(selected by the PR gate): a native stub returningE_OUTOFMEMORYfrom create and from texture upload produces aNativeRendererExceptionwith the HRESULT, a reported(false, 0x8007000E)status, aRendererFailedhealth breadcrumb (stageRendererCreatefor the HUD worker) and a completed worker, through bothAnalogHudRenderWorkerandDriftGaugeRenderWorkeron a real hidden HWND with the WARP device.NativeRendererRecoveryTests: three growing retries forE_OUTOFMEMORY, none forDXGI_ERROR_DEVICE_HUNG,DXGI_ERROR_DEVICE_REMOVEDorE_FAIL, and budget reset.HudNativeHostTests(nightly WPF runtime suite) now injects a transient0x8007000Einto the live shared HUD and checks that the old target is destroyed, the status does not become unavailable, and a new target and worker present again; the existing permanent-failure check follows unchanged.Residual risks
DXGI_ERROR_DEVICE_REMOVEDon hybrid iGPU/dGPU laptops) is deliberately out of scope; it would need separate evidence.HudNativeHostTestsextension is verified by the local run noted below and the next nightly run.