Direct3D 11 Binding

Started by Kai, January 17, 2015, 00:16:28

Previous topic - Next topic

Kai

I have just recently begun implementing the humble start of a Direct3D 11 binding for LWJGL 3.

This is going to be a bit more involving than the OpenGL binding, because Direct3D is an object-oriented API and meant to be used via C++.
Therefore, I am planning to map the most necessary C++ classes in Direct3D 11 to Java interfaces or classes.
For example, ID3D11Device will become a real Java interface with virtual methods.
Each Java class and interface shall have the same methods that you can invoke on them like the virtual functions in its Direct3D C++ class.

Regarding Win32 API for window creation, LWJGL 3 already has everything for that. So, window creation code is going to be done in Java with the Win32 API functions, like in WindowsDisplay and WGLDemo.
Just like with OpenGL, the goal for the D3D binding is to make a Direct3D application written in Java with LWJGL 3 feel like it has been written in C++. This allows for easy transition between the two languages and to easily apply examples and tutorials to LWJGL 3.

I am not planning on using the Kotlin generator approach or SWIG during the very early development stages.
That's because I want to get a feeling for what it looks like mapping C++ to Java, and how an "optimal" generator and template would look like to support that in a good way.

Luckily, there are very few things in C++ that actually need mapping to Java:

1. Out parameters for class and struct pointers
Just like how LWJGL 3 does it, the d3d11 binding will use PointerBuffer for that.

2. Value structs as input parameters
Here, the struct will be represented by a Java class that will contain a method to write it into a ByteBuffer.
That ByteBuffer is then handed to the JNI function and cast there to the appropriate struct type.

3. Virtual functions on objects
These will get the "this" pointer as long address. Each Java instance of a d3d11 class will have a "long address" field which holds the address at which d3d allocated the instance in memory.
In the JNI function, this address will then be reinterpreted as a pointer to an instance of the corresponding C++ class.

4. Enums and flags
Enums that are not flags (cannot be bitwise OR'ed) will be represented as Java enums for typesafety. Since Direct3D is more typesafe than OpenGL by design we can gain something from it here.
Flags that can be bitwise OR'ed, like DXGI_SWAP_CHAIN_FLAG, will get a single Java interface with int constants.

All in all Direct3D 11 is a pretty clean API. Hopefully will be fun to get something started with Java and LWJGL 3 in little time.

Regarding feature-set and milestones, I will be going along the lines provided by the MSDN Direct3D Tutorial Win32 Sample.

I'll let you know when I have Tutorial01 working with LWJGL 3.

Cheers,
Kai

Kai

The Tutorial01 application of the Direct3D Tutorial Win32 Sample is now ported to LWJGL 3 with Direct3D 11!

You can see the solution here: https://github.com/httpdigest/lwjgl3-d3d11/blob/master/test/org/lwjgl/d3d11/tutorial01/Tutorial01.java

Design

The first design looks like the following:
- The inheritance hierarchy of the Direct3D 11 classes is directly reflected in the Java classes
- The Direct3D 11 interfaces, those beginning with a capital 'I' are also interfaces in Java
- Each Java interface declares the method that were also introduced/declared in a particular D3D11 interface
- The interfaces are implemented via classes in the "impl" package
- Those classes contain the implementations of the interface methods
- The implementation class also extends the implementation class of the parent interface (this allows to inherit all implemented methods of parent COM and D3D11 interfaces)
- Each implementation class declares the native methods with the same naming convention as LWJGL 3 uses for OpenGL functions, starting with 'n'
- Those native methods are public and can be invoked without going through the interface method implementations

Typesafety

The D3D11 binding introduces another level of abstraction allowing to have typed "Out" parameters for methods. Methods which output a defined type via out-parameter will get an Out<T> parameter with T being that type.

Other things include arrays, such as arrays of structs, which will be converted to ByteBuffer filled with the contents of these structs.

Wrapper objects

All objects allocated by D3D11 will be typesafely wrapped in a Java object. Each wrapper class has a "long ptr" field containing the address of the wrapped native object/struct.

COM-Lookup

Direct3D 11 makes heavy use of COM methods to lookup registered instances of particular classes by GUID. Since we want to have type-safety but cannot know what object of what concrete type a generic COM-"Lookup" method returns, the user is forced to specify the expected type via a Class<T> parameter.

An example of this is: IUnknown.QueryInterface. This returns a generic void**.
In order to create a view on a typed interface, we need to instantiate a wrapper object with the address. Currently, this is done via the Java Reflection API to invoke the constructor taking a long parameter of the user-specified Class<T> object.
Later this can be replaced by runtime-codegeneration or even JDK7+ invokedynamic with inline caching for constant callsites.

...

SHC

I would have appreciated you if this is JGO Kai. You are doing a nice work. I'm not on windows, so I can't test it right now, but am interested in running this. I'll be installing Windows soon. It's really cool.

Kai

Hey Sri Harsha,

many thanks for your appreciation!  :)
I want LWJGL to be the definite solution for doing Direct3D with Java. At least to my knowledge there currently is no solution for that.

So I will continue to manually port the next few tutorials to LWJGL and once the design is clear and feels good the next step is to develop the Kotlin generator to support that kind of C++ to Java translation, or make a complete new generator with runtime acceleration support via Java 7+ features.

The current implementation has no memory overhead and GC pressure for your in-game-loop actions and few allocations for your initial resource allocations.
So, no GC pressure for hotspots is definitely a design goal!

Thank you for your post again!

