Frassle's picture

Wgl (and other) bindings

I'm doing some investigative work into how to do CIL rewriting better. As part of that I'm seeing how difficult it is to remove the OpenTK rewrite project and instead spit out the CIL opcodes in the Bind step using Silk. As far as I can tell the Bind project only spits out source for GL/ES, while Rewrite rewrites the CIL of any function marked with AutoGenerated such as the ones in Wgl and Glx. As far as I can tell the source code for these functions isn't auto-generated (at least not by Bind)? If this is the case then it's quite simple to fill in the function bodies by hand with the relevant Silk calls but I don't want to do that if they're just going to be overwritten by an auto generated step later down the line.

----
As an aside, the actual investigation is going quite well. Most functions are working and the Silk rewriter (Weave), while not blazing fast, isn't too slow and probably has lots of room for performance improvements. Still bugs to iron out with both spitting out the correct sequence of Silk calls in OpenTK.Bind and fixing things in Silk but it's going well! This should make it easier to both understand what the bindings are doing and to also make use of things like generic pointers in one off code.

Here's an example of what the GL bindings look like with Silk:

        public static void Clear(OpenTK.Graphics.OpenGL4.ClearBufferMask mask)
        {
            unsafe
            {
                #if DEBUG
                using (new ErrorHelper(GraphicsContext.CurrentContext))
                {
                #endif
                    Silk.Cil.Ldarg(0);
                    Silk.Cil.Load(EntryPoints);
                    Silk.Cil.Ldc_I4(47);
                    Silk.Cil.Ldelem_I();
                    Silk.Cil.Calli(System.Runtime.InteropServices.CallingConvention.StdCall, typeof(void), typeof(OpenTK.Graphics.OpenGL4.ClearBufferMask));
                #if DEBUG
                }
                #endif
                Silk.Cil.Ret();
                throw new InvalidProgramException();
            }
        }

The exception is an ugly hack and gets left in the final CIL at the moment, but will eventually be replaced with something cleaner.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

Wgl and Glx were indeed autogenerated until OpenTK 1.1.2, using the old-style Marshal.GetDelegateForFunctionPointer approach. In 1.1.2, I rewrote them by hand, removing any unused functions (around 99% of them) along with the final traces of the old extension loading mechanism. This was part of the effort to improve startup times and reduce memory usage.

So yeah, you'd have to write the necessary instructions by hand in those cases. They won't be overwritten and there are no plans to offer full-blown Wgl/Glx bindings as part of OpenTK - Bind is currently used for GL, ES, CL, Wayland and Mantle (heh). In the future, I'd also like to autogenerate AL, but that will take some work (OpenAL does not yet have an xml registry.)

Regarding Silk, I really like the idea of having the instructions visible and debuggable. Right now, debugging the bindings requires a round trip through a decompiler which is rather annoying.

How do you declare pinned locals in Silk?

Frassle's picture

Currently using a DeclareLocal function, it's one of the areas of the API I'm less happy with but it seems to work.

 
Silk.Cil.DeclareLocal("int[] pinned", "a"); // or "int& pinned", I'm double checking the specs and implementation on what exactly is allowed with pinned variables.
Silk.Cil.Load(some_int_array);
Silk.Cil.StoreByName("a"); // array is now pinned

"T pinned" copies the CIL assembly syntax.

I've also considered using a fake Pinned<T> type that is replaced with "T pinned", and a MarkPinned<T>(T variable) which would pin "variable" if it was a local var (and error otherwise) and I'm not sure which is easiest and cleanest yet. I'll probably also have a helper function "IntPtr Pin<T>(T value)" for when a raw intptr pointer suffices.

Frassle's picture

So Silk is feature complete enough to be able to generate nearly all the bindings for OpenTK. I've been testing that all the examples work but that only tests so much of the API and I'm only testing on Windows. But given that all the methods follow common templates all the generated methods should be fine. I've only hand written some of the Wgl functions so most of Wgl and the other platforms will just throw NotImplementedException, but again they all follow common patterns so it shouldn't be hard to fill them in.

You can test the bindings here and see the main Silk repo here.

Roadmap from here:

  1. Fix the build to not rebuild OpenTK and rerun Weave everytime
  2. Improve performance to run Weave faster
  3. Finish the remaining bindings in OpenTK
  4. Finish the stack analyzer in Silk
  5. ...
Frassle's picture

So steps 1,2,3,4 are done.

