kotlin-graphics / glm

jvm glm
122 stars 19 forks source link

Multiplatform? #6

Open Dominaezzz opened 5 years ago

Dominaezzz commented 5 years ago

I want to use this library in a multiplatform application. Since the bulk of this library is written in pure Kotlin, going multiplatform would be fairly trivial. The only possible issue I can think of is that JitPack may not work with it, breaking current users. Would need to setup build for all platforms to publish to Bintray or Maven central.

elect86 commented 5 years ago

With pleasure, however JitPack is enough for me at the moment

I know Bintray and Maven require going throw so much pain that makes me giving up.

But if you want to try, you are very welcome.. I sent you an invite :)

Dominaezzz commented 5 years ago

Ah sweet, I'll give it a try. I also just noticed it has a dependency on kotlin-unsigned, which will have to go multi-platform too. Unless you're thinking of migrating to kotlin's unsigned types.

Wasabi375 commented 5 years ago

There is another problem you have to solve first. I think the way we handle native memory is dependent on lwjgl or at least dependent on java.nio. I don't think there is a multiplatform version of any of those. I think it should be possible to move all those dependencies to the jvm version, but I'm not sure how much work this is actually.

Dominaezzz commented 5 years ago

Well, I just did a search for ByteBuffer and it looks like there are a couple options. Migrate them to use kotlinx.io.core.IoBuffer which is multi-platform (except for android native), abstract away the JVM specific bits into a separate jvm "extension" library or only implement the java.nio related parts in the JVM source set.

How much do you care about breaking changes?

Wasabi375 commented 5 years ago

I personally don't care. If we go multiplatform IoBuffer seems like the best choice for the future even though it's still experimental. As for breaking changes, I don't think they will be any problem.

elect86 commented 5 years ago

Oh man, I lost the reply mail..

anyway @Dominaezzz, the limitation with the stdlib unsigned is that they dont extend Number because inline classes cant extend classes, I explicitely asked to change Number to interface, but my request was turned down because it didnt seem like an issue for them

Extending Number is cool because you can pass it as generic and then converting to the type you want.

We may discuss if this is actually useful also for unsigned.. maybe we could switch to stdlib unsigned and make unsigned vectors somehow special compared to the others.

Regarding breaking changes, I personally repute that if the change makes sense, then we should just go for it

Although I'd pay attention for any regression performance-wise

elect86 commented 4 years ago

any news, @Dominaezzz ?

Dominaezzz commented 4 years ago

I made a branch with some changes. Have you looked at it?

Dominaezzz commented 4 years ago

The current code is a bit of a tangle of interfaces, which is quite bothersome to make multiplatform. Would be easier to wait for io-2 or make the library more kotlin "friendly" (less like the C api). The former being easier.

elect86 commented 4 years ago

I made a branch with some changes. Have you looked at it?

Yep, they are only confined to gradle or?

The current code is a bit of a tangle of interfaces, which is quite bothersome to make multiplatform. Would be easier to wait for io-2 or make the library more kotlin "friendly" (less like the C api). The former being easier.

I'm totally available to change the current structure if it makes sense.

By io-2 do you mean kotlin.io? is there already an ETA?

Dominaezzz commented 4 years ago

Yes, the new revamped kotlinx-io. It should be released sometime around Kotlin 1.4 release.

Oh, maybe I didn't push. Structural changes would be required, it's been a while so I don't remember what exactly needed to change. I just remember pulling interfaces into common code required pulling so many other things at once.

Also, dependencies would also need to be made multiplatform. unsigned won't work well with Kotlin native, there would be a fair bit of copying involved.

It's a bit of work.

elect86 commented 4 years ago

Yes, the new revamped kotlinx-io. It should be released sometime around Kotlin 1.4 release.

Where is possible to read something about that?

Oh, maybe I didn't push. Structural changes would be required, it's been a while so I don't remember what exactly needed to change. I just remember pulling interfaces into common code required pulling so many other things at once.

What would you do then? Top functions inside the glm package?

Also, dependencies would also need to be made multiplatform. unsigned won't work well with Kotlin native, there would be a fair bit of copying involved.

Problem with the stdlib unsigned is that they do not extend the Number abstract class..

We could keep unsigned a jvm-only dep

Dominaezzz commented 4 years ago

Where is possible to read something about that?

