unoplatform/uno · error · InvalidOperationException

Offscreen framebuffer is not complete

Error message

Offscreen framebuffer is not complete

What it means

GLCanvasElement's FrameBufferDetails allocates an offscreen GL framebuffer (color texture + depth/stencil renderbuffer) for offscreen rendering, then asserts gl.CheckFramebufferStatus == FramebufferComplete. If not, it throws InvalidOperationException. GL defines several incompleteness causes (missing attachment, mismatched dimensions, unsupported internal format, unsupported sample counts, no GL context current).

Source

Thrown at src/AddIns/Uno.WinUI.Graphics3DGL/GLCanvasElement.FrameBufferDetails.cs:51

					gl.TexParameter(GLEnum.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.Linear);
					gl.TexParameter(GLEnum.Texture2D, GLEnum.TextureMagFilter, (int)GLEnum.Linear);
					gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0,
						GLEnum.Texture2D, _textureColorBuffer, 0);
				}
				gl.BindTexture(GLEnum.Texture2D, 0);

				_renderBuffer = gl.GenRenderbuffer();
				gl.BindRenderbuffer(GLEnum.Renderbuffer, _renderBuffer);
				{
					gl.RenderbufferStorage(GLEnum.Renderbuffer, InternalFormat.Depth24Stencil8, (uint)renderSize.Width, (uint)renderSize.Height);
					gl.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment,
						GLEnum.Renderbuffer, _renderBuffer);
				}
				gl.BindRenderbuffer(GLEnum.Renderbuffer, 0);

				if (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
				{
					throw new InvalidOperationException("Offscreen framebuffer is not complete");
				}
			}
			gl.BindFramebuffer(GLEnum.Framebuffer, 0);
		}

		public void Dispose()
		{
			_gl.DeleteFramebuffer(Framebuffer);
			_gl.DeleteTexture(_textureColorBuffer);
			_gl.DeleteRenderbuffer(_renderBuffer);
		}
	}
}

View on GitHub (pinned to 0418340488)

Solutions

  1. Guard against zero/negative RenderSize before creating the framebuffer; skip rendering until a valid size is set.
  2. Query gl.CheckFramebufferStatus's specific reason (gl.GetFramebufferAttachmentParameter / driver logs) to distinguish format vs. attachment failure.
  3. On drivers without Depth24Stencil8, fall back to a stencil-only or depth-only renderbuffer, or skip depth/stencil if your scene doesn't need it.
  4. Ensure a GL context is current on the calling thread when the framebuffer is built.

Example fix

// before
if (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
{
	throw new InvalidOperationException("Offscreen framebuffer is not complete");
}

// after
if (RenderSize.Width <= 0 || RenderSize.Height <= 0) { return; }
var status = gl.CheckFramebufferStatus(GLEnum.Framebuffer);
if (status != GLEnum.FramebufferComplete)
{
	this.Log().Error($"Framebuffer incomplete: {status}");
	return; // skip this frame rather than crash
Defensive patterns

Strategy: validation

Validate before calling

if (RenderSize.Width <= 0 || RenderSize.Height <= 0) return; // skip degenerate size

Type guard

static bool ValidRenderSize(Size s) => s.Width > 0 && s.Height > 0;

Try / catch

try { BuildFramebuffer(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("framebuffer")) { this.Log().Error(ex); }

Prevention

When it happens

Trigger: RenderSize has zero width or height; the depth/stencil InternalFormat (Depth24Stencil8) is unsupported by the GL driver; the framebuffer was bound without a current GL context; renderbuffer storage allocation silently failed on the driver.

Common situations: Element resized to 0x0 before first render; running on a GL driver lacking EXT_packed_depth_stencil / GL_DEPTH24_STENCIL8; context loss between framebuffer creation and the status check; running GLCanvasElement on a headless/Software GL context.

Related errors


AI-assisted analysis of unoplatform/uno@0418340488 (2026-08-13). Data as JSON: /api/errors/092d28b5984f0774. Report an issue: GitHub.