65.9K
CodeProject 正在变化。 阅读更多。
Home

Boero ListView: 第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (3投票s)

2008年9月2日

GPL3

8分钟阅读

viewsIcon

74397

downloadIcon

1012

一个扩展的属主绘制 ListView, 完全使用 OpenGL 渲染

weather.png

背景

偶尔,论坛上有人会问到在ListView 背景中渲染视频、DirectX 或 OpenGL 的问题。大多数情况下,他们会被断然告知“不可能”,除非用户愿意使用覆盖层。覆盖层在 Vista Aero 中悄然消失了。这就是视频播放器 VLC 和 BSPlayer 的墙纸模式被禁用,以及我曾经在一些 Quadro 卡上使用的、用于渲染背景的炫酷 ListView 被禁用。

此控件实现了 ListView 应有的功能——完全通过 OpenGL 进行硬件渲染。源代码中包含了一个 BoeroLV 和一个完全相同的标准 System.Windows.Forms.ListView 的对比示例。查看示例源代码,了解其中一些可用的可能性。

Using the Code

本文档假设您已完全熟悉 OpenGL 和 Tao Framework OpenGL 包装器,可在 taoframework.com 找到。该控件应该能直接替换大多数标准的、未分组的大图标 ListView。像 BackgroundColorFont 等常用属性都已涵盖。像 TileDetailSmallIcon 视图和分组等一些选项目前尚不支持。任何有时间或有兴趣实现这些功能的人都非常欢迎。我之所以留下这些,将在本文第二部分中揭示原因。

一旦用它替换了标准的 ListViewRenderBackground 事件就可以用来在当前渲染上下文中执行几乎任何 OpenGL 调用,前提是避免使用某些保留的显示列表和纹理。保留的纹理和显示列表在代码中已标记,并且如果需要使用特定范围,则可以完全自定义。另请注意 maxItems 常量。为了在渲染时降低 CPU 开销,项目是使用一个简陋的场景图进行渲染和调整的。说我老古董也好,我没有使用顶点缓冲区,但是显示列表能够很好地管理这些并保持简单。这也意味着,如果 OpenGL 的下一个主要版本计划中将移除显示列表,那么此控件可能会遇到麻烦。

Visual Studio 窗体设计器

设计器可以处理此控件*……* 在我的 XP 和 Vista 系统上(但在我的 Mono 体验中不行),我从未能从同一个进程中成功地重新实例化相同的渲染上下文两次。即使在捆绑的 Tao 示例中,如果您尝试运行同一个示例两次,应用程序也会在没有任何错误或解释的情况下消失。同样,如果您关闭设计器窗口并重新打开它,Visual Studio 会在没有任何解释、异常或保存的情况下关闭!在找到补丁之前,请自行承担风险。

示例中演示了一个非常简单的旋转茶壶背景。请注意,所有渲染调用都来自线程安全的 System.Windows.Forms.Timer ,这样渲染代码就不会与事件或跨线程的混乱情况并行运行。这使得渲染您想要的内容变得简单,而无需担心有人更改 Color Font 属性,并且对其他进程也非常友好,将 CPU 活动保持在最低水平。

RenderBackground

private void Teapot_Background(long frame)
{
    // Call projection matrix.
    Gl.glCallList(100);
    Gl.glPushMatrix();
    Gl.glTranslatef(40, -20, -80);
    Gl.glRotated(frame / 15.0, 1, 0, 0);
    Gl.glRotated(frame / 12.0, 0, 1, 0);
    Gl.glRotated(frame / 7.0, 0, 0, 1);
    // Teapot list pops the model view matrix.
    Gl.glCallList(101);
}       

等等,现在。显示列表 100 和 101 是在哪里构建的?在渲染上下文变为当前之前不可能构建这些列表,并且在窗体显示之前也不可能使渲染上下文变为当前。最初,这里有一个检查,在第一次遍历时构建列表。“如果 100 不是一个有效的列表,则构建它。”一旦我的列表构建完成,每次帧渲染都会进行大量不必要的分支。除了 RenderBackground 之外,还需要第二个事件。这个事件就是 AfterInitRC

