WPF 中的 OpenGL 控件崩溃并出现 OOM 异常

本文关键字:OOM 异常 崩溃 中的 OpenGL 控件 WPF | 更新日期: 2023-09-27 18:34:01

每次打开包含 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;
} 

WPF 中的 OpenGL 控件崩溃并出现 OOM 异常

首先,我注意到您正在从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 位图不是一次性的,因此释放它们使用的内存的唯一方法是不再在任何位置引用它们。

我能够使用您的输入解决问题,因此首先非常感谢大家:)

@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 控件的模块(在 Windows 窗体控件主机中)和 WPFImage 的 Dispose 函数中:

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

周末愉快!