The most official thing I can point to is the Kotlin 1.4 blog. Other places would be hand wavy estimates from Kotlin devs in Slack.

What would you do then? Top functions inside the glm package?

You would need to make a lot of the member functions into extension functions or top-level functions. This would make the library not very nice to use from Java. You could start moving the smaller interfaces into common code, I'm not very familiar with the library so I wouldn't know exactly where to start.

Problem with the stdlib unsigned is that they do not extend the Number abstract class.

I understand the need for this but using Number means boxing very often. Also, I guess there isn't much Kotlin devs can do about it, it's a Java api. :man_shrugging:

elect86 commented 4 years ago

You would need to make a lot of the member functions into extension functions or top-level functions. This would make the library not very nice to use from Java.

No other ways?

Otherwise we can theoretical have the top level functions as the real multiplatform core and the jvm interfaces would just point to those (maybe inlined)

You could start moving the smaller interfaces into common code, I'm not very familiar with the library so I wouldn't know exactly where to start.

Ok, let's start with the projection matrix functions, if you can prepare the structure and point me to the right location I'll move a couple of those

I understand the need for this but using Number means boxing very often. Also, I guess there isn't much Kotlin devs can do about it, it's a Java api. 🤷‍♂

Yeah, but the convenience is huge. Otherwise we shall write a lot of boilerplate code (or auto-generate)

elect86 commented 4 years ago

Hi @Dominaezzz, still interested in this? From my side there is plenty of availability

I've been notified of some rising interest into multiplatform libraries for images and fonts (@Sylvyrfysh).

I'd love to merge all the spare efforts in an unique one.

Also, dependencies would also need to be made multiplatform. unsigned won't work well with Kotlin native, there would be a fair bit of copying involved.

It's a bit of work.

We can skip unsigned for the moment and postpone any decision/evaluation for later

You would need to make a lot of the member functions into extension functions or top-level functions. This would make the library not very nice to use from Java.

What is preferred among the two?

We can always add an additional layer/wrapper explicitely for Java to make it play nicely with it

You could start moving the smaller interfaces into common code, I'm not very familiar with the library so I wouldn't know exactly where to start.

We can start by the matrixClipSpace extension with functions to create ortho and perspective projection matrices, they are first methods used in the simplest scenarios.

What do you think?

Dominaezzz commented 4 years ago

What is preferred among the two?

I think extension functions. (Might have to be on a case by case basis).

We can start by the matrixClipSpace extension with functions to create ortho and perspective projection matrices, they are first methods used in the simplest scenarios.

We could start there, after pulling out Mat4 into common.

still interested in this?

I am but I'm not available to do the work on it.

elect86 commented 4 years ago

I think extension functions. (Might have to be on a case by case basis).

Ok, then extension functions be

We could start there, after pulling out Mat4 into common.

Fine, but I have no idea where to start from

I am but I'm not available to do the work on it.

It's ok, I can do the part about writing the mathematical code (methods, classes and so on), but I have no experience in native/MP, that's your knowledge I guess

Could you take care of the project structure?

I just deleted everything in the multiplatform branch, created a dummy Mat4 and a dummy ortho

Sylvyrfysh commented 4 years ago

What backends do you want, @Dominaezzz ? The code I've started working with is aimed at everything, do you also want everything or is a subset what you want?

Dominaezzz commented 4 years ago

Everything really, I don't expect this to require anything platform specific.

elect86 commented 4 years ago

I gave it a refresh, @Dominaezzz

I started with the gradle build file, moved to kotlin script, followed the tutorial on the website removed the glm-test module and all external dependencies except lwjgl ones

I have some problems understanding how to distinguish common code from the jvm specific one and how to set that in gradle

if you could help on that, it'd be great

nlbuescher commented 4 years ago

Reading through the thread and seeing what I can do to help. Some thoughts:

elect86 commented 4 years ago

We can forget about Java for the moment. If you need the methods as extension function, this is no problem at all.

However I can see a solution to that. We can define all our methods as extension functions as Dominic suggested and then I'll add an additional wrapper exclusively for the jvm, to make it play nice with Java (at least the core functions, already today there are stuff, like custom vector swizzle, which is basically kotlin-only).

My final goal is to have a library which helps with 3d real time graphics. Having something resembling the standard de-facto library on C/C++ world is a huge adavantage.

