将 OpenGL 后台缓冲区直接复制到 GDI DC 像素数据上

Posted

技术标签:

【中文标题】将 OpenGL 后台缓冲区直接复制到 GDI DC 像素数据上【英文标题】:Copy OpenGL back buffer directly onto GDI DC pixel data 【发布时间】:2014-04-23 12:36:54 【问题描述】:

我正在编写一个 GUI,它通过 OpenTK 和 C# 上的 GLControl 使用 OpenGL,我正在尝试使用脏矩形来仅绘制需要绘制的控件。显然,仅仅为了刷新鼠标悬停按钮而重绘整个最大化的表单是不明智的。

我的第一次尝试是使用 glScissors,但这并不限制 SwapBuffers,在我的平台上,我怀疑(因为性能几乎完全取决于窗口大小)不会“交换”而是做一个完整的副本将后缓冲区转移到前缓冲区。

第二次尝试是 glAddSwapHintRectWIN,理论上它会限制 SwapBuffers 的交换(在本例中为复制)区域,但这只是一个提示,它根本不会做任何事情。

第三次尝试是 glDrawBuffer 将后台缓冲区的一部分复制到帧缓冲区,出于某种未知原因,即使我只复制了缓冲区的一部分,当窗口大小之前,性能仍然会以同样的方式下降增加。

它表明无论我做什么,全区域刷新它仍然在进行中。

所以我正在尝试使用 glReadPixels () 并以某种方式获得一个指针以直接绘制到从控件的 CreateGraphics() 获取的 hDC 像素数据上。这可能吗?

编辑:

我认为 GLControl 有问题,为什么这段代码的性能取决于屏幕大小,我没有做任何交换缓冲区或清除,只是在前缓冲区上绘制一个恒定大小的三角形:驱动程序问题,也许吧?

        GL.DrawBuffer(DrawBufferMode.Front);

        Vector4 Color;
        Color = new Vector4((float)R.NextDouble(), 0, 0, 0.3F);

        GL.Begin(BeginMode.Triangles);

        GL.Color4(Color.X, Color.Y, Color.Z, Color.W);



        GL.Vertex3(50, 50, 0);
        GL.Vertex3(150F, 50F, 0F);
        GL.Vertex3(50F, 150F, 0F);

        GL.End();

        GL.Finish();

编辑 2 此解决方案不可行: 绘制到纹理上并使用 glGetTexImage 绘制到 GDI 位图上,然后将该位图绘制到窗口 hDC 上

使用 glReadPixels 从缓冲区中读取缓冲区像素到 GDI 位图上,然后将该位图绘制到窗口 hDC 上。

将窗口拆分到视口网格上并仅更新包含脏矩形的单元格

【问题讨论】:

GL.GetString(StringName.Renderer) 返回什么? 【参考方案1】:

首先,您使用的是什么平台(GPU 和操作系统)?我们在谈论什么样的表现?

请记住,尝试在同一个 hDC 上组合 GDI 和 OpenGL 时存在一些限制。实际上,在大多数情况下,这将关闭硬件加速并通过 Microsoft 的软件渲染器为您提供 OpenGL 1.1。

硬件加速 OpenGL 针对每帧重绘整个窗口进行了优化。 SwapBuffers() 使后备缓冲区的内容无效,这使得在默认帧缓冲区上进行双缓冲时无法实现脏矩形。

有两种解决方案:

    请勿致电SwapBuffers()。设置GL.DrawBuffer(DrawBufferMode.Front) 并使用单缓冲更新脏的矩形。这有严重的缺点,包括在 Windows 上关闭桌面合成。 不要直接渲染到默认帧缓冲区。相反,分配并渲染到帧缓冲区对象中。这样,您可以仅更新 FBO 中已修改的区域。 (您仍然需要复制 FBO 以筛选每一帧,因此取决于您的 GUI 复杂性,它可能会或可能不会提高性能。)

编辑:

单个三角形的 40-60 毫秒表示您没有获得任何硬件加速。检查GL.GetString(StringName.Renderer) - 它是给出你的GPU的名称还是返回"Microsoft GDI renderer"

如果是后者,则必须从 GPU 供应商的网站安装 OpenGL 驱动程序。这样做,性能问题就会消失。

【讨论】:

我认为你错了,你可以在双缓冲上实现脏矩形,是的,后台缓冲区无效,这就是为什么你每帧都清除它,但想法是只绘制和清除无效的后缓冲区上的区域,其他所有内容仍然是未定义的,并将该区域复制到帧缓冲区。我在 Windows 7 上有一个 ATI Radeon Mobility HD 4250。由于显而易见的原因,我不能离开双缓冲,第二个选项也是一样的,我必须将整个后缓冲区绘制到前缓冲区 the idea is to clear only the invalidated area on the back buffer, everything else still would be undefined。由于一切都未定义,因此一切都无效 - 换句话说,使用双缓冲时必须重绘一切。我不明白这会如何工作。 对不起,我的意思是只清除和绘制后缓冲区上的“脏矩形”,然后将该区域复制到前缓冲区。在实践中,脏矩形区域当然会比后台缓冲区的总面积小得多 这仍然没有(必然)帮助:(a)它需要具有复制语义的 GraphicsMode(更慢!)和(b)SwapBuffers 无论如何都会将整个后缓冲区复制到前缓冲区,不仅仅是脏位。对于 (a),请提交 feature request。我不知道有没有办法解决(b)。编辑:你得到什么样的表现? 当窗口最大化时,我的帧率降低了大约 50 倍,DirectX 已经支持dirty rectangles 我不再使用 OpenTK,我已经使用 DLLImport 和 p/ 导入了一些 OpenGL 函数调用,所以这不是 OpenTK 问题。我有一些部分解决方案,例如将纹理读取到位图上,然后绘制该位图,但无论如何这很慢,我只适用于非常小的脏矩形【参考方案2】:

在使用 OpenTK 进行多次测试后,似乎在单缓冲或双缓冲模式下,随着控件大小的增加观察到的减速仍然存在,即使启用了恒定大小的剪刀。即使是否使用 GL.Clear() 也不会影响减速。 (请注意,只有高度变化会产生重大影响。)

使用 ansi c 示例进行测试,我得到了相同的结果。

在 linux 下进行相同的测试也得到了相同的结果。

在 linux 下,我注意到当我从一个显示器移动到另一个显示器时,帧速率会发生变化。即使禁用了垂直同步。

下一步是检查 directX 是否具有相同的行为。如果是,则限制位于显示器和显卡之间的总线上。

编辑:结论:

这种行为会导致您产生错误的印象。考虑只在带有脏矩形机制的 FBO 上构建您的界面,然后在四边形(由三边形更好)上渲染它,然后像往常一样进行交换,而不要认为您可以通过剪裁一些操作来改进给定窗口大小的交换。

【讨论】:

以上是关于将 OpenGL 后台缓冲区直接复制到 GDI DC 像素数据上的主要内容,如果未能解决你的问题,请参考以下文章

将opengl主帧缓冲区复制到fbo

OpenGL(FBO)中的普通后台缓冲区+渲染到深度纹理

在OpenGL中绘制到半透明帧缓冲区时如何抗锯齿? [复制]

OpenGL复制顶点缓冲区对象

OpenGL:复制帧缓冲区对象

OpenGL glBufferSubData 偏移问题