unoplatform/uno · error · Exception

MapBufferRange(PixelPackBuffer) returned null

Error message

MapBufferRange(PixelPackBuffer) returned null

What it means

Thrown on Android/iOS native GLES where gl.GetBufferSubData is unavailable, so the code maps the PIXEL_PACK_BUFFER with gl.MapBufferRange(ReadBit) to copy the read-back pixels to CPU memory. MapBufferRange returning a null pointer means the driver refused to expose the buffer's store — typically because no buffer of that target is bound, the buffer has no data store allocated, the GPU still owns it, or memory is exhausted. Dereferencing a null mapped pointer would crash, so the null is checked explicitly.

Source

Thrown at src/SamplesApp/SamplesApp.Samples/Windows_UI_Composition/PixelBuffersGlCanvasElement.cs:86

			{
				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");
				}
				new ReadOnlySpan<byte>(mapped, readBack.Length).CopyTo(readBack);
				gl.UnmapBuffer(BufferTargetARB.PixelPackBuffer);
			}
			else
			{
				fixed (byte* p = readBack)
				{
					gl.GetBufferSubData(BufferTargetARB.PixelPackBuffer, 0, (nuint)readBack.Length, p);
				}
			}
			gl.BindBuffer(BufferTargetARB.PixelPackBuffer, 0);
			for (int i = 0; i < pixels.Length; i++)
			{
				if (readBack[i] != pixels[i])
				{
					throw new Exception($"PBO round-trip mismatch at byte {i}: got {readBack[i]}, expected {pixels[i]}");
				}

View on GitHub (pinned to 0418340488)

Solutions

  1. Call gl.GetError() immediately before and after MapBufferRange to capture the exact code (0x505 out_of_memory, 0x502 invalid_operation when no buffer bound, 0x506 invalid_value when offset+length exceeds store).
  2. Insert gl.Flush() (or on WebGL2 a fence sync + ClientWaitSync) between ReadPixels and MapBufferRange so the GPU finishes the readback before the host maps the same store.
  3. Confirm a PIXEL_PACK_BUFFER is still bound at map time: re-issue gl.BindBuffer(BufferTargetARB.PixelPackBuffer, _packPbo) immediately before MapBufferRange.
  4. If the GLES version lacks MapBufferRange read support, fall back to a second ReadPixels directly into a CPU pointer (no PBO) on mobile, or use gl.ReadPixels with a PACK buffer only on desktop paths.
  5. Check the buffer actually has storage: BufferData was called with a null data pointer and StreamDraw/StreamRead usage — verify size is non-zero and matches pixels.Length.

Example fix

// before
var mapped = (byte*)gl.MapBufferRange(BufferTargetARB.PixelPackBuffer, 0, (nuint)readBack.Length, MapBufferAccessMask.ReadBit);
if (mapped is null) throw new Exception("MapBufferRange(PixelPackBuffer) returned null");

// after — flush, rebind, and capture the reason
if (OperatingSystem.IsAndroid() || OperatingSystem.IsIOS())
{
    gl.Flush();
    gl.BindBuffer(BufferTargetARB.PixelPackBuffer, _packPbo);
    var pre = gl.GetError();
    var mapped = (byte*)gl.MapBufferRange(BufferTargetARB.PixelPackBuffer, 0, (nuint)readBack.Length, MapBufferAccessMask.ReadBit);
    if (mapped is null)
    {
        var why = gl.GetError();
        throw new Exception($"MapBufferRange(PixelPackBuffer) returned null (pre=0x{(int)pre:x}, post=0x{(int)why:x})");
    }
    // ...
}
Defensive patterns

Strategy: validation

Validate before calling

// Confirm a PIXEL_PACK_BUFFER is bound and has storage before mapping
gl.GetInteger(GLEnum.PixelPackBufferBinding, out int bound);
if (bound == 0) { /* nothing bound — MapBufferRange will return null */ gl.BindBuffer(BufferTargetARB.PixelPackBuffer, _packPbo); }
gl.Flush(); // ensure ReadPixels finished before mapping the same store

Try / catch

try
{
    var mapped = (byte*)gl.MapBufferRange(BufferTargetARB.PixelPackBuffer, 0, (nuint)len, MapBufferAccessMask.ReadBit);
    if (mapped is null) throw new Exception("map null");
    // ... copy ...
}
catch (Exception ex) when (ex.Message.Contains("MapBufferRange"))
{
    // Fallback: read pixels directly into CPU memory (no PBO) on this mobile context
    gl.BindBuffer(BufferTargetARB.PixelPackBuffer, 0);
    fixed (byte* p = readBack) gl.ReadPixels(0, 0, TexSize, TexSize, GLEnum.Rgba, GLEnum.UnsignedByte, p);
}

Prevention

When it happens

Trigger: Only on OperatingSystem.IsAndroid() || OperatingSystem.IsIOS() branches (line 80-90): gl.MapBufferRange(BufferTargetARB.PixelPackBuffer, 0, readBack.Length, MapBufferAccessMask.ReadBit) returns null. Preceded by gl.BufferData(PixelPackBuffer, size, null, StreamRead) and gl.ReadPixels into offset 0, then gl.BindFramebuffer(Framebuffer, 0).

Common situations: The PIXEL_PACK_BUFFER got unbound between ReadPixels and MapBufferRange (e.g. the BindFramebuffer(0) on line 76 changed bound state but should not affect the PBO); the GLES context does not support mapping a buffer that is still the destination of an in-flight ReadPixels without a gl.Finish; MapBufferRange with only ReadBit is unsupported on some GLES2-only contexts (needs GL_EXT_map_buffer_range or GLES3); GL_OUT_OF_MEMORY on constrained mobile GPUs.

Related errors


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