Cheers,
Kai

SHC

I once used DirectX from C# using SharpDX, but then moved to opengl since it was more easier for me, java is my favorite language. But both libraries are really cool, I want to play with both of them.

Kai

Doing more search on DirectX with Java, there is a stackoverflow thread mentioning the bitbucket project directx-for-java.
Will have a look at it and their concepts of doing it.

EDIT: Ah okay, they are using bridj, and as such are using more generic translations to C++/C data types, such as Pointer and not "long" addresses and ByteBuffer directly, like LWJGL3.
Also I guess, in order to make native methods without actually writing native code possible, bridj has to generate native code on the fly and use JNI's env->RegisterNatives to bind them to the JNI. They say it is about 10% slower than direct JNI.

Kai

Tutorial02 is now ported to LWJGL3!!

And we can see a yellow triangle now. :)

It features compiling and creating vertex and pixel shaders, setting vertex buffers and primitive topologies and draw command.

quew8

Well speaking on a principals level, OpenGL is obviously more in keeping with Java's cross platform nature and I think it is a much better API which I don't doubt will one day entirely replace Direct3D. but saying that, I am very interested in trying this out and will do so once I have more free time.

But most importantly, well done for getting as far as you have (in 2 days, wow) and for the colossal undertaking. Nice one.

Kai

Quote from: quew8 on January 19, 2015, 11:28:11
...which I don't doubt will one day entirely replace Direct3D.
I hope you're right. ;)
But it's always good having one more notch on your belt. :)
And thank you for your appreciation!

Kai

After having played with the super-minimal Direct3D 11 binding I already have, some design-related things became apparent now, that should be resolved before going on and writing a generator for the whole binding:

Layered Library

Because Direct3D 11 is an object-oriented API and not just a procedural C-API like OpenGL, it is convenient to actually use it in an object-oriented way. Therefore, the current state of the binding features classes with the same inheritance hierarchy as do the Direct3D 11 API classes.
Nevertheless, it puts a burden on people wanting to use the code by having to deal with other people's (namely me :) written classes. Because I cannot estimate any possible use scenario that people would find the Direct3D binding useful, it is better to simply just provide a lowest-level JNI-binding library without the Direct3D/COM-class hierarchy.

That means, the Direct3D binding will be two layers:

First:
Low-level JNI-functions for the virtual methods of all Direct3D classes. Just like LWJGL does it with OpenGL functions, those Direct3D methods will be declared in a class named after the Direct3D/COM interface/class they are declared in.
Those JNI functions will simply have raw pointers and buffers as parameters, just like how the OpenGL binding does it.

And second:
An optional-to-use object-oriented class hierarchy with type-safe convenience methods on top of the simple JNI-functions of the first layer. No one needs to use that second layer. Everyone will be free to write their own class hierarchy on top of the first layer where they see fits.

But what's with all those structs?
Having thought about it for a while I find it better to put the Java definitions of those structs together with a mapping strategy (how do we get those structs into native memory for Direct3D to use) into the second layer.
The first layer will just work with already-in-ByteBuffer structs. So, if you decide to just use the first layer, you are going to have some work putting those structs in native memory.
You can however also provide some sort of generator for that. But anyways, the second layer will contain those structs if you need  a simple solution.
Also, you would be able to also only just use the struct mapping without the actual Direct3D/COM class hierarchy also provided by the second layer.

So, it feels like there should really be three layers: JNI-functions, struct mapping and Direct3D/COM inheritance hierarchy.

So maybe a good step will be to split the already existing lwjgl-d3d11 project into three projects. Or just have different packages for the three layers in the existing project. I don't know. Let's see what feels better. In the end, it will just be a deployment issue to hand three different jars to the end-users.

Cheers,
Kai

spasi

It sounds like the low-level API and structs should already by supported by the LWJGL bindings generator. Is there some functionality you're missing?

The struct classes that LWJGL currently generates, come with both a static API (that can be used to safely read/write ByteBuffers) and an OO-style API (a simple instance that wraps a ByteBuffer). Note that both styles are in a prototype stage and there hasn't been much effort to make them nicer. It hasn't been a priority, but I will spend more time on it and I'm open to suggestions and ideas.

Kai

I haven't used the LWJGL bindings generator for those low-level JNI functions currently. I don't know whether something might be missing. I definitely have to make use of it and learn it more, though. :)

Regarding the structs, I would very much like the Kotlin generator to provide some mechanism for memory management when dealing with pointers to other structs and non-primitive types (such as strings) as struct members.

What I am thinking of is that LWJGL also provides runtime support classes for taking a struct with typed members, like java.lang.String and as it "serializes" the members into a ByteBuffer, it calling back to the user to provide ByteBuffer space for the struct members.

But that is definitely "one level above" the simple "just handle pointers as ByteBuffer and void** as PointerBuffer".

I am currently trying to do exactly that with the "StructUtils" class in the d3d11 binding. I imagine having such a kind of runtime support class for "serializing" a deep struct with pointers into ByteBuffer(s).
But currently, also that StructUtils class does not feature "following" String or array members and serializing them into ByteBuffer(s). And also the StructUtils class has some deep knowledge of how struct members are actually packed in memory by msvc (or actually as defined by the application binary interface of Windows on x86 and ia64, so that should stay constant) depending on the member size and offset.
That seemed to be the best option for me at the moment I needed it. Can also do something entirely different, though.

mudlee

Just a humble question here: will there be DX wrapper in lwjgl? I found no other newer threa and I didn't want to create a new one.