Creating builds from Windows (LWJGL3) for Linux and macOS using JarSplice

Hello new forum. This is my first message.

I created very simple example using LWJGL 3 and deprecated OpenGL 1.1. I want to use this example for testing builds on your machines. I want to be sure that JarSplice works correctly. Please, run it and say that this example works on Linux and Mac. I created this builds from Windows 10. If you have Windows, please, try to run it on Windows.

[]TheQuad_Linux.zip
[
]TheQuad_macOS.zip
[*]TheQuad_Windows.zip

Screenshot:

My friend valen10 tried to run my example using Debian 10 and he received this error message:

[quote]$ ./TheQuad_Linux.sh
Failed to execute process ‘./TheQuad_Linux.sh’. Reason:
', which is not an executable command.d the interpreter '/bin/sh
[/quote]
He opened .sh using hex-editor, replaced \r\n on \n and it works fine. How can I solve this problem on my side in JarSplice?

[quote]which is not an executable command.d the interpreter '/bin/sh
[/quote]
I think I solved this problem on Linux. Please, try to run this build: TheQuad_Linux.zip If it will work I will explain you how I solved the problem.

[quote]I think I solved this problem on Linux. Please, try to run this build: TheQuad_Linux.zip If it will work I will explain you how I solved the problem.
[/quote]
No, my idea does not work. I thought it will work if I would delete “lwjgl-natives-macos.jar” and “lwjgl-natives-windows.jar” from JarSplice JAR-list. Linux build works only if you will deleted 0x0D code at the beginning of the bash “.sh” file. But I do not understand where and how. I think it is impossible to deleted 0x0D on Windows.

I opened the “.sh” file in Notepad++:

I selected the UNIX format and saved:

Please, try: TheQuad_Linux.zip

Does not run on my macbook. No errors are logged.

I opened your application up with an unarchiver, and took out the app.jar. I tried running it from the console with:

java -jar app.jar -XstartOnFirstThread

Received this error:


Exception in thread "main" java.lang.ExceptionInInitializerError
	at org.lwjgl.glfw.GLFW.glfwCreateWindow(GLFW.java:1842)
	at Program.<init>(Program.java:17)
	at Program.main(Program.java:55)
Caused by: java.lang.IllegalStateException: GLFW windows may only be created on the main thread and that thread must be the first thread in the process. Please run the JVM with -XstartOnFirstThread. For offscreen rendering, make sure another window toolkit (e.g. AWT or JavaFX) is initialized before GLFW.
	at org.lwjgl.glfw.EventLoop$OffScreen.<clinit>(EventLoop.java:39)
	... 3 more

Everything after -jar <the.jar> will be considered a command line argument for the application to be received via the String[] parameter of the main method and not a JVM option, regardless of whether that argument started with a dash or be a recognized JVM option.
It would need to be: java -XstartOnFirstThread -jar app.jar

Please, help me to run my application on macOS and Linux. I cannot install these OS on VirtualBox because I do not have a free space on my hard drive. I am trying to make builds for macOS and Linux during 3-4 days. I have read a lot of forums, tutorials, docs, videos but I do not understand what is wrong. I just add all required JARs to JarSplice. What information do you need to help me? I deleted all duplicate of files from lwjgl.jar, lwjgl-glfw.jar, for example: lwjgl.dll.sha1, liblwjgl.dylib.sha1 and so on because they are in lwjgl-natives-linux.jar, lwjgl-natives-macos.jar and so on. Should I public list of JARs that I added to JarSplice? What information should I post here to help you to find my mistakes?

This is the code:

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import org.lwjgl.opengl.GL;

public class Program
{
    public Program()
    {
        if (!glfwInit())
        {
            System.err.println("GLFW failed to initialize.");
            System.exit(1);
        }

        int width = 256;
        int height = 256;
        long win = glfwCreateWindow(width, height, "002. Quad", 0, 0);
        
        glfwShowWindow(win);
        
        glfwMakeContextCurrent(win);
        
        GL.createCapabilities();
        
        while (!glfwWindowShouldClose(win))
        {
            glfwPollEvents();
            
            glClear(GL_COLOR_BUFFER_BIT);
            
            glBegin(GL_QUADS);
            {
            	glColor3f(1f, 0f, 0f);
            	glVertex2f(-0.5f, 0.5f);

            	glColor3f(0f, 1f, 0f);
            	glVertex2f(0.5f, 0.5f);

            	glColor3f(0f, 0f, 1f);
            	glVertex2f(0.5f, -0.5f);
            	
            	glColor3f(1f, 1f, 1f);
            	glVertex2f(-0.5f, -0.5f);
            }
            glEnd();
            
            glfwSwapBuffers(win);
        }
        
        glfwTerminate();
    }
    