The advantage of an unsigned extending Number, is that, of course, one can have a generic method accepting it and then you convert it internally to what you need there.

But again, this is not a strict requirement

I'll send you an invitation :)

nlbuescher commented 4 years ago
  • the initial scope was that. But nothing stops us from extending it and adding new functionalities, like I myself did

We can forget about Java for the moment. If you need the methods as extension function, this is no problem at all.

However I can see a solution to that. We can define all our methods as extension functions as Dominic suggested and then I'll add an additional wrapper exclusively for the jvm, to make it play nice with Java (at least the core functions, already today there are stuff, like custom vector swizzle, which is basically kotlin-only).

That's a good approach, I think

My final goal is to have a library which helps with 3d real time graphics. Having something resembling the standard de-facto library on C/C++ world is a huge adavantage.

Definitely.

  • the problem with the stdlib unsigned is that they do not extend the abstract class Number (which is a technical obstacles for inline classes), I tried to ask Jetbrains for a switch and make it as an interface, but I was told there were not enough usage cases for that. Hopefully they will change their mind if we ask it all together.

The advantage of an unsigned extending Number, is that, of course, one can have a generic method accepting it and then you convert it internally to what you need there.

But again, this is not a strict requirement

As Dominic mentioned above, using generics actually causes automatic boxing (ie, the storage type of Vec3 and Mat4 will end up being Object on the JVM, and possibly elsewhere). In order to avoid that we'd actually have to define the various vector and matrix types separately, and depending on how many there are, it might be best to do that through code generation using something like kotlinpoet. That would eliminate the need for the Number extend.

I'll take a look what I can do about the Multiplatform build

elect86 commented 4 years ago

As Dominic mentioned above, using generics actually causes automatic boxing (ie, the storage type of Vec3 and Mat4 will end up being Object on the JVM, and possibly elsewhere). In order to avoid that we'd actually have to define the various vector and matrix types separately, and depending on how many there are, it might be best to do that through code generation using something like kotlinpoet. That would eliminate the need for the Number extend.

Yep, that would do eliminate it

However it might be worth starting small with a manual writing of some classes and see how it plays on different platform before trying to scale up and use kotlinpoet (are you familiar with it though?)

nlbuescher commented 4 years ago

definitely. yeah I've used Kotlin poet before with the kgl implementation, but I have to admit it's a pain to use codegen in general, but it's also a pain to have to write everything manually.

elect86 commented 4 years ago

Yeah, which platform(s) in particular are you interested in?

nlbuescher commented 4 years ago

I'm personally working with Kotlin/Native (Linux, macOS, and Windows), but with common code, it's trivial to run on any platform.

nlbuescher commented 4 years ago

@elect86 what was the reason for choosing glm_ as a package name instead of simply glm?

elect86 commented 4 years ago

Clashes with the glm object

nlbuescher commented 4 years ago

I'm having difficulty getting a multiplatform configuration to compile because of java module shenanigans, probably caused by lwjgl. What exactly is the purpose of the lwjgl dependency?

EDIT: To be clear, the compiler is crashing for reasons unknown.

elect86 commented 4 years ago

native vecs and mats uploads

you can skip it for the moment

nlbuescher commented 4 years ago

So going through the code after removing the lwjgl dependency, a multiplatform port would basically require starting over from scratch. Every data type is written with memory management in mind, which is something that's platform-specific and should either be omitted, or offloaded to a platform-specific module (eg glm-jvm, with extensions for lwjgl or something like that). This will not be easy.

nlbuescher commented 4 years ago

@elect86 Because of how templates work in C++, I've set up the basic structure for codegen in the multiplatform branch and put in a basic Vec2 class generation. I haven't touched the travis or jitpack files, but they'll likely need to be updated or replaced for multiplatform CI (I'm not very knowledgable about that)

elect86 commented 4 years ago

Generation code looks pretty clear and logic, I like it

So going through the code after removing the lwjgl dependency, a multiplatform port would basically require starting over from scratch. Every data type is written with memory management in mind, which is something that's platform-specific and should either be omitted, or offloaded to a platform-specific module (eg glm-jvm, with extensions for lwjgl or something like that). This will not be easy.

are vecs and mats backed by primitive arrays fine in native?

@elect86 Because of how templates work in C++, I've set up the basic structure for codegen in the multiplatform branch and put in a basic Vec2 class generation. I haven't touched the travis or jitpack files, but they'll likely need to be updated or replaced for multiplatform CI (I'm not very knowledgable about that)

