Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Boero ListView: Part I

0.00/5 (No votes)
21 Sep 2008 1  
An extended owner draw ListView that is completely rendered using OpenGL
weather.png

Background

Every now and then, someone on a forum asks about rendering video or DirectX or OpenGL to a ListView background. Most often, they are shot down by an emphatic "impossible" unless the user is open to overlays. Overlays quietly disappeared in Vista Aero. This is what crippled the wallpaper modes of the video players VLC and BSPlayer. It also happened to cripple a slick ListView I used on some Quadro cards to render some background underlays.

This control implements ListView the way it was meant to be - completely hardware rendered in OpenGL. Included with the source is an example of the BoeroLV adjacent to an identical standard System.Windows.Forms.ListView. View the example source for some of the possibilities available.

Using the Code

This article assumes full familiarity with OpenGL and the Tao Framework OpenGL wrappers, available at taoframework.com. The control should be a standard drop-in replacement for most standard ungrouped large icon ListViews. Common properties like BackgroundColor, Font, etc. are covered. Some options like Tile, Detail, SmallIcon view and groups are not currently supported. Anyone having the time or interest to code out these implementations is more than welcome. The reason for me leaving these out will become apparent in part 2 of this article.

Once used to replace a standard ListView, the RenderBackground event may be used to make about any OpenGL calls on the current rendering context, given that certain reserved display lists and textures are avoided. The reserved textures and display lists are marked in the code and are fully customizable in case you need to use a certain range. Also note the constant maxItems. To keep down CPU overhead while rendering, items are rendered and adjusted using a rudimentary scene graph. Call me old fashioned by not using vertex buffers, but display lists manage this nicely and keep this simple. It also means this control might be in trouble if display lists disappear from the next major release of OpenGL as is planned.

Visual Studio Form Designer

Designer can handle this control*... * On my XP and Vista systems (but not in my Mono experience) I can never manage to reinstantiate the same rendering context twice from the same process. Even in the bundled Tao examples, if you try to run the same one twice, the app will disappear with no fault or explanation. Similarly, if you close a designer window and reopen it, Visual Studio will close with no explanation, no exception, and no save! Consider yourself warned until a patch is found.

A very simple spinning teapot background is demonstrated in the example. Note that all rendering calls are done from a thread-safe System.Windows.Forms.Timer such that rendering code is never run parallel to events or cross-thread nastiness. This makes it simple to render what you want without worrying about someone changing Color or Font properties and also is very generous to other processes keeping CPU activity to a minimum.

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

Wait now. Where did display lists 100 and 101 get built? It isn't possible to build the lists before the rendering context is made current, and it isn't possible to make the RC current until the form is shown. Initially, there was a check here to build lists on the first pass. "If 100 is not a valid list, build it." This is a lot of unnecessary branching at every frame once my list is built. There was a need for a second event in addition to RenderBackground. This event is AfterInitRC.

AfterInitRC

In the example, I use glutWireTeapot. This means I have to init FreeGlut, but I only have to do that once - once after the rendering context is established. Right away in AfterInitRC, call glutInit(). While we're here, build a few other lists for some other examples. Why not have the option of a scrolling ticker that shows my weather forecast, or an analog clock? Load or create all textures and lists here in this event to avoid studdering animation. Note it may be desirable to multithread and have a second context with list sharing. That is beyond the scope of this article but hang on for part 2. For now, this control is meant to be basic and pretty without draining CPU or overheating your liquid-cooled Halfquake Tournament card.

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(); 

One of the best functions available to the Control class is DrawToBitmap. Technically, you don't even need to create a file. Genius! With that in mind, we can take any control and draw it right into a GL_TEXTURE_RECTANGLE_ARB. This makes framing up a weather forecast a breeze.

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

There are a few other properties that have long been missing from ListView.

HighlightColor

Use this to customize the tinting and label colors from the standard SystemColors.Highlight. Note it is easily possible to customize this color by item, but that code is left to the reader. You could even forget tinting and draw the kooky background rounded squares behind each selected item just like Vista... if you're into that sort of thing...

Note it is tricky to get text shaded. Icon images are simple because they load with an alpha channel. Text under the icons are created using a hidden System.Windows.Forms.Label, but Control.DrawToBitmap allows no alpha channel. Instead, the label draws white text on a black background and uses custom fragment shaders to combine foreground and background colors. Default texture mode GL_MODULATE wasn't enough to pull it off since it does not have multiple active colors for foreground and background. Instead the following simple GLSL fragment shaders were created, which seem to work very well:

Icon Shader
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);
}
Text (Label) Shader
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);
}
A note on ClearType smoothing. Despite what Joel Splosky says, I love ClearType smoothing. It's genius to offset different color channels giving the appearance of extra smooth text edges. Unfortunately, it is lost in this shader. It's not easy to tell without a zoomed screen capture, but ClearType smoothing is not captured by the grayscaling of the label textures.

HighlightAlpha

This makes a big difference in today's modern UI. Translucency is everything!

MaxFrameRate

This value inversely impacts the timer used to refresh the background. Setting this value to 90 does not guarantee 90 FPS. It guarantees that the process will try to render a frame every 1/90 a second, but does not include time required by the GPU to render or even other processes running at a higher priority. The example is set to 50 by default. On my C2D/GF8600M GS, it typically runs a comfortable 33fps with typically 0% CPU time. Note that if I pull the plug on my laptop and my GPU goes into wimpy mode, this CPU burden cranks up to about 15% because my GPU and CPU take longer to render.

If you get frustrated with slow animations or high CPU utilization in your code, try optimizing with some VBOs or display lists. Or find a way to cut down on the huge mesh you made of your vertices.

Points of Interest or FAQ

Q: Why no Groups?
A: Not pertinent to part 2.

Q: Why do Windows leave invalidated regions in XP?
A: Invalidated region events are dodged like Keanu Reeves. Vista is the Windows of tomorrow, and its composite window layers need no such events. Either implement triple buffering, or don't move Windows in front of your ListView.

Q: Why do I see artifacts while I'm scrolling?
A: I was never able to remove these.

Q: Why are the icons offset when there is a horizontal scrollbar visible?
A: Another puzzle left to the reader.

Q: What about MONO?
A: Tao is fully compatible with Mono. I've tried it in Sabayon with limited success. The core code works, but getting a rendering context on an existing Form is beyond me right now. I've tried SDL which only seems to be able to create a separate window.

Q: What about DirectX?
A: Could be possible. If the video texture bug were ever fixed, it might be a nice option. For now, I prefer the potential Mono compatibility for the Mac/Linux community.

Q: Can other standard Controls be OpenGL-ized using this technique?
A: Probably, if you follow the constructor and init your RC right. I don't personally see the point in a 3D GridView unless a VP wants to throw $$$$ at it. Knock your socks off.

Q: Where is my stereoscopic ListView?
A: %) Tried it. Left myself cross-eyed for 3 days. At your own risk...

Q: Shell coding - is there a file browser that uses this?
A: Stay tuned for part 2.

History

The control has gone through a few revisions:

  • From rebuild every display list and texture at every refresh
  • To interrupt the refresh and invalidate WNDPROC messages to only draw if necessary
  • To timer-driven refresh delegate using no if branching
  • To fully optimized scene graph that changes only what needs to be changed

It is finally at a comfortable level of optimization for me.

Apologies

The Tao framework was registered in my GAC, but I did not include them in the original example source download. The zip has been updated to run on machines that do not have Tao installed.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here