unoplatform/uno · critical · Exception
Readback FBO is not complete
Error message
Readback FBO is not complete
What it means
Thrown by GLCanvasElement_PixelBuffersElement.Init when gl.CheckFramebufferStatus returns anything other than GL_FRAMEBUFFER_COMPLETE after attaching the readback texture (InternalFormat.Rgba8, 64x64) to a framebuffer. It means the GL driver considers the FBO unusable for rendering/readback, so the byte-exact PBO round-trip self-check that follows cannot proceed. This is a hard precondition: a non-complete FBO makes gl.ReadPixels undefined.
Source
Thrown at src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PixelBuffersGlCanvasElement.cs:69
gl.BindBuffer(BufferTargetARB.PixelUnpackBuffer, _unpackPbo);
gl.BufferData(BufferTargetARB.PixelUnpackBuffer, new ReadOnlySpan<byte>(pixels), BufferUsageARB.StreamDraw);
_texture = gl.GenTexture();
gl.BindTexture(TextureTarget.Texture2D, _texture);
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, TexSize, TexSize, 0, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);
gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.Nearest);
gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureMagFilter, (int)GLEnum.Nearest);
gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureWrapS, (int)GLEnum.ClampToEdge);
gl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureWrapT, (int)GLEnum.ClampToEdge);
gl.BindBuffer(BufferTargetARB.PixelUnpackBuffer, 0);
// --- Readback path: texture -> FBO -> ReadPixels -> pack PBO ---
_fbo = gl.GenFramebuffer();
gl.BindFramebuffer(GLEnum.Framebuffer, _fbo);
gl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, GLEnum.Texture2D, _texture, 0);
if (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
{
throw new Exception("Readback FBO is not complete");
}
_packPbo = gl.GenBuffer();
gl.BindBuffer(BufferTargetARB.PixelPackBuffer, _packPbo);
gl.BufferData(BufferTargetARB.PixelPackBuffer, (nuint)pixels.Length, null, BufferUsageARB.StreamRead);
gl.ReadPixels(0, 0, TexSize, TexSize, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);
gl.BindFramebuffer(GLEnum.Framebuffer, 0);
// --- Round-trip self-check: pack PBO -> CPU, byte-exact against the source ---
var readBack = new byte[pixels.Length];
if (OperatingSystem.IsAndroid() || OperatingSystem.IsIOS())
{
// Native GLES has no glGetBufferSubData; map the pack PBO and copy it out.
var mapped = (byte*)gl.MapBufferRange(BufferTargetARB.PixelPackBuffer, 0, (nuint)readBack.Length, MapBufferAccessMask.ReadBit);
if (mapped is null)
{
throw new Exception("MapBufferRange(PixelPackBuffer) returned null");
}View on GitHub (pinned to 0418340488)
Solutions
- Call gl.GetError() right before the CheckFramebufferStatus to surface the real GL error code that made the texture/FBO incomplete (0x502 invalid operation, 0x501 invalid enum, etc.).
- Query the actual GL context via gl.GetString(StringName.Version) and StringName.ShadingLanguageVersion to confirm whether RGBA8 color-renderable targets are supported on this device; on pure GLES2 swap InternalFormat.Rgba8 for a format the driver advertises as renderable.
- Verify the texture is complete on its own: ensure TexImage2D ran with non-zero dimensions and, if the min filter were Mipmap-based, that all mip levels exist (here it is Nearest, so that is not the cause, but confirm no prior bind overwrote _texture).
- Check the completeness code explicitly (FramebufferStatus.Unsupported, IncompleteAttachment, IncompleteMissingAttachment) instead of only 'not complete' to pinpoint whether the attachment, dimensions, or format is the offender.
Example fix
// before
if (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
{
throw new Exception("Readback FBO is not complete");
}
// after — surface the specific completeness code and any pending GL error
var fbErr = gl.GetError();
var status = gl.CheckFramebufferStatus(GLEnum.Framebuffer);
if (status != GLEnum.FramebufferComplete)
{
throw new Exception($"Readback FBO not complete: status=0x{(int)status:x}, pending GL error=0x{(int)fbErr:x}");
} Defensive patterns
Strategy: validation
Validate before calling
// Validate the texture is renderable-complete before attaching it to the FBO gl.BindTexture(TextureTarget.Texture2D, _texture); gl.GetTexLevelParameter(TextureTarget.Texture2D, 0, GetTextureParameter.TextureWidth, out int w); gl.GetTexLevelParameter(TextureTarget.Texture2D, 0, GetTextureParameter.TextureHeight, out int h); if (w == 0 || h == 0) return; // texture has no storage; FBO attach will be incomplete var pending = gl.GetError(); if (pending != GLEnum.NoError) return; // earlier error corrupted state
Try / catch
// Wrap the whole Init so an FBO failure does not kill the host app
try { Init(gl); }
catch (Exception ex) when (ex.Message.Contains("FBO is not complete"))
{
_initFailed = true;
App.MainWindow?.LogError($"PixelBuffers FBO incomplete: {ex.Message}");
} Prevention
- Always query gl.CheckFramebufferStatus's specific code, not just 'not complete'.
- Use sized renderable internal formats (Rgba8) rather than unsized (Rgba) for FBO color attachments.
- Drain gl.GetError() before the FBO check to surface the underlying cause.
- Query the context's supported renderable formats on GLES/WebGL before relying on a format.
When it happens
Trigger: Specifically after gl.FramebufferTexture2D(Framebuffer, ColorAttachment0, Texture2D, _texture, 0) followed by gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete (line 67). The attached _texture was created with InternalFormat.Rgba8 and TexImage2D sized TexSize=64, Nearest min/mag filter (no mipmaps required).
Common situations: The GL context is OpenGL ES / WebGL2 where RGBA8 color-renderable support is missing or the sized internal format is rejected; the texture's TexImage2D storage was never actually allocated (an earlier GL error left _texture incomplete); running on a software/llvmpipe rasterizer that reports limited framebuffer configs; a previous un-queried GL error corrupted texture state before the FBO attach.
Related errors
- Offscreen 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/e62a10a1d281355c.
Report an issue: GitHub.