I can handle those later

Shall we offload to code generation also Glm tied functions like these? I think it would make sense to have Float, Double for the corresponding floating point matrix. Shall we consider offering an alternative accepting Ints?

Extension function means something like, for example:

fun Glm.frustum (T left, T right, T bottom, T top, T near, T far)

Is this ok for you, guys?

nlbuescher commented 4 years ago

are vecs and mats backed by primitive arrays fine in native?

They are preferable. primitive arrays can be pinned and sent to C functions as pointers, which basically every C function expects in some way. Exposing the inner storage through an asFloatArray() function also allows them to be mutated from a C function, just like in C++.

Shall we offload to code generation also Glm tied functions like these? I think it would make sense to have Float, Double for the corresponding floating point matrix. Shall we consider offering an alternative accepting Ints?

That's a good question: should those functions be put into the artificial namespace Glm, or should they just be top-level functions? I feel like extension functions to the Glm object are probably a good solution.

In terms of types, glm actually defines only Float, Double, Boolean, Int, and UInt vectors, and many functions are specific to floating point types. That should be reflected in the API for this library. The goal should be feature parity, but in a Kotlinic way, with as much API mirroring as reasonable.

It's also a question whether the Kotlin port needs to keep the extremely short type names, since they're not kotlinic at all. I would propose using FloatVec2, IntVec2, etc, FloatMat3, FloatMat3x2, FloatMat4, etc as a compromise between conciseness and clarity.

EDIT: On a related note, while many collection types in Kotlin have mutable and immutable variants, vectors work much like arrays, which even in Kotlin are always mutable.

elect86 commented 4 years ago

They are preferable. primitive arrays can be pinned and sent to C functions as pointers, which basically every C function expects in some way. Exposing the inner storage through an asFloatArray() function also allows them to be mutated from a C function, just like in C++.

Excellent, this is what I like as well

~That's a good question: should those functions be put into the artificial namespace Glm, or should they just be top-level functions?~ I feel like extension functions to the Glm object are probably a good solution.

Ok

In terms of types, glm actually defines only Float, Double, Boolean, Int, and UInt vectors, and many functions are specific to floating point types. That should be reflected in the API for this library. The goal should be feature parity, but in a Kotlinic way, with as much API mirroring as reasonable.

It makes sense

It's also a question whether the Kotlin port needs to keep the extremely short type names, since they're not kotlinic at all. I would propose using FloatVec2, IntVec2, etc, FloatMat3, FloatMat3x2, FloatMat4, etc as a compromise between conciseness and clarity.

Maybe Float2, Int2 for vectors? This wouldn't however work very well with matrices, although I never saw the needs beyond floating point types..

EDIT: On a related note, while many collection types in Kotlin have mutable and immutable variants, vectors work much like arrays, which even in Kotlin are always mutable.

we may use view interfaces, that would allow the values to be simply retrieved

nlbuescher commented 4 years ago

Maybe Float2, Int2 for vectors? This wouldn't however work very well with matrices, although I never saw the needs beyond floating point types..

Float2, Int2, etc are established ways to name vectors in the industry (cf DirectX and Metal), but doesn't fit with GLM, which uses the vec<2, double, …> convention. Given that GLM's types are defined as vec2d, I think DoubleVec2 might be a better Kotlin translation (given the goal of GLM familiarity). Doesn't matter to me personally, but it should be consistent.

EDIT: Matrices would be named as Float4x4, Float3x4, etc, and GLM only defines matrix types for floating point values (Float and Double).

we may use view interfaces, that would allow the values to be simply retrieved

What I'm saying is that I don't think we need immutable types since vectors are basically fixed-length arrays with linear algebra logic built in, so I don't think view interfaces are necessary either.

elect86 commented 4 years ago

Float2, Int2, etc are established ways to name vectors in the industry (cf DirectX and Metal), but doesn't fit with GLM, which uses the vec<2, double, …> convention. Given that GLM's types are defined as vec2d, I think DoubleVec2 might be a better Kotlin translation (given the goal of GLM familiarity). Doesn't matter to me personally, but it should be consistent.

EDIT: Matrices would be named as Float4x4, Float3x4, etc, and GLM only defines matrix types for floating point values (Float and Double).