AfterInitRC

在示例中,我使用了 glutWireTeapot。这意味着我必须初始化 FreeGlut,但只需要执行一次——在渲染上下文建立后的一次。在 AfterInitRC 中,立即调用 glutInit()。既然我们在这里,就为其他一些示例构建一些其他的列表。为什么不提供一个显示我天气预报的滚动字幕,或者一个模拟时钟的选项呢?在此事件中加载或创建所有纹理和列表,以避免动画卡顿。请注意,可能会希望使用多线程并拥有一个具有列表共享的第二个上下文。这超出了本文档的范围,但请继续关注第二部分。目前,此控件旨在基本且美观,同时不会耗尽 CPU 或使您的液冷 Halfquake Tournament 显卡过热。

orangeteapot.png

/// John Boero 17-AUG-2008 <summary>
/// Use the AfterInitRC event to create some basic display lists used in our backgrounds.
/// </summary>
private void boeroLV_AfterInitRC()
{
    // Optional: Do this if we draw glut teapots, etc.
    Glut.glutInit();

    ProjectionSetup();

    // Initialize teapot list.
    Gl.glNewList(101, Gl.GL_COMPILE);
    Gl.glEnable(Gl.GL_LINE_SMOOTH);
    Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
    Gl.glLineWidth((float)Height / 100);
    Gl.glColor4f(1, 1f, 0, 0.1f);
    Gl.glEnable(Gl.GL_BLEND);
    Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE);
    Glut.glutWireTeapot(40);
    Gl.glLineWidth((float)Width / 100);
    Gl.glColor4f(1, 0, 1, 0.2f);
    Glut.glutWireTeapot(40);
    Gl.glPopMatrix();
    Gl.glEndList(); 

Control 类提供的一个最好的函数是 DrawToBitmap。严格来说,您甚至不需要创建文件。天才!考虑到这一点,我们可以获取任何控件并将其直接绘制到 GL_TEXTURE_RECTANGLE_ARB 中。这使得设置天气预报非常方便。

