 And so, please welcome on stage Giuseppe. He's going to talk about how Kotlin can change developer experience with modern graphics APIs. Please. Hi, thank you. I, as I said, I'm Giuseppe Berberi. I am a graphic developer by Reno who is working on a software called M-Check, which is a cut-like software for analyzing the visibility field of different types of vehicles. And basically, I followed along with Kotlin some years ago, and then I started writing some utility leaves to help the development with graphics. So, let's get started. We started with OpenGL. I will assume that, yeah, we are all Kotlinians. And some of you, I already know OpenGL and maybe Vulkan. How many knows OpenGL? Okay. Vulkan? Okay. So, for the others, let's say, this is much more about Kotlin, so you can still understand most of the stuff. So, let's get started with OpenGL. So, it's a really whole library, say, started in 1991 with STI. Then they left the development in 2006 and basically handover to Kronos, which released a very big version 3.0 in 2008. They also introduced the application mechanism and context. And the 4.1 is the less available MacOS. This is a little shame. And the last release available for all the other platforms is 4.6, which was released in 2017, two years ago. Vulkan came a little earlier than that. So, welcome, GLN. What is about, basically about, makes some, I would say, some massive use of inline classes, where basically you already know it's an integer. And most of the time, it's an integer, but works like a class. And also Enums, although they are not really available in Kotlin, but there's a little trick. Then also we have inline functions. So, basically, you write the nice code and then everything, all the, let's say, I will not say ugly, but all the boilerplate code will be then replaced by the compiler. It's also object-oriented. So, as I say, on the inline classes, we have all the methods, DSL, where you can easily wrap some scope, some, let's say, OpenGL object, and then inside the scope, do something nice. Functional programming. So, you can pass around lambdas whenever needed. And there are also some convenience of loading function that basically take maybe some parameter, some, let's say, parameter, which are quite obvious out of the question. So, we can have some more concise code. And it plays quite well with, quite nicely with another library, GLM. It's basically a part of the original CGLM. To, the best way is just to start showing code. And let's go through a very, very simple Hello World program. So, in this case, for example, we have, we start our main, we define some variable, where we have, for example, the program variable, then an numerator to identify the two buffer that we're going to use, one for the vertex and one for the element. Then the actual variable, which is under the hood, it's direct int buffer, it's native memory. Then we have one integer for the vertex array and two for the uniform, so-called MVP for the model view projection matrix and one for the diffuse color. Then let's dig into our first method, any program. Okay. Here you can see, in our function, we start, just, I will show you first, the plain way to do it in Kotlin. So, we first create our program, then we want to create our vertex shader. We have another function for that called any vertex shader. We go inside our any vertex shader. We can define here the source. This is a pretty standard shader, where we have, for example, matrix to then incoming attribute, vertex attributes. And here Kotlin makes very nice, because first we have the typical quote. So, compared to Java, this is already a very nice point, very nice advantages. And if we want, we can also inject, as you see in the, in the comment, we can inject some explicit vertex layout, like, for example, position. Then we have also one for the color. We have an outside of the color that will be linearly interpreted by the segment shader. And then in the main, we simply multiply the incoming position by the matrix. We assign to the GLSL position, and then we also pass the color. Then we create our vertex shader. We pass the, we need to pass, of course, the type of vertex shader that we want to create. In this case, vertex shader. Then we can have a generic in each shader, where we give the object we just created, and the source. In the in each shaders, we receive, as I say, the shaders, and the source. So, first we want to set the source to the shaders. We want then to compile the shaders. It's a lot of redundancy here. Then we want to check if, of course, everything went fine, or we, we want to know if there was some problem. So, we retrieve the compile status. GL gets shader. I means that, of course, we will get an int. Then we need to, for example, to check this int against one OpenGL concept, like glFalcon. If the compile fails, then we need to, we want to see what went wrong. So, we want, of course, to retrieve the infolog, but to retrieve the infolog, we need, first, to retrieve the infolog length. And this is what we are doing here. GL gets shader i. We pass the shader set, and then the concept infolog length. We return an int. Then with this, we can call GL gets shader info, which will return automatically a string. And then finally, we can, through our next exception, with the reason why the compilation failed. Then we can come back, and we can return our vertex, which is an int. So, how can we make this nicer? First, we will return from the int's vector shader, GL shader. This is an inline classes. The source is the same. Let's comment out for space problems. Then our original GL creates shader will be converted to a static function on the inline class. So, we want a GL shader, and we call create, passing, in this case, vector shader is, you can think about like a name. Then what we do here is basically we scope the GL shader. And then we can simply type much shorter code, because we scoped it, so we know what this is going about, and we don't need, and we don't want any more redundancy. So, we call simply source. Maybe this is unfortunately naming. Then we compile our shaders. Compile status. This is also nice. We are inside these, let's say inside these scopes. So, compile status is just, let's say, get custom get variable, which will return, of course, the compile status. And we already know that the compile status should be a Boolean. So, immediately we can return directly a Boolean. And then also the infolog. I don't want to write all the border code. So, I want to hide in the library. I just want to type GL shader infolog, and then I retrieve all the log that is telling me what went wrong. So, we saw here GL shader dot create vertex shader. Question is, what is this vertex shader? It's an inline class or a numerator? Well, it's actually both, because although Kotlin doesn't have yet inline numerator, there is an issue on U-track, which I personally hoping to implement also inline numerator. It's going to take some time. Hopefully one day we will get them. But in the meantime, there was a user, I don't remember the name, but on the forum, on the Kotlin forum, wrote these nitrics where we can basically use inline classes as an numerator. There's just one disadvantage. Let's say that basically the hand user can create some additional numerator, but of course we want to play nice. So, it's a cooperative environment. The user will not hurt itself and create an additional, maybe wrong, numerator. Then in our init, we come back to the init program. We just came back from create a vertex shader. Fragment is basically the same. I will not go inside because it's a matter of time. Then we have to attach the vertex and the fragment to our program. In this case, for example, we bind the attribute location, like position and color, to some parameterized variable, and then we link our program. Let's, for a moment, pause here and see what this can become. So, program can be also an inline classes where we call basically GR program create, and then we call apply. Here, GR program will be scoped again. We call our corresponding create vertex and fragment shader, which in this case, they will return inline classes instead of int. Then we can use, for example, the nice overload operaton from Kotlin to attach the vertex to the program. We can then, instead all of this stuff, gl binding attribute location, blah, blah, we can simply type the string. We are inside the scope, so we offer an extension function on the string that will, when we'll be set, it will be automatically called gl binding attribute location behind the curtains. So, it's more like, I would say, natural. Then we link, just one call, and then we continue. We continue. Here, instead of link status, we have, as before, just one variable because we are inside this block. Also, info log is just one line. Then we detach the shaders. It's not mandatory, but if you want to do the things clear, we detach them from the program, and then we simply delete the shaders, since they are inline classes, we have a method available on the inline class itself. Honestly, you can do all of this with just one line. Using gl program, in it from path, you give the path of the shaders, and then the library will look through in this path for, you give up to the name of the shaders, then the shaders need to be, that you have all the same name, and then through some standard shader extension like ver, frag, jam, test, and so on. We look if they are available, if they are available, we load them, and then it will compile into the program. Also, it will look in the same directory if you have any import on any additional shaders. Remember when we had to bind the htb location? This, if you remember, need to be done before linking the program. So, how can we do? We just pass them as a lambda at the end of our init program, and this lambda will be, of course, served to you with the program base. It's an object that basically offers you all this nice stuff like attribute with an extension variable on a stream. And this will be called before the link program. Then the link program, blah, blah, return the gl program to you. Everything nice and pathway. Okay, now init buffer. We go in the init buffer. This is pretty standard. We generate the buffer. We bind the, we bind the buffer that we retrieved from our buffer variable using the numerator just to make it nice. Then we bind to the jrray buffer target. We upload the position. We detach the buffer. Then we do the same for the element. And then we check the error. And we come out. So here, the buffer, we can basically generate them in just one line. We scope them. We, since we scope, we can just use the numerator to retrieve the corresponding buffer. We bound it. And then we pass lambda, what we call, for example, just data position data. Everything is already known. We know the buffer. We know the target. No need to redeclare them. Same for the element and so on. At the end, the buffer will be detached. Check error. We need vertex array. We do the same. Generate the vertex array. Bind. We need to bind the buffer. We need to call a jr vertex attribute point where we give all the stuff, all the stuff to specification for our vertex layout. Then we detach the bind buffer. And the vertex array required to left bond the element array. This is what we do. We enable. We detach. We check the error. What we can do? We can call jr. Gen vertex array. We give the reference to our inline function. This is also something really cool in Kotlin. We bound. We scope the buffers. We call the numerator bound on array. GLN offers some pretty fine layout. Just post to Kotlin set. We automatically call all the line that you see above. We bind the element. We enable. We check the error and we come back. Then here in the render, we basically have a window size lecture position where we calculate our projection, matrix, view, and mvp matrix. Then GLN offers you some overloading function like this GL report the window size. Then we see the corresponding buffer. And we go to the next stuff. We use the program. We retrieve the memory stack in order to allocate our matrix in netting memory on the thread stack. And we bind the vertex array, draw element, and bind. Unuse the program. This can be done in the following code. Program used. We enter. Just the uniform mvp and we give the matrix. Everything will be done in the background. Vertex array bound as before. Draw element. Just pass element. Triangle is the default type primitive. And all the rest will be retrieved from the, in this case, element into an int buffer. So we can retrieve the number of elements that remains. The type. And then the pointer at the end is defaulted to zero. That's true. At the end, we can just call the corresponding delete on the stuff. So with DSA, we can basically what was before, like GL buffer parameter, get translated to GL, get named buffer parameter. With GLN, it's simply called buffer immutable storage on the inline class itself. Pretty natural and standard. So Vulkan, it's not only across platform rendering but also computing API. It's supposed to also, let's say, take over OpenCL. Really low-horror. You have more than 1,000 lines for a single high-law triangle, boot upon mantle, donated by MD. It was released in 2016. And the last release was at the beginning of this year. We have basically also inline classes and inline functions. It's object-oriented. All the VK object has a lot of function on there. Function on Kagami and convenience overloading, low function. So where basically we call the very end function of the LBGGL binding just before passing, going to the native. And then also play nice with GLM. Let's go just through a very short stuff. So just like creating something, we want to have validation. What do we need to do first? Usually you want to retrieve the stack at the beginning of the function and then use in the body. We need to call, we need to allocate the VK application info. But in order to do this, we need the first two for our application and giant name create, allocate some native space and then allocate this string on the native memory via stack UTF-8. Then we call VK application info. We set this type, all the string, happy version one, we go next. We retrieve the required extension. It's a pointer buffer. If it's null, then we get, we are, we know, we are in, in a narrow state. Then we create a VK instance creating info also with the KoreanS type and then we pass our application info we just created. Then if this required instance extension is not empty and we require, we want, the user want to have validation, then we need to allocate, we need to take the pointer buffer and then make space for another slot and then copy back all the stuff and finally allocate the new extension. Like we have done here, so we copy all the extension for the, from the required extension to the instance extension variable. We allocate the VK debug duties, then we actually put it and then we set the variable. Otherwise we set immediately the variable of before because we didn't have to add a new extension. Then again, if we need validation, then we need the VK-lider-conference validation, which has all the current validation functionality. We want to check if these are available. So basically we retrieve all the instance-layer properties in this way. You create pointer to non-inter to retrieve all the layer properties count and then with this you can actually allocate all the buffer for the layer properties and then finally all the layer properties. Then we check if this layer is present. If this is present, then we simply allocate a pointer buffer. We give this, this validation layer name in this pointer buffer and then we assign to the corresponding variable. If not, we fire another. At the end we create a pointer buffer where we'll be saved the instance pointer. So we call, we create, create instance and we pass at the end our pointer buffer. We will return, we will get an int at the end. Then we will create our VK instance. We pop the stack and we return the result. Create instance. So with VK2 we can just do this. Create instance. We return directly and inline classes. It's everything on Java. We don't want to deal, we already take the heat of the performance since we are in JVM. I don't want to also to take the heat about writing, let's say, I will not say hardly but pass me that, bear with me on this. So I don't want to write all the boiler page tools. So application info. Hello triangle, my engined ones. No need to deal with pre-string and so on. I don't have to specify the type. So then I get the required instance session. It's one unique array list of string. I want to avoid the nullability. So just if it's empty, I'm in the failure state. Then I create the instance creating info. If it's not empty, if I want validation, I just add the string into the array list and then I simply pass the array list to the corresponding variable. If it's validation, VK layer validation, we just one line, we retrieve all our layer properties. All the boiler code is again hidden by the library. We check if it's present a layer, if it's present, we simply have them. Otherwise we find the error. And then we create the instance. Again, no pointers. So we don't have to create a pointer buffer and then retrieve from the pointer buffer after calling the function. It's a direct instance session and there is no allocation function because I never saw anything about passing something for the allocation. So this is always null. What is I expecting VK result? Well, since everything is in line, we can just call the function that it's a dummy constructor and then also accept a lambda at the end. It's in line, so we can write return and in the lambda we be given the result. At the first one, we return the instance. What happened to the stack? Well, you remember we had to create the stack once and then use inside the code. Here what we do, basically, we use the stack just in two places, where we retrieve the layer properties and when we get the instance. This will be all automatically done. I mean, if you don't pass it in this way, the stack will be retrieved. You will get the heat of the stack get twice. If you don't want to get this heat, then what you do, you basically call create instance using the memory stack as a receiver. So everything nice, very few modifications and it's really efficient because in the hot loop you don't want to pay an additional heat. So you can usually pass the stack that you get all through down the stack function where it's needed. All the libraries are available at itab.com under Kotlin classes organization. Sorry, I don't have time for a question and answer and I hope you will find some interesting stuff over there.