We can start then like that.

What I'm saying is that I don't think we need immutable types since vectors are basically fixed-length arrays with linear algebra logic built in, so I don't think view interfaces are necessary either.

ok

Do you happen to know how to generate methods comments?

nlbuescher commented 4 years ago

So there's an addKdoc function for FunSpec.Builder and I imagine for class builders as well

elect86 commented 4 years ago
Dominaezzz commented 4 years ago
nlbuescher commented 4 years ago
Dominaezzz commented 4 years ago

I wonder if one could write a generic function and then generate code based on a list of types, kind of like C++ templates or macros, but at build time... thinking

It is currently possible to do this actually. We can write a Kotlin compiler plugin to convert generic types to multiple specific types.

class Vector2<T>(val x: T, val y: T, val z: T)

@Reify
typealias Vector2f = Vector2<Float>

@Reify
typealias Vector2d = Vector2<Double>

after building with our Reification plugin,

class Vector2<T>(val x: T, val y: T, val z: T)

class Vector2f(val x: Float, val y: Float, val z: Float)
class Vector2d(val x: Double, val y: Double, val z: Double)

Example of compiler plugin in action -> kotlin-power-assert

nlbuescher commented 4 years ago

That is fascinating... I'm not sure what it would look like to try and implement functions that can take a different generic type than the class though. for example:

fun <T, U> Vector2<T>.plus(scalar: U): Vector2<T>

Perhaps a parameter on the Reifiy annotation? I feel like in many ways this would make coding easier, but I'm not sure if it's a rabbit hole worth going down 🤔

And then there'd need to be an option for the compiler to remove the generic version entirely (or perhaps a sealed class hierarchy is able to achieve what's needed there)

AndreasMattsson commented 3 years ago

Lots of interesting discussions here.

Just wondering if any thoughts have been put into, rather than doing a major reworking of the entire library for Multiplatform use, doing it piece by piece and start with simply splitting the interfaces into basic ones and more full featured (JVM-only ones)?

So that for any ones that currently include JVM-specific functionality they each extend a second Platform{InterfaceName} interface and have this declared with expect interface in the commonMain and actual interface with separate interfaces for JVM (with just the JVM-specific code) and native (simple empty interfaces)

I would be perfectly fine having some of the ByteBuffer, InputStream functions etc be JVM only to begin with. Then platform specific implementations could be added over time to bring the full multiplatform library up to par with the JVM-only parts?

elect86 commented 3 years ago

Lots of interesting discussions here.

Just wondering if any thoughts have been put into, rather than doing a major reworking of the entire library for Multiplatform use, doing it piece by piece and start with simply splitting the interfaces into basic ones and more full featured (JVM-only ones)?

@nlbuescher started writing down something for code generation, I also wrote something in this direction, especially on the DSL side, but I don't have much time. Also I wonder if the approach you mention makes actually more sense at the very moment.

So that for any ones that currently include JVM-specific functionality they each extend a second Platform{InterfaceName} interface and have this declared with expect interface in the commonMain and actual interface with separate interfaces for JVM (with just the JVM-specific code) and native (simple empty interfaces)

I would be perfectly fine having some of the ByteBuffer, InputStream functions etc be JVM only to begin with. Then platform specific implementations could be added over time to bring the full multiplatform library up to par with the JVM-only parts?

Yeah, I totally agree with

I'd send you an invitation in case you are interested in some contributions

0xf24 commented 3 years ago

Hi all,

also currently trying to use this in a multiplatform project, but one that has only JVM and common platforms. One thing I've been experimenting with is similar to what AndreasMattsson suggested; creating "dummy" expect classes that have all implementation and jvm specific code stripped away, and then putting actual classes in a JVM module with actual declarations for each method. apart from some teething issues with kotlin's type system (losing type information when function bodies are removed), it's pretty straightforward and only requires someone to go through and manually refactor all of the source files.

This will only work for JVM platforms, but it can be extended to add support for native platforms down the line

For my purposes, this is ideal, as the common code using these matrixes doesn't need any of the java dependant features

elect86 commented 2 years ago

So, I picked this up and trying again with KSP code generation here, I'd like to gather whatever feedback you have

elect86 commented 2 years ago

A note: I'm experimenting using the stdlib unsigned for the moment

elect86 commented 2 years ago

First multiplatform test (jvm + linux) passing