WPF中的OpenGL控件因OOM异常而崩溃

时间:2014-12-12 09:55:50

标签: c# wpf opengl out-of-memory prism

每次打开包含OpenGL控件的模块时,都会在WPF PRISM应用程序中调用以下代码段。

代码" bmp.Save(ms,ImageFormat.Bmp);"在连续打开模块30至40次后,以OOM异常中断。

我们通过使用内存分析工具删除幸存的对象来优化代码。 我们还能在这做什么?

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;
} 

2 个答案:

答案 0 :(得分:1)

首先,我注意到你正在从你的opengl字节数组中创建一个System.Drawing.Bitmap,然后将其转换为System.Windows.Media.Imaging.BitmapImage。为什么不跳过中间步骤而改为WriteableBitmap呢?

    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;
    }

对于pixelFormat,您会传递PixelFormats.Bgr24(如果没有,那么PixelFormats.Rgb24),我认为这与Gl.GL_BGR相对应。这更直截了当,理论上应该为中间步骤使用更少的内存。

接下来,在创建位图后,您在使用位图做什么?您是将它们保存到磁盘然后删除对它们的所有引用,还是将它们保存在内存列表中?由于WPF位图是一次性的,因此释放它们所使用的内存的唯一方法就是不再在任何地方引用它们。

答案 1 :(得分:1)

我能够使用您的输入解决问题,所以首先要感谢所有人:)

@dbc:你是对的,我错了;)=>事实证明,退出模块时,我们的WPF图像显示生成的位图未正确发布。如果我将其源设置为null然后将图像本身设置为null,则不再存在问题。

这是工作实施:

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;
        }

并且在包含OpenGL控件的模块的Dispose函数中(在Windows窗体控件主机中)和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; // 
        }

周末愉快!