PBO / GL_PIXEL_UNPACK_BUFFER causes Memory Leak in iOS 9 ?

After seeing one of our projects' memory usage continously increasing from ~90MB up to 300MB in a couple of minutes ( 100-300KB/s ) I've run down to the cause of it. I don't know if this is an iOS 9 issue or not since we recently upgraded to iOS 9 on an iPad Air 2, but I've narrowed it down to using Pixel Buffer Objects for texture uploads.


So, every frame I do this :


  static bool VersionCheck = false;
  static bool UsePBOs = false;/
  if (!VersionCheck)
  {
       VersionCheck = true;
       const char *Version = (const char*)glGetString( GL_VERSION );
       float fVer = 2.0f;
       if (Version && sscanf( Version, "OpenGL ES %f", &fVer ))
       {
            if ( fVer >= 3.0f )
            UsePBOs = true;
       }
  }
  /
  if ( UsePBOs )
  {
       int TotalData = Width * Height *4;
       static unsigned int PBO = 0;
       if ( PBO == 0)
       {
            glGenBuffers( 1, &PBO );
            CheckGLError();
     
            glBindBuffer( GL_PIXEL_UNPACK_BUFFER, PBO );
            CheckGLError();
            glBufferData( GL_PIXEL_UNPACK_BUFFER, TotalData, NULL, GL_STREAM_COPY);
            CheckGLError();
       }

       glBindBuffer( GL_PIXEL_UNPACK_BUFFER, PBO );
       CheckGLError();
       GLubyte* ptr = (GLubyte*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, TotalData, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT );
       CheckGLError();

  if ( ptr )
  {
       pthread_mutex_lock( &CameraPixelsMutex );

       if ( Data )
            memcpy( ptr, Data, TotalData );

       pthread_mutex_unlock( &CameraPixelsMutex );
       GLboolean ret = glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER );
       CheckGLError();
       if ( ret != 1 )
       {
       NSLog(@"glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER ) returned %d", ret );
       }
  }
  else
       NSLog(@"glMapBufferRange returned nullptr %p", ptr );

       glBindTexture( GL_TEXTURE_2D, TextureID );
       glTexSubImage2D( GL_TEXTURE_2D, /
       0, /
       0,
       0, /
       Width, /
       Height, /
       GL_BGRA, /
       GL_UNSIGNED_BYTE, /
       0 
       );
       CheckGLError();

       glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );
       CheckGLError();
  }
  else  
  {
       pthread_mutex_lock( &CameraPixelsMutex );

       glBindTexture( GL_TEXTURE_2D, TextureID );
       CheckGLError();
       if ( Data )
       {
            glTexSubImage2D( GL_TEXTURE_2D, 
            0, 
            0, 
            0,
            Width, 
            Height, 
            GL_BGRA, 
            GL_UNSIGNED_BYTE, 
            Data 
       );
  }
  CheckGLError();

  pthread_mutex_unlock( &CameraPixelsMutex );
  }


The thing, the GLES3/PBO branch is causing this 100K/s leak while if I only use the GLES2 route (where perf goes down by 50%) the memory usage stays constant ( i waited around 5 minutes and it doesn't go up at all ).


This feels like a bug in the GL driver or something may be wrong with me code, please advise.

Replies

Is the leak increasing by a fixed amount per frame? If you change the width and height of the buffer, does the leak rate change correspondingly? Have you tried duplicating/looping any code inside the function (e.g. glMapBufferRange / glUnmapBuffer) to see if it affects the leak rate? That may help you narrow down which function call is responsible.

It's not fixed. It's 100-300KB/s. How would duplicating code help if the leak is smaller than the buffer or texture ? The texture/buffer is 640 * 480 * 4 bytes, that's 1.2MB / frame. At 35 FPS, that's over 30MB/s. The leak is just 1% of that. I don't think it's an actual function call that's wrong since it's the whole operation that's doing the leak. Perhaps the drive reallocates the memory mapped region and doesn't delete it entirely while unmapping ? Or perhaps the texture has a different size in memory and instead of a copy operation, the driver does a renaming operation and forgets to delete the texture descriptor or something similarly small ? On an iPhone 5 we tested the leaked bytes / s were smaller, but the thing is that the framerate was smaller as well.

The purpose of duplicating function calls is actually to see if you can make the bug worse (leaking more bytes per frame), which may help track down which specific GL calls are responsible, and which are not. Can you try counting the frames and the leak rate and verify that it's leaking a constant amount per frame? (I suspect that it is.)


If you destroy and recreate the entire GL context, do you get the leaked memory back? That might be an interesting test.

Just a fair warning, iOS 9.1 hasn't fixed the issue.

PS: I can't play around with the GL context since I'm using Unity.

9.2 still hasn't fixed this

We have now managed to reproduce the issue outside Unity and made a sample project that reproduces the issue. We have submitted a bug report on the apple bug reporter along with the sample project,

Bug ID=24056477

Same problem here.


In our case we observed that the leaks only happen if we run our project in XCode. The leaks do not happen if we run the project in the profiler (Instruments) or if we run it directly by launching it on the device. Debug or Release build does not matter.


If anyone observes that the problem goes away with newer iOS releases, please report here.

We have tested our code on a new iPad Pro with iOS 9.3.1 and the PBO leak is gone (the same code leaks on an iPad Air 2 with iOS 9.1).