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
Font, etc. are covered. Some options like
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
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
Font properties and also is very generous to other processes keeping CPU activity to a minimum.
private void Teapot_Background(long frame)
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);
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
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
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.
private void boeroLV_AfterInitRC()
Gl.glLineWidth((float)Height / 100);
Gl.glColor4f(1, 1f, 0, 0.1f);
Gl.glLineWidth((float)Width / 100);
Gl.glColor4f(1, 0, 1, 0.2f);
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.
int weatherTexture = 0x200001;
XmlDocument weatherMilwaukee = new XmlDocument();
foreach (XmlNode weather in weatherMilwaukee.GetElementsByTagName("forecast_conditions"))
foreach (XmlElement condition in weather)
Bitmap bm = new Bitmap(weaControl.Width, weaControl.Height);
weaControl.BackColor = boeroLV.BackColor;
There are a few other properties that have long been missing from
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
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:
uniform sampler2DRect sampler0;
uniform vec4 fc;
vec4 texture = texture2DRect(sampler0, gl_TexCoord.st);
gl_FragColor = mix(gl_TextureEnvColor, mix(fc, texture, 0.5), texture.a);
Text (Label) Shader
uniform sampler2DRect sampler0;
uniform vec4 bc, fc;
vec4 texture = texture2DRect(sampler0, gl_TexCoord.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.
This makes a big difference in today's modern UI. Translucency is everything!
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
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
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.
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.
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.