    public static void main(String[] args)
    {
        new Program();
    }
}

Still gives the same error:


java -XstartOnFirstThread -jar app.jar
Exception in thread "main" java.lang.ExceptionInInitializerError
	at org.lwjgl.glfw.GLFW.glfwCreateWindow(GLFW.java:1842)
	at Program.<init>(Program.java:17)
	at Program.main(Program.java:55)
Caused by: java.lang.IllegalStateException: GLFW windows may only be created on the main thread and that thread must be the first thread in the process. Please run the JVM with -XstartOnFirstThread. For offscreen rendering, make sure another window toolkit (e.g. AWT or JavaFX) is initialized before GLFW.
	at org.lwjgl.glfw.EventLoop$OffScreen.<clinit>(EventLoop.java:39)
	... 3 more

@8Observer8
When I run your program from the code you provided it runs perfectly fine.

Thank you very much for your test. Where is a problem? Inside of JarSplice? Is exist any analog of JarSplice? I want to create .exe, .app, and .sh from Windows.

I rewrote my example to TypeScript and WebGL. You can run it in your browser by one click: TheQuad_WebGL

It must work on Window, Linux, macOS and mobile devices. You need only one click to run it from any OS like it was in deprecated Java Applets.

I did not find how to create standalone executables from Windows (analog JarSplice for Lwjgl3). But I found the solutions for myself. I will use WebGL / TypeScript for creating cross-platform Web applications and Unity / C# for creating cross-platform native desktop applications.

I created these builds from Windows 10. You can try to run this example if you want:

Hello

JarSplice isn’t the panacea. Maybe you could try JarSplicePlus, Oracle Java Packager tool and/or jlink.

Your Unity demo is terribly slow on my machine. This similar demo (tutorialspoint.com) using WebGL without Unity is much faster:

