Reputation: 1362
The following code segment is called in a WPF PRISM application every time a module containing the OpenGL control is opened.
The code "bmp.Save(ms, ImageFormat.Bmp);" breaks with an OOM Exception after opening the module continuously for 30 to 40 times.
We optimized the code by removing surviving objects with a memory profiling tool. What else can we do here?
public BitmapImage SaveToImage(bool automaticOrientation = false)
{
this.MakeCurrent();
// 1. Render the scene offscreen and get framebuffer bytes
// -------------------------------------------------------------------------------
byte[] bmpBuffer = this.renderer.GetOffscreenBytes(automaticOrientation);
// 2. Save the bytes to a memory stream
// -------------------------------------------------------------------------------
using (MemoryStream ms = new MemoryStream())
{
using (Bitmap bmp = new Bitmap(GLCore.HWSETTINGS.OFFBUFF_SIZE, GLCore.HWSETTINGS.OFFBUFF_SIZE, PixelFormat.Format24bppRgb))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(bmpBuffer, 0, bmpData.Scan0, bmpBuffer.Length);
bmp.UnlockBits(bmpData);
bmp.Save(ms, ImageFormat.Bmp);
//NEW
bmp.Dispose();
}
// 3. Save the memory stream to a BitmapImage and return-it
// -------------------------------------------------------------------------------
ms.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = ms;
bi.EndInit();
// NEW
bi.Freeze();
// END NEW
return bi;
}
}
...
public bool MakeCurrent()
{
if (this.context.deviceContext == IntPtr.Zero || this.context.renderingContext == IntPtr.Zero) return false;
// 1. Exit if we can't activate the rendering context
// -------------------------------------------------------------------------------
if (!Wgl.wglMakeCurrent(this.context.deviceContext, this.context.renderingContext))
{
MessageBox.Show("Can not activate the GL rendering context.", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
Environment.Exit(-1);
}
return true;
}
...
public byte[] GetOffscreenBytes(bool automaticOrientation = false)
{
// 1. Save current orientation
// -------------------------------------------------------------------------------
GLSceneOrientation currentOrientation = GLCore.SCENESETTINGS.ORIENTATION;
// 2. Reset orientation if necessary. Otherwise copy zoom to the offscreen
// buffer's viewport in case we are using orthogonal ptojection
// -------------------------------------------------------------------------------
if (automaticOrientation)
{
GLCore.SCENESETTINGS.ORIENTATION.rotX = GLCore.SCENESETTINGS.ORIENTATION.rotY = .0f;
GLCore.SCENESETTINGS.ORIENTATION.zoom = this.offscreenRenderBuff.Viewport.Zoom = 1.0f;
}
else
this.offscreenRenderBuff.Viewport.Zoom = this.viewport.Zoom;
// 3. Bind offscreen buffer
// -------------------------------------------------------------------------------
this.offscreenRenderBuff.Bind();
// 4. Perform rendering
// -------------------------------------------------------------------------------
Render();
// 5. Copy result of rendering to a byte array. Use a standard size of 1024*1024
// -------------------------------------------------------------------------------
byte[] bmpBuffer = new byte[GLCore.HWSETTINGS.OFFBUFF_SIZE * GLCore.HWSETTINGS.OFFBUFF_SIZE * 3];
Gl.glReadPixels(0, 0, GLCore.HWSETTINGS.OFFBUFF_SIZE, GLCore.HWSETTINGS.OFFBUFF_SIZE, Gl.GL_BGR, Gl.GL_UNSIGNED_BYTE, bmpBuffer);
// 6. Unbind offscreen buffer
// -------------------------------------------------------------------------------
this.offscreenRenderBuff.Unbind();
// 7. Restore orientation
// -------------------------------------------------------------------------------
GLCore.SCENESETTINGS.ORIENTATION = currentOrientation;
// 8. Return byte array
// -------------------------------------------------------------------------------
return bmpBuffer;
}
Upvotes: 2
Views: 444
Reputation: 11
I was able to fix the problem using your input so first of all thanks a lot to all of you :)
@dbc: you were right and I was wrong ;) => It turns-out that our WPF image showing the generated bitmap wasn't released properly upon exiting a module. If I set its source to null and then the image itself to null, no problem anymore.
Here the working implementation:
public BitmapSource SaveToImage(bool automaticOrientation = false)
{
// Set OpenGL context
// -------------------------------------------------------------------------------
this.MakeCurrent();
// Render the scene offscreen and get framebuffer bytes
// -------------------------------------------------------------------------------
byte[] bmpBuffer = this.renderer.GetOffscreenBytes(automaticOrientation);
// Convert the bytes to a Bitmap source
// -------------------------------------------------------------------------------
BitmapSource srcBMP = CreateBitmap(GLCore.HWSETTINGS.OFFBUFF_SIZE, GLCore.HWSETTINGS.OFFBUFF_SIZE, bmpBuffer, System.Windows.Media.PixelFormats.Bgr24);
bmpBuffer = null;
return srcBMP;
}
public static BitmapSource CreateBitmap(int width, int height, byte[] bmpBuffer, System.Windows.Media.PixelFormat pixelFormat)
{
var bitmap = new WriteableBitmap(width, height, 96, 96, pixelFormat, null);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), bmpBuffer, bitmap.BackBufferStride, 0);
bitmap.Freeze();
return bitmap;
}
and in the Dispose function of the module holding the OpenGL control (in a Windows Forms control host) and the WPFImage:
public void Dispose()
{
this.GLControl.Release(); // Releases OpenGL resources on the GPU
this.GLControlHost.Dispose(); // Needed otherwise the child control is retained
this.GLImage.Source = null; // Needed to avoid memory leak
this.GLImage = null; //
}
Have a nice weekend!
Upvotes: 1
Reputation: 117285
Firstly, I notice you are creating a System.Drawing.Bitmap
from your opengl byte array, then converting that to a System.Windows.Media.Imaging.BitmapImage
. Why not skip the intermediate step and make a WriteableBitmap
instead?
public static BitmapSource CreateBitmap(int width, int height, byte[] bmpBuffer, PixelFormat pixelFormat)
{
var bitmap = new WriteableBitmap(width, height, 96, 96, pixelFormat, null);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), bmpBuffer, bitmap.BackBufferStride, 0);
bitmap.Freeze();
return bitmap;
}
For the pixelFormat
you would pass PixelFormats.Bgr24
(or if not, then PixelFormats.Rgb24
), which I believe corresponds to Gl.GL_BGR
. This is more straightforward, and should in theory use less memory for intermediate steps.
Next, what are you doing with your bitmaps after you create them? Are you saving them to disk and then removing all references to them, or are you keeping them in an in-memory list? Since WPF bitmaps are not disposable, the only way to free the memory they use is to no longer refer to them anywhere.
Upvotes: 1