Search code examples
javamultithreadingopengllwjglvao

LWJGL glGenVertexArrays() is blocking the execution


I recently asked a question on the forum on why the ExecutorService was blocking my program after getting the futures: ExecutorService and Future are blocking the main thread

The thing is that after a lot of debugging, the guilty line wasn't the future.get() but int vaoID = GL30.glGenVertexArrays();

I'll sum up what I'm trying to do, and then post the code. Basically, I'm generating terrains as the player moves. To avoid freezing the game whenever the generation happens, I implemented multithreading with callables and ExecutorService and Futures. So when I have to generate terrains I'm submitting new tasks to the ExecutorService (the task is to calculate all the vertices/normals/colors of the terrain), and then for each future, if it is done I'm getting the result and generate the model of the terrain. But at each frame, the first future that finishes is causing a freeze. Actually, it's not the future itself that is causing it but the creation of the model, and more precisely the int vaoID = GL30.glGenVertexArrays(); method.

To sum up, I think that glGenVertexArrays(); is blocking the thread, waiting for maybe the other threads to finish, and I don't have a single clue of what is going on. I am using LWJGL 2 and OpenGL 3.0+.

Here is the code that handles the multithreading:


//method called each frame
private void checkForFutures(List<Future<SomeClass>> terrainsInCreation) {
        try
        {
            List<Future<SomeClass>> futuresToRemove = new ArrayList<Future<SomeClass>>();
            for(Future<SomeClass> future:terrainsInCreation) {
                if(future.isDone()) {
                    float time = DisplayManager.getCurrentTime();
                    try {
                        SomeClass t = future.get();
                        Key key = new Key(t.terrain.getChunkX(),t.terrain.getChunkZ());
                        t.terrain.generateTerrain(loader, t.vertices, t.indices,t.colors, t.normals); // Guilty line !
                        futuresToRemove.add(future);
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                        Thread.sleep(1000000);
                    }
                }
            }
            terrainsInCreation.removeAll(futuresToRemove);
        }
        catch (InterruptedException ie)
        {
            System.err.println("BIG PROBLEM ON TERRAIN MANAGER");          
        }
    }

//method called when I have terrains to generate
    private void generate(List<Callable<SomeClass>> terrainsToCreate) {

        for(int i = 0; i < terrainsToCreate.size();i++) {
            terrainsInCreation.add(executor.submit(terrainsToCreate.get(i)));
        } 
    }

Here is the generateTerrain() method:

        int vaoID = createVAO();
        bindIndicesBuffer(indices);
        storeDataInAttributeList(0,3, positions);
        storeDataInAttributeList(1,3, colors);
        storeDataInAttributeList(2, 3, normals);

        unbindVAO();
        
        return new RawModel(vaoID, indices.length);


(I simplified the code)

And here is the createVao() method:


private int createVAO() {
        long start = DisplayManager.getCurrentTime();
        int vaoID = GL30.glGenVertexArrays(); // The first of each frame takes ~40-200ms
        long glGen = DisplayManager.getCurrentTime();

        vaos.add(vaoID);
        long add = DisplayManager.getCurrentTime();

        GL30.glBindVertexArray(vaoID);
        long bind = DisplayManager.getCurrentTime();
        
        System.out.println(String.format("Vao: gen: %d + add: %d + bind: %d = %d", 
                (glGen-start),
                (add-glGen),
                (bind-add),
                (DisplayManager.getCurrentTime()-start)));
        return vaoID;
    }

And finally here is the print output in a regular generation:


NEW CHECK
NEW CHECK
Vao: gen: 7 + add: 0 + bind: 0 = 7
Vao: gen: 1 + add: 0 + bind: 0 = 1
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
NEW CHECK
Vao: gen: 209 + add: 0 + bind: 0 = 209 (why????)
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 1 + add: 0 + bind: 0 = 1
Vao: gen: 1 + add: 0 + bind: 0 = 1
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 1 = 1
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
NEW CHECK
Vao: gen: 4 + add: 0 + bind: 0 = 4
Vao: gen: 0 + add: 0 + bind: 0 = 0
Vao: gen: 0 + add: 0 + bind: 0 = 0
NEW CHECK
NEW CHECK

Each frame I'm printing "NEW CHECK" and as you can see it is not exactly the first of each frame sometimes it is not but when a generation event occurs the first is always super slow.

My question is what is going on here and what can I do / test / update ?

Thanks for your help.

EDIT

I must precise that GL30.glGenVertexArrays() is called in the main thread. Here is the context code executed before anything else at the start of the program:


ContextAttribs attribs = new ContextAttribs(3,2)
        .withForwardCompatible(true)
        .withProfileCore(true);
        Mouse.setGrabbed(true);

        try {
            DisplayMode displayMode = new DisplayMode(WIDTH, HEIGHT);
            Display.setDisplayMode(displayMode);
            Display.create(new PixelFormat(),attribs);
            Display.setTitle("CubeLand");
        } catch (LWJGLException e) {
            e.printStackTrace();
        }

EDIT 2

I removed the multithreading, by doing everything in the main thread, and about the same freeze happens when generating, which is normal because the calculations are huge, BUT the GL30.glGenVertexArrays() is not taking any time to compute. This proves that this method is waiting for all the other threads to end, but why and how to avoid that??


NEW CHECK
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
Vao gen: 0
NEW CHECK

EDIT 3 Some news, when I unplug my laptop, the graphic card is turned off and the motherboard graphic chipset replace it. Suddenly, the issue is gone. Off course I still have freezes because now that my performance are cut off, the processor struggles to generate the terrain even with multithreading, but the vao gen time is back to 0. This is confusing.


Solution

  • Okay, so after 2 weeks of struggling with this, I finally found a workaround. After a long profiling session, I'm 90% sure that glGenVertexArrays() is waiting for some threads to finish their tasks, I don't know why, but it correlates 2/3 times. The workaround is to wait for all the futures to come out, and then generate the terrains (aka call glGenVertexArrays()). This way, the glGenVertexArrays() is not pausing the main thread, and to avoid creating dozens of terrains at the same time I'm only creating like 2-3 per frames, which is unnoticeable.