{"record":{"id":"c90a10c6708e7b75","repo":"dotnet/wpf","slug":"sr-d3dimage-musthavebackbuffer","errorCode":null,"errorMessage":"SR.D3DImage_MustHaveBackBuffer","messagePattern":"SR\\.D3DImage_MustHaveBackBuffer","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/D3DImage.cs","lineNumber":298,"sourceCode":"        ///     the D3DImage. After you unlock, we will copy the dirty areas to the front buffer.\n        ///\n        ///     Can only be called while locked.\n        ///\n        ///     IMPORTANT: After five dirty rects, we will union them all together. This\n        ///                means you must have valid data outside of the dirty regions.\n        /// </Summary>\n        public void AddDirtyRect(Int32Rect dirtyRect)\n        {\n            WritePreamble();\n\n            if (_lockCount == 0)\n            {\n                throw new InvalidOperationException(SR.Image_MustBeLocked);\n            }\n\n            if (_pInteropDeviceBitmap == null)\n            {\n                throw new InvalidOperationException(SR.D3DImage_MustHaveBackBuffer);\n            }\n\n            dirtyRect.ValidateForDirtyRect(nameof(dirtyRect), PixelWidth, PixelHeight);\n            if (dirtyRect.HasArea)\n            {\n                // Unmanaged code will make sure that the rect is well-formed\n                HRESULT.Check(UnsafeNativeMethods.InteropDeviceBitmap.AddDirtyRect(\n                    dirtyRect.X, \n                    dirtyRect.Y, \n                    dirtyRect.Width, \n                    dirtyRect.Height, \n                    _pInteropDeviceBitmap\n                    ));\n                    \n                // We're now dirty, but we won't consider it a change until Unlock\n                _isDirty = true;\n                _isChangePending = true;\n            }","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/D3DImage.cs#L280-L316","documentation":"After verifying the lock, D3DImage.AddDirtyRect requires that a back buffer has been assigned via SetBackBuffer; if _pInteropDeviceBitmap is null it throws InvalidOperationException(D3DImage_MustHaveBackBuffer). You cannot mark dirty regions on an image that has no back-buffer surface yet.","triggerScenarios":"Calling AddDirtyRect before the first successful SetBackBuffer call; calling it after SetBackBuffer(null) released the back buffer; calling it when TryLock succeeded but the front buffer is unavailable and software fallback left _pUserSurfaceUnsafe/_pInteropDeviceBitmap null; calling it during initialization before the D3D surface was created.","commonSituations":"Startup ordering bugs where the render loop runs before the back buffer is set; teardown races where the surface was released but the render callback still fires; cases where SetBackBuffer was passed null to detach but the dirty-rect path still executes.","solutions":["Call SetBackBuffer(D3DResourceType.IDirect3DSurface9, surfacePtr) with a valid surface before any AddDirtyRect call.","Gate the render/dirty-rect code on 'back buffer assigned' state in your renderer; skip the frame otherwise.","Do not call AddDirtyRect after SetBackBuffer(null); re-set the buffer before resuming rendering."],"exampleFix":"// before\nif (rendered)\n    d3dImage.AddDirtyRect(rect); // throws if no back buffer set yet\n\n// after\nif (_backBufferSet && rendered)\n{\n    d3dImage.AddDirtyRect(rect);\n}\n// where _backBufferSet = true only after a successful SetBackBuffer with non-null pointer","handlingStrategy":"validation","validationCode":"if (backBufferPtr == IntPtr.Zero) return; // no back buffer; skip dirty rect\nd3dImage.AddDirtyRect(rect);","typeGuard":"bool HasBackBuffer(IntPtr ptr) => ptr != IntPtr.Zero;","tryCatchPattern":"try { d3dImage.AddDirtyRect(rect); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"back buffer\"))\n{\n    // buffer not yet set — set it under lock, then retry once\n}","preventionTips":["Set the back buffer (non-null) before starting the render loop.","Track back-buffer state and skip AddDirtyRect when it is unset or was set to null.","During teardown, stop the render loop before calling SetBackBuffer(null)."],"tags":["wpf","d3dimage","interop","invalid-operation","missing-back-buffer"],"backgroundTag":"invalid-state-transition","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}