// Initialize weather feed.
try
{
    int weatherTexture = 0x200001;
    XmlDocument weatherMilwaukee = new XmlDocument();
    // Surely everyone wants to know Milwaukee weather instead of their own, right?
    weatherMilwaukee.Load(HttpWebRequest.Create
	(http://www.google.com/ig/api?weather=milwaukee).
	GetResponse().GetResponseStream());
 
    foreach (XmlNode weather in weatherMilwaukee.GetElementsByTagName("forecast_conditions"))
    {
        foreach (XmlElement condition in weather)
        {
            switch (condition.Name)
            {
                 ....
                 case "icon":
                     weaControl.picConditions.Image =
                       Image.FromStream(
                       HttpWebRequest.Create( "http://google.com" 
                       + condition.GetAttribute("data")).GetResponse().GetResponseStream());
                 break;
                 // Ignore humidity wind_condition
            }
        }

        Bitmap bm = new Bitmap(weaControl.Width, weaControl.Height);
        weaControl.BackColor = boeroLV.BackColor;
        weaControl.DrawToBitmap(bm, weaControl.Bounds);
        BoeroView.ImageToTexture(weatherTexture++, bm);
    }
    .....            
}

ListView 中还缺少其他几个属性。

HighlightColor

使用此属性来自定义标准 SystemColors.Highlight 的着色和标签颜色。请注意,可以轻松地按项目自定义此颜色,但代码留给读者自行解决。您甚至可以忘记着色,并在每个选定项目后面绘制古怪的背景圆角正方形,就像 Vista 一样……如果您喜欢这种东西的话……

请注意,为文本着色很棘手。图标图像很简单,因为它们带有 alpha 通道。图标下方的文本是通过一个隐藏的 System.Windows.Forms.Label 创建的,但 Control.DrawToBitmap 不允许 alpha 通道。相反,标签会在黑色背景上绘制白色文本,并使用自定义的片段着色器来组合前景和背景颜色。默认的纹理模式 GL_MODULATE 不足以实现这一点,因为它没有多个活动颜色用于前景和背景。取而代之的是创建了以下简单的 GLSL 片段着色器,它们似乎效果很好:

图标着色器
uniform sampler2DRect sampler0;
uniform vec4 fc;
void main(void)
{
    vec4 texture = texture2DRect(sampler0, gl_TexCoord[0].st);
    gl_FragColor = mix(gl_TextureEnvColor[0], mix(fc, texture, 0.5), texture.a);
}
文本(标签)着色器
uniform sampler2DRect sampler0;
uniform vec4 bc, fc;
void main(void)
{
    vec4 texture = texture2DRect(sampler0, gl_TexCoord[0].st);
    gl_FragColor = mix(bc, fc, texture.r);
}
关于 ClearType 平滑的一点说明。尽管 Joel Splosky 有什么看法,但我喜欢 ClearType 平滑。它通过偏移不同的颜色通道,使文本边缘看起来更加平滑,这是非常巧妙的。不幸的是,在这个着色器中它会丢失。如果不放大屏幕截图很难看清,但 ClearType 平滑不会被标签纹理的灰度处理所捕捉。

HighlightAlpha

在当今的现代 UI 中,这会产生很大的影响。半透明一切!

MaxFrameRate

此值反比于用于刷新背景的计时器。将此值设置为 90 并不保证 90 FPS。它保证进程将尝试每 1/90 秒渲染一帧,但不包括 GPU 渲染所需的时间,甚至不包括以更高优先级运行的其他进程所需的时间。示例默认设置为 50。在我的 C2D/GF8600M GS 上,它通常以 33fps 运行,CPU 使用率通常为 0%。请注意,如果我拔掉笔记本电脑的电源,我的 GPU 会进入节能模式,那么 CPU 负担会增加到大约 15%,因为我的 GPU 和 CPU 需要更长的时间来渲染。

如果您对代码中的慢动画或高 CPU 利用率感到沮丧,可以尝试使用 VBO 或显示列表进行优化。或者找到一种方法来减少您为顶点创建的大网格。

关注点或常见问题解答

问:为什么没有分组?
答:与第二部分无关。

问:为什么 XP 中的窗口会留下无效区域?
答:无效区域事件的规避方式就像 Keanu Reeves 躲避 paparazzi 一样。Vista 是未来的 Windows,其复合窗口层不需要这些事件。要么实现三缓冲,要么不要将窗口移到 ListView 前面。

问:为什么滚动时会出现伪影?
答:我从未能消除这些。

问:当水平滚动条可见时,为什么图标会偏移?
答:这是留给读者的另一个谜题。

问:关于 MONO 呢?
答:Tao 完全兼容 Mono。我在 Sabayon 中尝试过,但效果有限。核心代码可以工作,但目前我无法在现有的 Form 上获取渲染上下文。我尝试过 SDL,它似乎只能创建一个单独的窗口。

问:关于 DirectX 呢?
答:有可能。如果 视频纹理 bug 能够修复,那将是一个不错的选择。目前,我更倾向于为 Mac/Linux 社区考虑潜在的 Mono 兼容性。

问:是否可以使用此技术将其他标准控件 OpenGL 化?
答:可能可以,如果您遵循构造函数并正确初始化渲染上下文。我个人认为 3D GridView 没有意义,除非有人愿意为此投入巨款。随心所欲吧。

问:我的立体 ListView 呢?
答:%) 试过了。让我花了三天时间眼花缭乱。后果自负……

问:Shell 编程 - 有使用此技术的文件浏览器吗?
答:请继续关注第二部分。

历史

该控件已经过几次修改

  • 从每次刷新时重新构建所有显示列表和纹理
  • 到中断刷新并使 WNDPROC 消息无效,仅在必要时进行绘制
  • 到无 if 分支的定时器驱动刷新委托
  • 到完全优化的场景图,仅更改需要更改的部分

它终于达到了我满意的优化水平。

抱歉

Tao 框架已在我的 GAC 中注册,但我没有将其包含在原始示例源代码下载中。Zip 文件已更新,可以在没有安装 Tao 的机器上运行。

© . All rights reserved.