{"record":{"id":"e62a10a1d281355c","repo":"unoplatform/uno","slug":"readback-fbo-is-not-complete","errorCode":null,"errorMessage":"Readback FBO is not complete","messagePattern":"Readback FBO is not complete","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PixelBuffersGlCanvasElement.cs","lineNumber":69,"sourceCode":"\t\t\tgl.BindBuffer(BufferTargetARB.PixelUnpackBuffer, _unpackPbo);\n\t\t\tgl.BufferData(BufferTargetARB.PixelUnpackBuffer, new ReadOnlySpan<byte>(pixels), BufferUsageARB.StreamDraw);\n\n\t\t\t_texture = gl.GenTexture();\n\t\t\tgl.BindTexture(TextureTarget.Texture2D, _texture);\n\t\t\tgl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, TexSize, TexSize, 0, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);\n\t\t\tgl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.Nearest);\n\t\t\tgl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureMagFilter, (int)GLEnum.Nearest);\n\t\t\tgl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureWrapS, (int)GLEnum.ClampToEdge);\n\t\t\tgl.TexParameter(TextureTarget.Texture2D, GLEnum.TextureWrapT, (int)GLEnum.ClampToEdge);\n\t\t\tgl.BindBuffer(BufferTargetARB.PixelUnpackBuffer, 0);\n\n\t\t\t// --- Readback path: texture -> FBO -> ReadPixels -> pack PBO ---\n\t\t\t_fbo = gl.GenFramebuffer();\n\t\t\tgl.BindFramebuffer(GLEnum.Framebuffer, _fbo);\n\t\t\tgl.FramebufferTexture2D(GLEnum.Framebuffer, FramebufferAttachment.ColorAttachment0, GLEnum.Texture2D, _texture, 0);\n\t\t\tif (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)\n\t\t\t{\n\t\t\t\tthrow new Exception(\"Readback FBO is not complete\");\n\t\t\t}\n\n\t\t\t_packPbo = gl.GenBuffer();\n\t\t\tgl.BindBuffer(BufferTargetARB.PixelPackBuffer, _packPbo);\n\t\t\tgl.BufferData(BufferTargetARB.PixelPackBuffer, (nuint)pixels.Length, null, BufferUsageARB.StreamRead);\n\t\t\tgl.ReadPixels(0, 0, TexSize, TexSize, GLEnum.Rgba, GLEnum.UnsignedByte, (void*)0);\n\t\t\tgl.BindFramebuffer(GLEnum.Framebuffer, 0);\n\n\t\t\t// --- Round-trip self-check: pack PBO -> CPU, byte-exact against the source ---\n\t\t\tvar readBack = new byte[pixels.Length];\n\t\t\tif (OperatingSystem.IsAndroid() || OperatingSystem.IsIOS())\n\t\t\t{\n\t\t\t\t// Native GLES has no glGetBufferSubData; map the pack PBO and copy it out.\n\t\t\t\tvar mapped = (byte*)gl.MapBufferRange(BufferTargetARB.PixelPackBuffer, 0, (nuint)readBack.Length, MapBufferAccessMask.ReadBit);\n\t\t\t\tif (mapped is null)\n\t\t\t\t{\n\t\t\t\t\tthrow new Exception(\"MapBufferRange(PixelPackBuffer) returned null\");\n\t\t\t\t}","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/unoplatform/uno/blob/04183404888ea21b4e5bfa3493e8d5f15e972c3a/src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PixelBuffersGlCanvasElement.cs#L51-L87","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nif (gl.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)\n{\n    throw new Exception(\"Readback FBO is not complete\");\n}\n\n// after — surface the specific completeness code and any pending GL error\nvar fbErr = gl.GetError();\nvar status = gl.CheckFramebufferStatus(GLEnum.Framebuffer);\nif (status != GLEnum.FramebufferComplete)\n{\n    throw new Exception($\"Readback FBO not complete: status=0x{(int)status:x}, pending GL error=0x{(int)fbErr:x}\");\n}","handlingStrategy":"validation","validationCode":"// Validate the texture is renderable-complete before attaching it to the FBO\ngl.BindTexture(TextureTarget.Texture2D, _texture);\ngl.GetTexLevelParameter(TextureTarget.Texture2D, 0, GetTextureParameter.TextureWidth, out int w);\ngl.GetTexLevelParameter(TextureTarget.Texture2D, 0, GetTextureParameter.TextureHeight, out int h);\nif (w == 0 || h == 0) return; // texture has no storage; FBO attach will be incomplete\nvar pending = gl.GetError();\nif (pending != GLEnum.NoError) return; // earlier error corrupted state","typeGuard":null,"tryCatchPattern":"// Wrap the whole Init so an FBO failure does not kill the host app\ntry { Init(gl); }\ncatch (Exception ex) when (ex.Message.Contains(\"FBO is not complete\"))\n{\n    _initFailed = true;\n    App.MainWindow?.LogError($\"PixelBuffers FBO incomplete: {ex.Message}\");\n}","preventionTips":["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."],"tags":["opengl","silk-net","fbo","framebuffer","rendering","gl-es","webgl"],"backgroundTag":null,"analyzedSha":"04183404888ea21b4e5bfa3493e8d5f15e972c3a","analyzedAt":"2026-08-13T21:08:11.651Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}