{"record":{"id":"4d80f08e703d7155","repo":"unoplatform/uno","slug":"pbo-round-trip-mismatch-at-byte-i-got-readback","errorCode":null,"errorMessage":"PBO round-trip mismatch at byte {i}: got {readBack[i]}, expected {pixels[i]}","messagePattern":"PBO round-trip mismatch at byte (.+?): got (.+?), expected (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PixelBuffersGlCanvasElement.cs","lineNumber":103,"sourceCode":"\t\t\t\t{\n\t\t\t\t\tthrow new Exception(\"MapBufferRange(PixelPackBuffer) returned null\");\n\t\t\t\t}\n\t\t\t\tnew ReadOnlySpan<byte>(mapped, readBack.Length).CopyTo(readBack);\n\t\t\t\tgl.UnmapBuffer(BufferTargetARB.PixelPackBuffer);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfixed (byte* p = readBack)\n\t\t\t\t{\n\t\t\t\t\tgl.GetBufferSubData(BufferTargetARB.PixelPackBuffer, 0, (nuint)readBack.Length, p);\n\t\t\t\t}\n\t\t\t}\n\t\t\tgl.BindBuffer(BufferTargetARB.PixelPackBuffer, 0);\n\t\t\tfor (int i = 0; i < pixels.Length; i++)\n\t\t\t{\n\t\t\t\tif (readBack[i] != pixels[i])\n\t\t\t\t{\n\t\t\t\t\tthrow new Exception($\"PBO round-trip mismatch at byte {i}: got {readBack[i]}, expected {pixels[i]}\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// --- Geometry + shader ---\n\t\t\t_vao = gl.GenVertexArray();\n\t\t\tgl.BindVertexArray(_vao);\n\t\t\t_vbo = gl.GenBuffer();\n\t\t\tgl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);\n\t\t\tvar quad = new float[]\n\t\t\t{\n\t\t\t\t-1f, -1f,     0f, 0f,\n\t\t\t\t 1f, -1f,     1f, 0f,\n\t\t\t\t 1f,  1f,     1f, 1f,\n\t\t\t\t-1f, -1f,     0f, 0f,\n\t\t\t\t 1f,  1f,     1f, 1f,\n\t\t\t\t-1f,  1f,     0f, 1f,\n\t\t\t};\n\t\t\tgl.BufferData(BufferTargetARB.ArrayBuffer, new ReadOnlySpan<float>(quad), BufferUsageARB.StaticDraw);","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/unoplatform/uno/blob/04183404888ea21b4e5bfa3493e8d5f15e972c3a/src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PixelBuffersGlCanvasElement.cs#L85-L121","documentation":"The sample performs a full byte-exact round-trip self-check: a known checkerboard is uploaded to a texture via a PIXEL_UNPACK_BUFFER, then read back via ReadPixels into a PIXEL_PACK_BUFFER, then copied to CPU memory, and every byte is compared against the original. This throw fires on the first byte that differs, so it exposes a silent data corruption somewhere in the upload/readback pipeline — most often a packing/alignment, format, or colorspace transformation the GL driver applied.","triggerScenarios":"After the readBack array is filled (either via MapBufferRange on mobile or GetBufferSubData on desktop), the loop at lines 99-105 finds readBack[i] != pixels[i] for some i. The pixels are RGBA8 UnsignedByte, TexSize=64, no PACK/UNPACK alignment is set so GL defaults apply (4-byte row alignment — 64*4 = 256 bytes per row is already 4-aligned).","commonSituations":"The GL driver premultiplies alpha or applies sRGB conversion because the framebuffer/texture is treated as sRGB-capable; BGRA vs RGBA byte order on some desktop GL drivers; a Y-flip between TexImage2D origin (bottom-left) and the readback; GL_PACK_ALIGNMENT defaulting to 4 conflicting with row stride on non-4-aligned widths (not the case at width 64, but would be at other widths); an earlier silent GL error left the texture partially uninitialized so ReadPixels returns zero/garbage.","solutions":["Set gl.PixelStore(PixelStorePname.PackAlignment, 1) and gl.PixelStore(PixelStorePname.UnpackAlignment, 1) before ReadPixels to rule out row-stride padding as the mismatch source.","Inspect which bytes differ: if every component of every pixel is shifted by a constant, suspect BGRA/RGBA swap or a Y-flip; if only alpha differs, suspect premultiply; if values are clamped or gamma-coded, suspect sRGB.","Confirm the texture internal format matches the readback format (TexImage2D used InternalFormat.Rgba8 + GLEnum.Rgba/GLEnum.UnsignedByte and ReadPixels used the same pair) — any mismatch in sized/unsized format pairing triggers silent conversion.","Print the index i and the surrounding pixel so you can tell whether the divergence is at row boundaries (alignment) or scattered (data corruption).","Verify no GL error was raised by TexImage2D/ReadPixels before the compare — a pending error usually means the readback wrote nothing and readBack is all zeros."],"exampleFix":"// before\nfor (int i = 0; i < pixels.Length; i++)\n{\n    if (readBack[i] != pixels[i])\n        throw new Exception($\"PBO round-trip mismatch at byte {i}: got {readBack[i]}, expected {pixels[i]}\");\n}\n\n// after — pin alignment, capture context on first divergence\ngl.PixelStore(PixelStorePname.PackAlignment, 1);\ngl.PixelStore(PixelStorePname.UnpackAlignment, 1);\nfor (int i = 0; i < pixels.Length; i++)\n{\n    if (readBack[i] != pixels[i])\n    {\n        int px = (i / 4) % TexSize, py = (i / 4) / TexSize, ch = i % 4;\n        throw new Exception($\"PBO mismatch at byte {i} (px={px},py={py},ch={ch}): got {readBack[i]}, expected {pixels[i]}\");\n    }\n}","handlingStrategy":"validation","validationCode":"// Set explicit pack/unpack alignment and verify no GL error preceded the readback\ngl.PixelStore(PixelStorePname.PackAlignment, 1);\ngl.PixelStore(PixelStorePname.UnpackAlignment, 1);\nif (gl.GetError() != GLEnum.NoError) return; // an earlier error means readback is unreliable","typeGuard":null,"tryCatchPattern":"try { /* round-trip compare */ }\ncatch (Exception ex) when (ex.Message.Contains(\"round-trip mismatch\"))\n{\n    // Log the byte offset + pixel coordinates to diagnose alignment vs colorspace\n    App.MainWindow?.LogError(ex.Message);\n    _roundTripOk = false; // continue rendering with the (possibly mis-converted) texture\n}","preventionTips":["Set PackAlignment and UnpackAlignment to 1 before TexImage2D and ReadPixels to rule out row padding.","Match the TexImage2D internal format / pixel format / type exactly with the ReadPixels format / type.","Watch for sRGB framebuffers/textures that silently gamma-convert on read/write.","Verify TexSize * 4 is a multiple of the default 4-byte alignment (it is at width 64)."],"tags":["opengl","silk-net","pbo","readback","data-integrity","pixel-format"],"backgroundTag":null,"analyzedSha":"04183404888ea21b4e5bfa3493e8d5f15e972c3a","analyzedAt":"2026-08-13T21:08:11.651Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}