Kaffeeklatsch teaser #3
Thursday, November 8th, 2007Over two nights of hacking I implemented a movement filter that’s compatible with the AVS Dynamic Movement effect.
Over two nights of hacking I implemented a movement filter that’s compatible with the AVS Dynamic Movement effect.
Google Video was giving me grief, so I posted it on YouTube this time:
Lately I’ve taken to hacking Affe quite a bit. Just a quick list of a few changes:
null is now a keyword.The last item could use some explanation. It’s a size optimization really, and probably unneeded, but I’ve always thought something like this would be neat for mcs to have. This implementation is my proof-of-concept.
There are a lot of things that require extra locals to be defined. For example, if some method returns a value type and you’re directly invoking on it (for example, somestring.Length.ToString()) then there are two IL sequences that could be used. Assuming the int is on the top of the stack, the first is box System.Int32; callvirt instance string object::ToString();. This is what seems intuitive, but there’s a reason mcs does not do this. Boxing creates a new object, and that object must now be garbage collected. It’s much more efficient to use the stack for value types. So instead of boxing, a new local is declared, say “temp”, and this IL is used: stloc temp; ldloca temp; call instance string int32::ToString();. No boxed object, no virtual method resolution, no additional strain on the GC, no wasted heap. But now we have another local.
What if you hard-coded ten such calls? That’s right, you get ten locals. The JIT may be smart enough to optimize them out, but the IL is still going to be bloated. That’s where my local bag comes in. When such an expression is compiled in Affe, it asks the compiler state object for a local from the bag, for temporary use. The state will look through a list of unused locals it has and will return the first it finds of the same type, but will keep it in the unused list (since the callee said it’s temporary). If none can be found then it will create one and add it to the list. (If the local was requested for permanent use then it would be removed from the list if it was there, or if it wasn’t then it won’t be added to the list.)
The upshot of this is that if you make 10 hardcoded invocations against a value type that’s being returned from somewhere (or even a constant) they will all use the same local, assuming they’re all the same value type.
Now consider the case where you have this code in C#: if (foo) { object o; ... } else { object o2; ... }. This is admittedly contrived, but it does the job. We have two scopes with a variable of the same type. With mcs you get two locals. With Affe you get one.
To maintain scoped variables, the Affe compiler state maintains a stack of symbol tables. Each block has its own table that is pushed prior to analysis, then popped, and again for the compile pass. When a table is pushed, the state will check for symbols that correspond to locals and will remove them from the local bag if present. Then when popped, all the locals are added to the bag. So in the example above, the “if” block’s table is pushed during analysis and a new local symbol is added. Then it’s popped, and the local gets put in the bag. When the “else” block’s analysis is run and it asks for a local of type object, the one from the bag is handed to it.
It’s not a terribly major optimization, and I expect in Affe’s case it actually takes more time during compilation than it saves during runtime, but I’m interested if this would be interesting for the mcs hackers. I imagine it would eliminate a fair amount of IL bloat.
I have finished removing Lua from Kaffeeklatsch and have enhanced Affe with the ability to bind field, property, and method calls at runtime. I noticed this gap when rewriting some of the key binding scripts; where I could just set a field before, my compiler threw an exception since it could not find the field on the IEffect interface. After wrestling with this for a while I finally came up with another hackish solution: a new operator.
The period operator binds during compilation, and the new dollar sign operator binds during runtime. Instead of the standard target-arguments-call emission, it emits a call to a static method declared inside Affe that takes the target object, the member name, and an object[] containing the call parameters. This method performs logic similar to a compiler-bound call (in fact, basically identical logic) except using the target’s runtime type. One annoying pitfall of this technique is that since the return type cannot be known at compile time, a late-bound invoke necessarily returns an object. And, of course, to unbox a primitive you must perform an exact cast.
Since I didn’t unbox automatically I added a parenthetical cast operator to Affe so that late-bound return values can be unboxed or converted. (Note that boxing is automatic.)
So where something like Effect("fountain").Rotate = true; would make Affe choke, this can now be written as Effect("fountain")$Rotate = true;.
I am toying with the idea of flagging certain types to be intelligently bound. For example, if IEffect was so flagged, the compiler would try resolving the call and if it could not bind it, would convert the expression to a late-bound call. This would give all the speed of compile-time binding where possible, and fall back on runtime binding when that failed. Since I’m trying to make Affe basically a fast scripting language for .NET, this is optimal. It may make debugging harder since errors are not caught at compile time, but you’d have that problem with the $ operator anyway.
I got Kaffeeklatsch back out of the toy box the other day and started integrating the Affe compiler into it. The goal is to remove Lua entirely, as it’s an unmanaged dependency and has been giving some odd errors as of late, crashing mono after a few seconds.
The SuperScope effect has been completely converted over from Lua to Affe. (Keybindings and the Custom effect are next on the list.) To celebrate this achievement I have posted a teaser video on Google Video. The quality is pretty horrible and there is no audio, but you get the general idea:
There are a few effects you are seeing here:
Every effect is a managed object; SuperScope is the only one in this video that uses user-supplied scripts; the others can have other parameters tweaked from the Gtk# preset designer.
I’ve finally put Kaffeeklatsch in a Subversion repository, and a public one at that! Simply check out from https://layla.chrishowie.com/svn/kaffeeklatsch and build… well the build process isn’t that simple, but oh well. You will need Tao, the Lua 5.0 library and development headers (check your distro’s packages), and Mono. Having MonoDevelop around will make the build process a bit easier.
Happy new year, and happy visualizing!
I’ve been making some small progress on Kaffeeklatsch, though most of it is behind the scenes stuff. For example, IEffect now extends IDisposable, and the frontend will dispose effects when removing them. This could be used, for example, in plugins that create OpenGL display lists and want to properly clean up after themselves when destroyed.
At the moment, the core effect library is lacking a dynamic movement plugin, which should deform the color buffer according to a user-supplied formula. I heard that Winamp AVS was made open-source some time ago, so it may be a simple job of porting that effect to C#. However, as this kind of math is not really my area of expertise, I would love for another FOSS developer to join the project and help out there. Post a comment if you’re interested.
It’s coming together slowly. Recently Kaffeeklatsch has been enhanced with Lua, which is used for key bindings and in several effects. For example, there is a “Custom” effect that simply runs a Lua script each frame. The script gets full access to OpenGL by default, but can access any loaded .NET classes. (This could be a security problem in the future, but it’s not a major concern right now.) Key bindings simply run a Lua script that is given access to the entire effect list, allowing bindings to do anything — manipulate properties and fields, and invoke methods. So a key binding can toggle multiple effects, change colors, you name it. You can also bind a different script to a key press and a key release, allowing for tap/hit effects.
Also available is an effect called SuperScope, which users of Winamp’s AVS will immediately recognize. The implementation uses Lua, and is almost completely compatible with Winamp’s implementation. With a little work, it will be 100% compatible. At first it was unbearably slow. More than 128 points with sync at 75hz (basically 128 * 75 = 9,600 points per second) would cause a significant drop in framerate. This was due to the implementation: the “point” script was compiled once per frame, and invoked n times by C#, the relevant global variables extracted, and the vertex drawn with OpenGL, through Tao. For each point. Whew.
I decided this needed to be fixed. The solution is not pretty, but it’s very effective. I moved the point loop into Lua, and wrote a small glue library in C that exposed glColor4d and glVertex2d to Lua, effectively bypassing a .NET-to-Lua call for every point — now only once per frame. I can now get up to 2,048 vertices, 75 times per second, before performance suffers.
A beta release may be coming soon. But first I need to find the source of a bug that manages to terminate mono with no stack trace after a few minutes of rendering.
Time bombs suck.
And so does Kaffeeklatsch. Now, anyway.
First, I added preset saving and loading a few days ago but never wrote about it. Since then I gave the design of this project some serious thought, and moved some classes around into strictly defined layers:
I moved as much common functionality as possible into the platform layer. Preset serialization, which was originally in the designer, was moved there to ensure a common preset format across different software that uses the platform. The controller was also in the designer — I’m still not sure why I put it there to start with.
There is still one loose end: media player connectivity. Currently that is split between the platform and designer layers. It’ll probably all be moved into the platform, with an extension mechanism for talking to different media players.
Kaffeeklatsch is quickly becoming exactly what I wanted it to be: cross platform, media player neutral, human controlled visualization. Huzzah!
Just a day later and all Kaffeeklatsch needs now is key bindings, then we’re ready for “production,” whatever that is.
This screenshot highlights the two biggest additions I coded today: the automatic generation of option pages for each effect (see the sources displayed!) and the gathering of scope data from XMMS. A small stub plugin sends the waveform and spectrum data to a UDP port on localhost, which Kaffeeklatsch picks up and passes to the effect renderers. Kludges rock.
If I didn’t mention it, this project will be released under the GPL. At least when I decide it’s not too embarrasing to release.
The sources displayed in the image are © 2006 Chris Howie and are available under the terms of the GNU General Public License version 2, or (at your option) any later version.