<!doctype html>
<html>
   <body>
      <canvas width = "570" height = "570" id = "my_Canvas"></canvas>

      <script>

         /*============= Creating a canvas ======================*/
         var canvas = document.getElementById('my_Canvas');
         gl = canvas.getContext('experimental-webgl');

         /*========== Defining and storing the geometry ==========*/

         var vertices = [
            -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1,
            -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1,
            -1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1,
            1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1,
            -1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1,
            -1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1, 
         ];

         var colors = [
            5,3,7, 5,3,7, 5,3,7, 5,3,7,
            1,1,3, 1,1,3, 1,1,3, 1,1,3,
            0,0,1, 0,0,1, 0,0,1, 0,0,1,
            1,0,0, 1,0,0, 1,0,0, 1,0,0,
            1,1,0, 1,1,0, 1,1,0, 1,1,0,
            0,1,0, 0,1,0, 0,1,0, 0,1,0 
         ];

         var indices = [
            0,1,2, 0,2,3, 4,5,6, 4,6,7,
            8,9,10, 8,10,11, 12,13,14, 12,14,15,
            16,17,18, 16,18,19, 20,21,22, 20,22,23 
         ];

         // Create and store data into vertex buffer
         var vertex_buffer = gl.createBuffer ();
         gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

         // Create and store data into color buffer
         var color_buffer = gl.createBuffer ();
         gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

         // Create and store data into index buffer
         var index_buffer = gl.createBuffer ();
         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

         /*=================== SHADERS =================== */

         var vertCode = 'attribute vec3 position;'+
            'uniform mat4 Pmatrix;'+
            'uniform mat4 Vmatrix;'+
            'uniform mat4 Mmatrix;'+
            'attribute vec3 color;'+//the color of the point
            'varying vec3 vColor;'+
            'void main(void) { '+//pre-built function
               'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);'+
               'vColor = color;'+
            '}';

         var fragCode = 'precision mediump float;'+
            'varying vec3 vColor;'+
            'void main(void) {'+
               'gl_FragColor = vec4(vColor, 1.);'+
            '}';

         var vertShader = gl.createShader(gl.VERTEX_SHADER);
         gl.shaderSource(vertShader, vertCode);
         gl.compileShader(vertShader);

         var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
         gl.shaderSource(fragShader, fragCode);
         gl.compileShader(fragShader);

         var shaderprogram = gl.createProgram();
         gl.attachShader(shaderprogram, vertShader);
         gl.attachShader(shaderprogram, fragShader);
         gl.linkProgram(shaderprogram);

         /*======== Associating attributes to vertex shader =====*/
         var _Pmatrix = gl.getUniformLocation(shaderprogram, "Pmatrix");
         var _Vmatrix = gl.getUniformLocation(shaderprogram, "Vmatrix");
         var _Mmatrix = gl.getUniformLocation(shaderprogram, "Mmatrix");

         gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
         var _position = gl.getAttribLocation(shaderprogram, "position");
         gl.vertexAttribPointer(_position, 3, gl.FLOAT, false,0,0);
         gl.enableVertexAttribArray(_position);

         gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
         var _color = gl.getAttribLocation(shaderprogram, "color");
         gl.vertexAttribPointer(_color, 3, gl.FLOAT, false,0,0) ;
         gl.enableVertexAttribArray(_color);
         gl.useProgram(shaderprogram);

         /*==================== MATRIX ====================== */

         function get_projection(angle, a, zMin, zMax) {
            var ang = Math.tan((angle*.5)*Math.PI/180);//angle*.5
            return [
               0.5/ang, 0 , 0, 0,
               0, 0.5*a/ang, 0, 0,
               0, 0, -(zMax+zMin)/(zMax-zMin), -1,
               0, 0, (-2*zMax*zMin)/(zMax-zMin), 0 
			   ];
         }

         var proj_matrix = get_projection(40, canvas.width/canvas.height, 1, 100);
         var mo_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];
         var view_matrix = [ 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 ];

         view_matrix[14] = view_matrix[14]-6;

         /*================= Mouse events ======================*/

         var AMORTIZATION = 0.95;
         var drag = false;
         var old_x, old_y;
         var dX = 0, dY = 0;

         var mouseDown = function(e) {
            drag = true;
            old_x = e.pageX, old_y = e.pageY;
            e.preventDefault();
            return false;
         };

         var mouseUp = function(e){
            drag = false;
         };

         var mouseMove = function(e) {
            if (!drag) return false;
            dX = (e.pageX-old_x)*2*Math.PI/canvas.width,
            dY = (e.pageY-old_y)*2*Math.PI/canvas.height;
            THETA+= dX;
            PHI+=dY;
            old_x = e.pageX, old_y = e.pageY;
            e.preventDefault();
         };

         canvas.addEventListener("mousedown", mouseDown, false);
         canvas.addEventListener("mouseup", mouseUp, false);
         canvas.addEventListener("mouseout", mouseUp, false);
         canvas.addEventListener("mousemove", mouseMove, false);

         /*=========================rotation================*/

         function rotateX(m, angle) {
            var c = Math.cos(angle);
            var s = Math.sin(angle);
            var mv1 = m[1], mv5 = m[5], mv9 = m[9];

            m[1] = m[1]*c-m[2]*s;
            m[5] = m[5]*c-m[6]*s;
            m[9] = m[9]*c-m[10]*s;

            m[2] = m[2]*c+mv1*s;
            m[6] = m[6]*c+mv5*s;
            m[10] = m[10]*c+mv9*s;
         }

         function rotateY(m, angle) {
            var c = Math.cos(angle);
            var s = Math.sin(angle);
            var mv0 = m[0], mv4 = m[4], mv8 = m[8];

            m[0] = c*m[0]+s*m[2];
            m[4] = c*m[4]+s*m[6];
            m[8] = c*m[8]+s*m[10];

            m[2] = c*m[2]-s*mv0;
            m[6] = c*m[6]-s*mv4;
            m[10] = c*m[10]-s*mv8;
         }

         /*=================== Drawing =================== */

         var THETA = 0,
         PHI = 0;
         var time_old = 0;

         var animate = function(time) {
            var dt = time-time_old;

            if (!drag) {
               dX *= AMORTIZATION, dY*=AMORTIZATION;
               THETA+=dX, PHI+=dY;
            }

            //set model matrix to I4

            mo_matrix[0] = 1, mo_matrix[1] = 0, mo_matrix[2] = 0,
            mo_matrix[3] = 0,

            mo_matrix[4] = 0, mo_matrix[5] = 1, mo_matrix[6] = 0,
            mo_matrix[7] = 0,

            mo_matrix[8] = 0, mo_matrix[9] = 0, mo_matrix[10] = 1,
            mo_matrix[11] = 0,

            mo_matrix[12] = 0, mo_matrix[13] = 0, mo_matrix[14] = 0,
            mo_matrix[15] = 1;

            rotateY(mo_matrix, THETA);
            rotateX(mo_matrix, PHI);

            time_old = time; 
            gl.enable(gl.DEPTH_TEST);

            // gl.depthFunc(gl.LEQUAL);

            gl.clearColor(0.5, 0.5, 0.5, 0.9);
            gl.clearDepth(1.0);
            gl.viewport(0.0, 0.0, canvas.width, canvas.height);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            gl.uniformMatrix4fv(_Pmatrix, false, proj_matrix);
            gl.uniformMatrix4fv(_Vmatrix, false, view_matrix);
            gl.uniformMatrix4fv(_Mmatrix, false, mo_matrix);

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
            gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

            window.requestAnimationFrame(animate);
         }
         animate(0);
      </script>

   </body>
