Plugins can use OpenGL to draw directly inside X-Plane; this capability is almost fifteen years old and goes back to X-Plane 6. Plugin drawing typically falls into four categories:
- Drawing user interface (floating windows, HUD-like UI, map details, etc.).
- Drawing custom panel instruments (particularly 2-d glass displays).
- Drawing effects in 3-d (custom smoke or fire, or maybe diagram marks showing lift).
- Drawing solid stuff in 3-d (e.g. pushback trucks, parts of the airplane, etc.).
These first two use cases work pretty well; the third case works barely, and the last one is a bit of a train wreck.
Problems With Plugin Drawing
OpenGL an 3-d hardware was very different when Sandy and I created the original plugin SDK 1.0. Hardware had a fixed function pipeline built into the GPU itself, and your computer had one CPU core, so the issues I’m going to describe weren’t a problem back then. Here are some problems with plugin drawing:
- Increasing overhead just to draw. It used to be that we could just send a message to the plugin system at “good times” and let drawing happen. X-Plane now has to do a bunch of work to prepare OpenGL for plugin use; this synchronization work slows down X-Plane, and it’s getting worse as X-Plane’s internal design diverges from the canonical OpenGL 1.5 pipeline. (Example: we have to copy our matrices into the fixed function pipeline matrices before we call you, then copy them back if you call us with a drawing routine. Slow!)
- Plugins don’t have access to the fastest drawing paths. The less our drawing looks like OpenGL 1.5, the less efficient plugins are in comparison to X-Plane. This is getting worse over time too. (For example: if we want to render two VR eyes at once, we have to call your plugin twice, once for each one.)
- Plugins don’t have access to our lighting environment or G-Buffer formats, and thus can’t easily draw in a way that integrates with our 3-d world. This is getting worse as our rendering engine becomes more complicated.
So…this is not fantastic. The first two use cases (UI and panel) aren’t that badly affected because the two of them require only 2 or 3 call-outs to plugins, drawing in a very simple manner. The second two (3-d use cases) are quite problematic.
These costs are going to get worse when X-Plane moves to Metal and Vulkan drives; the cost of syncing over to OpenGL will be higher, plugins won’t even be drawing on the fastest APIs, and we may have to do some expensive texture synchronization.
Fixing The Problem
My goal isn’t to completely eliminate the costs of plugin drawing; rather just to minimize the costs by providing better alternatives for common tasks.
So in the long term, my plan is to add something to the SDK that we should have had for a while: the ability to create persistent object and effect systems in the X-Plane world.
Right now if you want to draw an object, you call XPLMDrawObjects – hopefully you’re doing this from just the right callbacks, but if you get this wrong, I forgive you; the rules for how to do this correctly in the v10 and v11 renderers are insanely complex, because the plugin API was invented fifteen years ago.
In the future, you will be able to simply create an object and X-Plane will draw it at all the right times, for any rendering mode, handling all of the special cases for HDR, shadows, reflections, you name it. You’ll just need to tell us when it moves. The same idea can be applied to particle systems for effects.
The one open issue with this scheme is how animation will work. Currently animations is handled by reading datarefs – if you want to animate multiple instances from a plugin, you have to figure out (inside your dataref handler!) which instance is being drawn and return the right animation values.
Not only is this a complicated mess, but it’s also slow; it requires us to call your dataref callback many times from inside the drawing loop, slowing everything down.
What I’d like to have in the future is some kind of ‘animation block’ where:
- Your plugin queries the object to find out which animation values it needs.
- Each time you update a particular instance of that object, you provide a packed block of the values of all of those animations.
- We use the block directly and never have to call datarefs.
This technique isn’t just faster, it also is easier to make multi-core and will play nicely when we can instance animation in the future.
The time frame for this is “during the v11 run hopefully” – that is, a new object API is not going to be in 11.0, and I can’t guarantee when it will be. I expect to support the old way (XPLMDrawObject and drawing hooks) at least through the entire v11 run, if not longer; our expected failure mode is ‘if you do it the old way, the sim might be really slow’. The new APIs will be designed to be completely friendly with multi-monitor, VR, Vulcan, and multicore.