unoplatform/uno · critical · Exception

Offscreen FBO is not complete

Error message

Offscreen FBO is not complete

What it means

Thrown by GLCanvasElement_PostProcessElement.Init when the offscreen render target's framebuffer is not complete after attaching the 256x256 color texture (InternalFormat.Rgba, Linear filter). A non-complete offscreen FBO means the multi-pass render-to-texture pipeline cannot work — Pass 1 draws into this target, Pass 2 samples it back.

Source

Thrown at src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PostProcessGlCanvasElement.cs:73

		{
			_startTime = DateTime.UtcNow;

			// --- Offscreen color texture ---
			_offscreenColor = gl.GenTexture();
			gl.BindTexture(TextureTarget.Texture2D, _offscreenColor);
			gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, OffscreenSize, OffscreenSize, 0, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);
			gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.Linear);
			gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureMagFilter, (int)GLEnum.Linear);
			gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureWrapS, (int)GLEnum.ClampToEdge);
			gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureWrapT, (int)GLEnum.ClampToEdge);

			// --- Offscreen FBO ---
			_offscreenFbo = gl.GenFramebuffer();
			gl.BindFramebuffer(GLEnum.Framebuffer, _offscreenFbo);
			gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, GLEnum.Texture2D, _offscreenColor, 0);
			if (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
			{
				throw new Exception("Offscreen FBO is not complete");
			}

			// --- Scene VAO/VBO ---
			_sceneVao = gl.GenVertexArray();
			gl.BindVertexArray(_sceneVao);
			_sceneVbo = gl.GenBuffer();
			gl.BindBuffer(BufferTargetARB.ArrayBuffer, _sceneVbo);
			gl.BufferData(BufferTargetARB.ArrayBuffer, new ReadOnlySpan<float>(_triangleData), BufferUsageARB.StaticDraw);
			gl.VertexAttribPointer(0, 2, GLEnum.Float, false, 5 * sizeof(float), (void*)0);
			gl.EnableVertexAttribArray(0);
			gl.VertexAttribPointer(1, 3, GLEnum.Float, false, 5 * sizeof(float), (void*)(2 * sizeof(float)));
			gl.EnableVertexAttribArray(1);

			// --- Post-process VAO/VBO ---
			_postVao = gl.GenVertexArray();
			gl.BindVertexArray(_postVao);
			_postVbo = gl.GenBuffer();
			gl.BindBuffer(BufferTargetARB.ArrayBuffer, _postVbo);

View on GitHub (pinned to 0418340488)

Solutions

  1. Use the sized renderable format InternalFormat.Rgba8 instead of the unsized InternalFormat.Rgba — unsized formats are not guaranteed color-renderable across GLES/WebGL contexts.
  2. Call gl.GetError() immediately before the status check to surface the underlying GL error.
  3. Read the specific completeness code (Unsupported, IncompleteAttachment, IncompleteMissingAttachment) to distinguish missing-attachment from unsupported-format.
  4. Confirm the texture was actually sized: ensure TexImage2D was called with OffscreenSize x OffscreenSize and a non-zero area, and that _offscreenColor is a valid name returned by GenTexture.
  5. On WebGL2/GLES3, query the implementation's supported renderable formats via gl.GetInternalformativ before relying on a particular format.

Example fix

// before
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, OffscreenSize, OffscreenSize, 0, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);
...
if (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
    throw new Exception("Offscreen FBO is not complete");

// after — sized renderable format + explicit completeness code
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, OffscreenSize, OffscreenSize, 0, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);
...
var status = gl.CheckFramebufferStatus(GLEnum.Framebuffer);
if (status != GLEnum.FramebufferComplete)
    throw new Exception($"Offscreen FBO not complete: 0x{(int)status:x}, GL error=0x{(int)gl.GetError():x}");
Defensive patterns

Strategy: validation

Validate before calling

// Use a sized, color-renderable format and check it before FBO attach
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, OffscreenSize, OffscreenSize, 0, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);
// Optionally query internalformat support on GLES3/WebGL2
// gl.GetInternalformativ(...) to confirm RENDERABLE bit

Try / catch

try { /* FBO setup + CheckFramebufferStatus */ }
catch (Exception ex) when (ex.Message.Contains("Offscreen FBO"))
{
    App.MainWindow?.LogError($"Offscreen FBO incomplete: {ex.Message}");
    // Skip multi-pass: render the scene directly to the default framebuffer
}

Prevention

When it happens

Trigger: Specifically after gl.FramebufferTexture2D(Framebuffer, ColorAttachment0, Texture2D, _offscreenColor, 0) and gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete (lines 70-73). The texture was created with InternalFormat.Rgba (unsized) and TexImage2D at OffscreenSize=256, Linear filtering (no mipmaps).

Common situations: Using the unsized InternalFormat.Rgba instead of a sized color-renderable format (Rgba8) — some drivers reject unsized formats as render targets; on GLES/WebGL the texture is color-renderable but the framebuffer needs GL_DEPTH_ATTACHMENT24 if depth is used (it is not here, but a stray depth test later would fail differently); llvmpipe/SwiftShader software contexts with limited render target configs; the texture storage was never allocated because TexImage2D's pixels pointer was 0/null and the driver deferred allocation until first use.

Related errors


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