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
- Use the sized renderable format InternalFormat.Rgba8 instead of the unsized InternalFormat.Rgba — unsized formats are not guaranteed color-renderable across GLES/WebGL contexts.
- Call gl.GetError() immediately before the status check to surface the underlying GL error.
- Read the specific completeness code (Unsupported, IncompleteAttachment, IncompleteMissingAttachment) to distinguish missing-attachment from unsupported-format.
- 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.
- 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
- Prefer sized renderable formats (Rgba8) over unsized (Rgba) for color attachments.
- Drain GetError before the status check to surface the underlying cause.
- Read the specific completeness code, not just 'not complete'.
- Query supported renderable formats on GLES/WebGL before relying on one.
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
- Readback FBO is not complete
- MapBufferRange(PixelPackBuffer) returned null
- Offscreen framebuffer is not complete
- Program link failed: {infoLog}
- Program link failed: {infoLog}
AI-assisted analysis of unoplatform/uno@0418340488 (2026-08-13).
Data as JSON: /api/errors/ed43ff2fb0e810ea.
Report an issue: GitHub.