There are things in Silk that I'm not totally happy with, DeclareVariable being one, and having to fake a throw at the end. But it works, it builds and runs OpenTK and even fixes a bug that's present in the standard bind/rewrite (https://github.com/opentk/opentk/issues/144).

I'm calling this Silk 1.0 and it's on NuGet and Github.

Roadmap from here:

  1. Reduce the number of NOP instructions generated. Currently instructions are just replaced with NOP rather than being removed.
  2. Look at replacing the use of strings to define types with generic wrappers instead (Pointer, Pinned etc). May keep both ways.
  3. Finish the InfoOf part of Silk. Will allow InfoOf.Field("Struct.Field") to return a runtime FieldInfo.
  4. Finish copying the CLI documentation into C# xml comments. Very useful for seeing how the instruction changes the stack.

Now OpenTK is a great test case for Silk, but is there any interest in replacing the current system with Silk?

the Fiddler's picture

In short, yes, I'd love to replace Generator.Rewrite with a more maintainable Silk version. I'll be testing the Silk-ified version of OpenTK over the weekend.

My main concerns are as follows:

  1. Add unit tests to automate testing of the generated bindings (string string[] StringBuilder bool bool[] float float[] float[,] float[,,] ref float out float float* T T[] T[,] T[,,] ref T IntPtr). https://github.com/opentk/opentk/pull/142 adds one more that was not previously available: ref string
  2. Ensure that the resulting assembly size is comparable. Generator.Rewrite strips away a number of things from the final assembly, such as [Autogenerated] and [Slot] attributes, which reduces the size of the binary considerably.
  3. Ensure that the resulting IL performs comparably.

Compilation speed is a secondary concern. We can trick Visual Studio to avoid unnecessary recompilations (i.e. compile OpenTK.dll in an intermediate path and copy it to the final path in the rewrite stage), but anything to improve the current times would be useful. I haven't spent any time looking into that though.

Frassle's picture
Frassle wrote:

Reduce the number of NOP instructions generated. Currently instructions are just replaced with NOP rather than being removed.

This now done, instructions get properly removed not just blanked out by being swapped with NOPs.

Frassle wrote:

Look at replacing the use of strings to define types with generic wrappers instead (Pointer, Pinned etc). May keep both ways.

Not sure about this. It's much easier to specify fields and methods with strings and so I need all the parsing functionality for that and may as well allow it to be used for specifying types as well. I might add Pointer etc still but it's moved down my list.

Frassle wrote:

Finish the InfoOf part of Silk. Will allow InfoOf.Field("Struct.Field") to return a runtime FieldInfo.

Kinda working, the instruction sequence generation is done but there's still issues with parsing the specifiers.

Frassle wrote:

Finish copying the CLI documentation into C# xml comments. Very useful for seeing how the instruction changes the stack.

Copied all the short documentation in. I'll also add long documentation to the xml remarks node at some point.

the Fiddler wrote:

Add unit tests to automate testing of the generated bindings (string string[] StringBuilder bool bool[] float float[] float[,] float[,,] ref float out float float* T T[] T[,] T[,,] ref T IntPtr). https://github.com/opentk/opentk/pull/142 adds one more that was not previously available: ref string

Hmm I guess that should be spat out by Bind... by adding a test api xml?

the Fiddler wrote:

Ensure that the resulting assembly size is comparable. Generator.Rewrite strips away a number of things from the final assembly, such as [Autogenerated] and [Slot] attributes, which reduces the size of the binary considerably.

Silk is currently much larger, that will be partly as the new removing NOP code isn't in yet and secondly as I'm still writing AutoGenerated attributes. I expect it will be the same size at the end. (The disadvantage here is that you won't see AutoGenerated attributes on the methods in the source as Silk can't (currently, and no plans for) remove a user defined attribute so we'll just have to not write them to the source.

the Fiddler wrote:

Ensure that the resulting IL performs comparably.

I'm aiming to get the generated IL to be the same. One thing that will be different is that Silk uses try/catch statements when calling the helper methods (allocate strings etc), while currently OpenTK doesn't. That's gonna be a small hit, but I think its only missing from OpenTK due to the difficulty of writing try catch statements with Cecil and is in fact a bug fix. They can always be taken out if deemed too bad for performance.

As always, roadmap:

  1. Vastly improve parsing of type/method/field references, currently major issues with generics.
  2. Switch to using Cecil Resolve functionality instead of looking up types ourselves.
  3. Improve diagnostics, see if possible to make use of PDB/MDB files that Cecil has open.
  4. Get compiled size of OpenTK with Silk closer to current compiled size
kevink's picture

Maybe a silly question:

Wouldn´t be nice to have also the platform specific things in the same manner?

Wouldn´t there be a performance hit?

Are there any problems I have overlooked?

I think It would be possible to write some XML-files like the khronos
specifications with the important API enums, functions.

the Fiddler's picture

@kevink: it is certainly possible to autogenerate platform bindings for EGL, GLX and WGL (OpenTK indeed used autogenerated bindings until roughly two months ago.) The necessary xml files can be found in the khronos registry - simply point Generator.Convert to process wgl.xml and glx.xml and then run Generator.Bind on the processed files. Some amount of bugfixing is to be expected, but nothing too terrible.

However, there are a few questions that would need satisfactory answers first:

  1. What is the use case for exposing platform APIs?
  2. Could we extend our cross-platform APIs instead?
  3. Do the benefits outweigh the costs of maintaining three additional platform-specific APIs? How many users would use these bindings in practice?
  4. Is there someone willing to spend the effort in implementing, testing and maintaining these APIs?

From my point of view, adding WGL, GLX and EGL to the public API would add a huge maintenance burden and reduce portability, for a very unclear benefit.

@Frassle:

Quote:

Silk is currently much larger, that will be partly as the new removing NOP code isn't in yet and secondly as I'm still writing AutoGenerated attributes. I expect it will be the same size at the end. (The disadvantage here is that you won't see AutoGenerated attributes on the methods in the source as Silk can't (currently, and no plans for) remove a user defined attribute so we'll just have to not write them to the source.

Right now, [Autogenerated] attributes are used by Generator.Rewrite to connect the public API with the correct function names/pointers (they do not exist in the final binary.) Using Silk, Generator.Bind can do this directly during code generation and [Autogenerated] has no reason to exist.

Quote:

I'm aiming to get the generated IL to be the same. One thing that will be different is that Silk uses try/catch statements when calling the helper methods (allocate strings etc), while currently OpenTK doesn't. That's gonna be a small hit, but I think its only missing from OpenTK due to the difficulty of writing try catch statements with Cecil and is in fact a bug fix. They can always be taken out if deemed too bad for performance.

You are right: I did not add try-catch statements because they are a huge pain in Cecil. Their lack is not that much of an issue, considering that a crash in unmanaged code or a memory allocation failure is - most likely - fatal, but it would still be nice to have them back.

Quote:

Hmm I guess that should be spat out by Bind... by adding a test api xml?

That's possible but the real question is how to test them in the first place. Two options: (a) write a tiny native test library and check that the bindings return the expected results; (b) pick a number of OpenGL functions that are a representative test and write an OpenGL program to test them. Given how annoying it is to compile native code in a cross-platform manner, I think I'll go for (b).

Frassle's picture
Frassle wrote:
  1. Vastly improve parsing of type/method/field references, currently major issues with generics.
  2. Switch to using Cecil Resolve functionality instead of looking up types ourselves.
  3. Improve diagnostics, see if possible to make use of PDB/MDB files that Cecil has open.
  4. Get compiled size of OpenTK with Silk closer to current compiled size

I've replaced the type/field/method lookup code with a completely new solution. Needs performance tuning again but handles generics and more complex type signatures correctly now.

Cecil resolve didn't seem to cut the bill, made the type specifier too complex for the Silk user so that's been dropped.

Diagnostics hasn't been looked into, currently it just prints an exception message if types/fields/etc can't be found.

Compiled size of OpenTK is getting closer to the target (the current normal OpenTK build size) but a lot of methods are declared AutoGenerated but don't have their source auto generated they were just picked up by the old CIL rewrite pass, mostly compatibility and obsolete methods. Bit awkward as it means either keep both rewriters, writing those methods by hand, dropping support for them, or getting them written by the binding generator. None of those options are ideal but I think for purpose of getting Silk to work writing them by hand might be the easiest.

kevink wrote:

Maybe a silly question:

Wouldn´t be nice to have also the platform specific things in the same manner?

Wouldn´t there be a performance hit?

Are there any problems I have overlooked?

I think It would be possible to write some XML-files like the khronos
specifications with the important API enums, functions.

The platform specific stuff which needs to call functions that can't be found using DllImport are being rewritten to use Silk. As Fiddler said they could be written out by the binding generator but I don't think it's worth it at the moment.

the Fiddler wrote:
Quote:

Hmm I guess that should be spat out by Bind... by adding a test api xml?

That's possible but the real question is how to test them in the first place. Two options: (a) write a tiny native test library and check that the bindings return the expected results; (b) pick a number of OpenGL functions that are a representative test and write an OpenGL program to test them. Given how annoying it is to compile native code in a cross-platform manner, I think I'll go for (b).

Should be fine to write a test like this in a separate branch from all the Silk code. Are you still happy with the idea of going with NUnit for automated testing?