</html>

This is my first time i visit here. I found so many entertaining stuff in your blog, especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the leisure here! Keep up the good work. I have been meaning to write something like this on my website and you have given me an idea.
happy wheels

Seems we’ve got yet another bot.

Keeping that stuff aside, why not create a fat JAR from gradle itself? Extract all your dependencies (includes libraries and native libraries) into one?


jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    from (configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
        exclude "META-INF/*.SF"
        exclude "META-INF/*.DSA"
        exclude "META-INF/*.RSA"
    }

    from (configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }) {
        exclude "META-INF/*.SF"
        exclude "META-INF/*.DSA"
        exclude "META-INF/*.RSA"
    }

    manifest {
        attributes 'Main-Class': 'com.mypackage.MyClass'
    }
}

This should do the trick of creating a fat jar file which can launch on double click. Now for the second issue of enforcing the main thread on macOS, do this in your main. I forgot the source, but the credit for this goes to kappaOne.


public static void main(String[] args) {
    if (Platform.get() == Platform.MACOSX) {
        enforceXstartOnFirstThread();
    }

    gameMain(args);
}

private static void enforceXstartOnFirstThread() {
    // get current jvm process pid
    String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
    // get environment variable on whether XstartOnFirstThread is enabled
    String env = System.getenv("JAVA_STARTED_ON_FIRST_THREAD_" + pid);

    // if environment variable is "1" then XstartOnFirstThread is enabled
    if (env != null && env.equals("1"))
        return;

    // restart jvm with -XstartOnFirstThread
    String separator = System.getProperty("file.separator");
    String classpath = System.getProperty("java.class.path");
    String mainClass = System.getenv("JAVA_MAIN_CLASS_" + pid);
    String jvmPath = System.getProperty("java.home") + separator + "bin" + separator + "java";

    if (mainClass == null) {
        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
        StackTraceElement main = stack[stack.length - 1];
        mainClass = main.getClassName();
    }

    List <String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();

    List <String> jvmArgs = new ArrayList<>();

    jvmArgs.add(jvmPath);
    jvmArgs.add("-XstartOnFirstThread");
    jvmArgs.addAll(inputArguments);
    jvmArgs.add("-cp");
    jvmArgs.add(classpath);
    jvmArgs.add(mainClass);

    try {
        ProcessBuilder processBuilder = new ProcessBuilder(jvmArgs);
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();

        InputStream is = process.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);

        String line;

        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }

        process.waitFor();
        System.exit(process.exitValue());
    } catch (Exception e) {
        e.printStackTrace();
    }

    System.exit(-1);
}

This should work fine as long as you stick with Java 8 and doesn’t go with Java 9 module system. Haven’t tried with newer Java versions since I’ve now got a full time job.

Unity is one of the best for native builds (like Exe, apk and so on), but WebGL build will not work, for example, on mobile devices. I will use Unity for native builds and for Web (for fun, for everyone who have good laptop or PC). I study pure WebGL but JavaScript is very terrible. TypeScript is better. I wrote simple prototype of classic Snake game in pure WebGL and TypeScript. You can play in Sandbox and you can see source code: https://plnkr.co/edit/7gjdZi2GNHZvtHQEMc4Y?p=preview

This is another my game in pure WebGL and TypeScript: https://8observer8.github.io/WebGL/Bitballoon/ I wrote this game one year ago.

TypeScript is very similar to C# and it is good for me to study these languages together. Author of C# and TypeScript is Anders Hejlsberg. He was created Delphi and Turbo Pascal too.

[quote=“gouessej,post:14,topic:59893”]
Maybe in the future. I cannot spend my time. I need to study how to earn money by foreign freelance. I spend a lot of time to study: English, pure WebGL, TypeScript, Unity, C# and so on. I want to make cross-platform applications but with Lwjgl it is too complicated for me. I cannot spend time on it now.

WebGL 1.0 is not experimental a few years already. Even new WebGL 2.0 is not experimental already too. But I use WebGL 1.0 because it supported on all modern browsers (expecting Opera Mini).

WebGL 1.0: https://caniuse.com/#feat=webgl
WebGL 2.0: https://caniuse.com/#feat=webgl2

SHC, thank you for posting your solution. I think it will be useful for me in the future. Maybe. Bye guys. Good luck.