# LWJGL glGenVertexArrays() erzeugt doppelte IDs



## eMmiE (18. Okt 2016)

Hi,

kurze, aber erschreckend lange


Spoiler: Einführung



Ich sitze jetzt schon länger an einem Projekt, bei dem es darum geht, eine funktionsfähige 3D Engine zu erzeugen.
Das Ganze probiere ich mit der LWJGL
Dabei erzeuge ich VAOs/VBOs für jedes 3D-Objekt der Welt (momentan für Terrains [aus Heightmaps geladen und texturiert und mit Shadern versehen] kein Problem).
Pro Terrain 1 Heightmap, an Höhlen und so einen Unfug habe ich noch nicht gedacht...
Ich kann die Terrains ohne Probleme anzeigen.

Hierzu habe ich diverse Threads (via Runnable) am Laufen, die, während das Programm läuft, im Hintergrund die Objekte aus dem Speicher werfen und neue reinladen. 
[Threads habe ich für Texturenverwaltung und Meshverwaltung(-> auch für Terrains) und Terrainmanagement, ausserdem für das Vordergrundfenster, damit ich close requests mitbekomme]
Die Umsetzung mit Runnable deswegen, weil ich mir davon erhoffe, dass das Programm im Vordergrund ohne größere FPS-Drops weiterlaufen kann, ich die Objekte, die ich NEU anzeigen möchte (bspw. beim Terrainwechsel), schon im Speicher habe.
Aktuell mache ich es so, dass ich 13*13 Terrains im Speicher habe und davon einen Ausschnitt (weniger als 11*11) anzeige.
Beim Terrainwechsel lade ich dann eine ganze Reihe (20) nach und werfe das Gegenstück dazu (andere Seite) aus dem Speicher raus.

Das funktioniert auch soweit ganz ok.

Das Problem ist jetzt, dass ich für die Entitäten (Rehe, Dinos, Glibberwürfel usw. -> Sachen mit KI) einen Hintergrundthread brauche, der deren Modelle, wenn die benötigt werden, in den Speicher reinlädt und auch wieder rausschmeißt.
Derselbe Thread soll dann auch die KI übernehmen.

Performance allerdings ist erstmal nicht mein Hauptziel, sondern nur, dass es funktioniert.



Mein Problem tritt jetzt auf, wenn ich von diesem Thread aus eine neue vaoID generieren will.
Für das generelle Erstellen und laden der Objekte (Terrain oder Modell) verwende ich meine Klasse BackgroundMeshLoader.

BackgroundMeshLoader:

```
package loader;

//hier stehen nur imports

public class BackgroundMeshLoader extends Loader {
    private static List<String> toLoad;
    //Die rel. Pfade, die geladen werden sollen
    private static List<String> toUnload;
    //Die rel. Pfade, die entladen werden sollen
    private static Map<String, Integer[]> map;
    //<rel. Pfade, {vaoID, indices-Anzahl}>
    private static Map<Integer, Integer[]> vaoTOvboIDs;
    //VBOs für Modellkoordinaten, Normalenvektoren und Texturkoordinaten
   
    private boolean appeared = false;
   //Test
    
//Siehe Runnable
    private boolean go;
    private boolean ready;
    private boolean interrupt;
    private boolean running;
   
    private SharedDrawable sd;
   
    public BackgroundMeshLoader(SharedDrawable sd) {
        this.go = false;
        this.ready = true;
        this.interrupt = false;
        this.running = false;
       
        this.sd = sd;
       
        toLoad = new ArrayList<String>();
        toUnload = new ArrayList<String>();
        map = new HashMap<String, Integer[]>();
        vaoTOvboIDs = new HashMap<Integer, Integer[]>();
    }
   
    public void load(String path) {
        if (!toLoad.contains(path)) {
            toLoad.add(path);
        }
    }
   
    public void unload(String path) {
        if (!toLoad.contains(path) && !toUnload.contains(path) && map.containsKey(path)) {
            toUnload.add(path);
        }
    }
   
    public void unload(int vaoID) {
        if (vaoTOvboIDs.containsKey(vaoID)) {
            GL30.glDeleteVertexArrays(vaoID);
            for (Integer i : vaoTOvboIDs.get(vaoID)) {
                GL15.glDeleteBuffers(i);
            }
            vaoTOvboIDs.remove(vaoID);
        }
    }
   
    public void process() {
        if (toLoad.size() > 0 | toUnload.size() > 0) {
            ready = false;
            this.go = true;
        }
    }
   
    public boolean isReady() {
        return this.ready;
    }
   
    public void start() {           
        new Thread(new Runnable() {
            public void run() {
                System.err.println(" - BACKGROUNDMESHLOADER started - ");
                init();

                System.err.println(" - BACKGROUNDMESHLOADER running - ");
                running = true;
               
                while( !interrupt ) {       
                    go = false;
                   
                    while( !go ) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException ie) {
                            ie.printStackTrace();
                        }
                        if (interrupt) {
                            break;
                        }
                    }
                   
                    for (String str : toUnload) {
                        System.out.println("unload "+ str);
                        int i = (int)map.get(str)[0];
                        GL30.glDeleteVertexArrays(i);
                       
                        Integer[] vboIds = vaoTOvboIDs.get(i);
                        for (Integer integer : vboIds) {
                            GL15.glDeleteBuffers(integer);
                        }
                        map.remove(str);
                    }
                   
                    for (String s : toLoad) {
                        System.out.println("load "+s);
                        try {       
                           
                            if (map.containsKey(s)) {
                                throw new IllegalArgumentException("Mesh already loaded");
                            }
                           
                            //modelData -> VAO,Size
                            int[] modelData = loadOBJModel(s);   
                            System.err.println("Der zugehörige VAO Index ist: "+modelData[0]);
                            map.put(s, new Integer[]{modelData[0], modelData[1]});
                           
                        } catch (Exception io) {
                            io.printStackTrace();
                        }
                    }                   
                   
                    clearLists();
                    ready = true;
                }
               
                running = false;
               
                System.err.println(" - BACKGROUNDMESHLOADER finished - ");
            }
           
            private void init() {
               
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
               
                try {
                    sd.makeCurrent();
                } catch (LWJGLException e) {
                    e.printStackTrace();
                }
               
            }
           
        }).start();       
    }
   
    public int loadToVAO(float[] positions, float[] textureCoords, float[] normals, int[] indices) {
        int vaoID = createVAO();
       
        if (vaoID == 1 && !appeared) {
            appeared = true;
        } else if  (vaoID == 1 && appeared){
            //throw new IllegalArgumentException("He really creates two ids with index 1");
        }
       
        Integer vbos[] = new Integer[4];
        vbos[0] = (bindIndicesBuffer(indices));
        vbos[1] = (storeDataInAttributeList(0,3,positions));
        vbos[2] = (storeDataInAttributeList(1,2,textureCoords));
        vbos[3] = (storeDataInAttributeList(2,3,normals));
        unbindVAO();
        vaoTOvboIDs.put(vaoID,vbos);
        return vaoID;
    }

//Laden eines Modells aus einer .obj-Datei
    public int[] loadOBJModel(String fileName) {       
        FileReader fr = null;
        //Pathing:
        //models
        //-buildings
        //-objects
        //-scene
        //-test
        try {
            fr = new FileReader(new File("res/models/"+fileName+".obj"));           
        } catch (FileNotFoundException e) {
            System.err.println("Couldnt find file: res/models/"+fileName+".obj");
            e.printStackTrace();
        }
       
        BufferedReader br = new BufferedReader(fr);
        String line;
        List<Vector3f> vertices = new ArrayList<Vector3f>();
        List<Vector2f> textures = new ArrayList<Vector2f>();
        List<Vector3f> normals = new ArrayList<Vector3f>();
        List<Integer> indices = new ArrayList<Integer>();
       
        float[] verticesArray = null;
        float[] normalsArray = null;
        float[] textureArray = null;
        int[] indicesArray = null;
       
        try {           
            while(true) {
                line = br.readLine();
                String[] currentLine = line.split(" ");
               
                if (line.startsWith("v ")) {
                    Vector3f vertex = new Vector3f(Float.parseFloat(currentLine[1]),Float.parseFloat(currentLine[2]),Float.parseFloat(currentLine[3]));
                    vertices.add(vertex);
                } else if (line.startsWith("vt ")) {
                    Vector2f texturecoord = new Vector2f(Float.parseFloat(currentLine[1]),Float.parseFloat(currentLine[2]));
                    textures.add(texturecoord);
                } else if (line.startsWith("vn ")) {
                    Vector3f normal = new Vector3f(Float.parseFloat(currentLine[1]),Float.parseFloat(currentLine[2]),Float.parseFloat(currentLine[3]));
                    normals.add(normal);
                } else if (line.startsWith("f ")) {
                    textureArray = new float[vertices.size() * 2];
                    normalsArray = new float[vertices.size() * 3];
                    break;
                }
            }
               
            while(line != null) {
                if (!line.startsWith("f ")) {
                        line = br.readLine();
                        continue;
                }
                String[] currentLine = line.split(" ");
                String[] vertex1 = currentLine[1].split("/");
                String[] vertex2 = currentLine[2].split("/");
                String[] vertex3 = currentLine[3].split("/");
               
                processFVertex(vertex1,indices,textures,normals,textureArray,normalsArray);
                processFVertex(vertex2,indices,textures,normals,textureArray,normalsArray);
                processFVertex(vertex3,indices,textures,normals,textureArray,normalsArray);
                line = br.readLine();
            }
            br.close();
           
        } catch(Exception e) {
            e.printStackTrace();
        }
       
        verticesArray = new float[vertices.size() * 3];
        indicesArray = new int[indices.size()];
       
        int vertexPointer = 0;
        for (Vector3f vec : vertices) {
            verticesArray[vertexPointer++] = vec.x;
            verticesArray[vertexPointer++] = vec.y;
            verticesArray[vertexPointer++] = vec.z;
        }
       
        for (int i = 0 ;i < indices.size();i++) {
            indicesArray[i] = indices.get(i);
        }

        return new int[] {loadToVAO(verticesArray, textureArray, normalsArray, indicesArray),indices.size()};
    }
    
//Datenarray verwerten
    private void processFVertex(String[] vertexData, List<Integer> indices, List<Vector2f> textures, List<Vector3f> normals, float[] textureArray, float[] normalsArray) {
        int currentVertexPointer = Integer.parseInt(vertexData[0]) - 1;
        indices.add(currentVertexPointer);
        Vector2f currentTex = textures.get(Integer.parseInt(vertexData[1]) - 1);
        textureArray[currentVertexPointer * 2] = currentTex.x;
        textureArray[currentVertexPointer * 2 + 1] = 1 - currentTex.y;
        Vector3f currentNorm = normals.get(Integer.parseInt(vertexData[2]) - 1);
        normalsArray[currentVertexPointer * 3] = currentNorm.x;
        normalsArray[currentVertexPointer * 3 + 1] = currentNorm.y;
        normalsArray[currentVertexPointer * 3 + 2] = currentNorm.z;
     }
   
    public Map<String, Integer[]> getMeshIDMap() {
        return map;
    }
    
//Interrupten
    public void stop() {
        interrupt = true;
        System.out.println("bgml interrupted");
    }
   
    public boolean isRunning() {
        return this.running;
    }
   
    public void clearLists() {
        toLoad.clear();
        toUnload.clear();
    }
    
//Listen löschen
    public void clear() {
        //clears VAOs and VBOs
        Set<Integer> l = vaoTOvboIDs.keySet();
        for (Integer i  : l) {
            GL30.glDeleteVertexArrays(i);
        }
       
        Collection<Integer[]> vboIds = vaoTOvboIDs.values();
        for (Integer[] i : vboIds) {
            for (Integer integer : i) {
                GL15.glDeleteBuffers(integer);
            }
        }
       
        vaoTOvboIDs.clear();
        toLoad.clear();
        toUnload.clear();
        map.clear();
    }
}
```

Loader:

```
package loader;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

public class Loader {
   
    protected static int createVAO() {
        //creates a new VAO
        int vaoID = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vaoID);       
        return vaoID;
    }
   
    protected static void unbindVAO() {
        GL30.glBindVertexArray(0);
    }
   
    protected static int storeDataInAttributeList(int attributeNumber, int coordinateSize, float[] data) {
        int vboID = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
        FloatBuffer buffer = storeDataInFloatBuffer(data);
        //-> static draw : gl now knows, we dont want to edit the stored data any more
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(attributeNumber, coordinateSize, GL11.GL_FLOAT, false, 0,0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
        return vboID;
    }
   
    protected static int bindIndicesBuffer(int[] indices) {
        int vboID = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER,  vboID);
        IntBuffer buffer = storeDataInIntBuffer(indices);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
        return vboID;
    }
   
    private static IntBuffer storeDataInIntBuffer (int[] data) {
        IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
        buffer.put(data);
        buffer.flip();
       
        return buffer;
    }
   
    private static FloatBuffer storeDataInFloatBuffer(float[] data) {
        FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
        buffer.put(data);
        buffer.flip();
       
        return buffer;
    }
}
```

BackgroundTerrainLoader-Methode(s.u.):

```
public void load(int x, int z) {   
        if (isCurrentlyLoaded(x,z)) {
            System.out.println("Tell me a lie");
            return;
        }
        File f = new File("res/textures/terrains/"+x+"_"+z+"_h.png");
        if (!f.exists()) {
            System.err.println(f.getAbsolutePath());
            System.err.println(x+ " | "+z+" nicht gefunden [FILE]");
            throw new IllegalArgumentException("!!!");
        }
        BufferedImage image = null;
        try {
            image = ImageIO.read(f);
        } catch (IOException io) {
            io.printStackTrace();
        }
       
        //Lade die Hilfsdateien für die Normalen an den Kanten
        if (wantToKnow) System.out.println("Für "+x+"|"+z+" geladen");
        f = new File("res/textures/terrains/"+(x-1)+"_"+z+"_h.png");
        BufferedImage hxmimage = null;
        if (f.exists()) {
            try {
                hxmimage = ImageIO.read(f);
            } catch (IOException io) {
                io.printStackTrace();
            }
        }
        f = new File("res/textures/terrains/"+(x+1)+"_"+z+"_h.png");
        BufferedImage hxpimage = null;
        if (f.exists()) {
            try {
                hxpimage = ImageIO.read(f);
            } catch (IOException io) {
                io.printStackTrace();
            }
        }
        f = new File("res/textures/terrains/"+(x)+"_"+(z-1)+"_h.png");
        BufferedImage hzmimage = null;
        if (f.exists()) {
            try {
                hzmimage = ImageIO.read(f);
            } catch (IOException io) {
                io.printStackTrace();
            }
        }
        f = new File("res/textures/terrains/"+(x)+"_"+(z+1)+"_h.png");
        BufferedImage hzpimage = null;
        if (f.exists()) {
            try {
                hzpimage = ImageIO.read(f);
            } catch (IOException io) {
                io.printStackTrace();
            }
        }
       
        int VERTEX_COUNT = image.getHeight();
        //image.getHeight() is usually 256
        int count = VERTEX_COUNT * VERTEX_COUNT;
        float[] vertices = new float[count * 3];
        float[][] heights = new float[VERTEX_COUNT][VERTEX_COUNT];
        float[] normals = new float[count * 3];
        float[] textureCoords = new float[count * 2];
        int[] indices = new int[6 * (VERTEX_COUNT - 1) * (VERTEX_COUNT - 1)];
        int vertexPointer = 0;
        for (int i = 0;i < VERTEX_COUNT;i++) {
            for (int j = 0;j < VERTEX_COUNT;j++) {
                vertices[vertexPointer * 3] = (float)j / ((float) VERTEX_COUNT - 1) * Terrain.SIZE;
                float height = getHeight(j,i,image);
                vertices[vertexPointer * 3 + 1] = height;
                heights[j][i] = height;
                vertices[vertexPointer * 3 + 2] = (float)i / ((float) VERTEX_COUNT - 1) * Terrain.SIZE;
                Vector3f normal = calculateNormal(j,i,image,hxmimage,hxpimage,hzmimage,hzpimage);
                normals[vertexPointer*3] = normal.x;
                normals[vertexPointer*3 + 1] = normal.y;
                normals[vertexPointer*3 + 2] = normal.z;
                textureCoords[vertexPointer * 2] = (float)j / ((float)VERTEX_COUNT - 1);
                textureCoords[vertexPointer * 2 + 1] = (float)i / ((float)VERTEX_COUNT - 1);
                vertexPointer++;
            }           
        }
       
        vertexPointer = 0;
        for (int gz = 0; gz < VERTEX_COUNT - 1; gz++) {
            for (int gx = 0; gx < VERTEX_COUNT - 1;gx++) {
                int topLeft = (gz * VERTEX_COUNT + gx);
                int topRight = topLeft + 1;
                int bottomLeft = (gz + 1) * VERTEX_COUNT + gx;
                int bottomRight = bottomLeft + 1;
                indices[vertexPointer++] = topLeft;
                indices[vertexPointer++] = bottomLeft;
                indices[vertexPointer++] = topRight;
                indices[vertexPointer++] = topRight;
                indices[vertexPointer++] = bottomLeft;
                indices[vertexPointer++] = bottomRight;
            }           
        }
               
        TerrainTexturePack ttp = new TerrainTexturePack(null,null,null,null,null);
        this.bgtl.load(new Texture("terrains/"+x+"_"+z+"_b",(byte) 0));
        ttp.setBlendMapPath("terrains/"+x+"_"+z+"_b");
       
        File file = new File("res/game/terrains/"+x+"_"+z+"_t.txt");
        char[] content = new char[(int)file.length()];
        try {
            FileReader fr = new FileReader(file);
            fr.read(content);
            fr.close();
        } catch (IOException io) {
            io.printStackTrace();
        }
        String str = new String(content);
       
        String[] strings = str.split(":");
       
        if (!strings[0].equals("null")) {
            ttp.setRTexturePath(strings[0]);
            this.bgtl.load(new Texture(strings[0],(byte) 0));
        } else {
            ttp.setRTexturePath("NULL");
        }
        if (!strings[1].equals("null")) {
            ttp.setGTexturePath(strings[1]);
            this.bgtl.load(new Texture(strings[1],(byte) 0));
        } else {
            ttp.setGTexturePath("NULL");
        }
        if (!strings[2].equals("null")) {
            ttp.setBTexturePath(strings[2]);
            this.bgtl.load(new Texture(strings[2],(byte) 0));
        } else {
            ttp.setBTexturePath("NULL");
        }
        if (!strings[3].equals("null")) {
            ttp.setBackgroundTexturePath(strings[3]);
            this.bgtl.load(new Texture(strings[3],(byte) 0));
        }
       
        int vaoID = this.bgml.loadToVAO(vertices,textureCoords,normals,indices);
        System.err.println("Terrain VAO ID: "+vaoID);
        this.allTerrains.add(new Terrain(x,z,ttp,vaoID,heights));
    }
```

Beim Laden von Terrains werden die Heightmaps geladen und direkt in Koordinaten und Normalen aufgeteilt, dann BlendMaps geladen und in ein Terrain Objekt gepackt.
Es wird dann nur die Methode BackgroundMeshLoader.loadToVAO(Daten); aufgerufen.
Beim Laden von Modellen verwende ich die Methode loadOBJModell().

Soooo...
Die Terrains lade ich zuerst.
Die Methode GL30.glGenVertexArrays() erstellt IDs von 1 bis 169.
Wenn ich DANACH (-> Terrainladen ist fertig) die Modelle lade, beginnt die Methode wieder bei 1.
Das Ergebnis davon ist, dass ich, wenn ich genügend Vertices malen lasse, eine untexturiertes Terrain sehe (schwarz).
Er überschreibt also, wie eingestellt, bereits gespeicherte Daten nicht.

Aber warum fängt er jetzt wieder bei 1 an?
Bin auch offen für Anregungen zum generellen Aufbau usw.

Danke im Vorraus,

eMmiE


----------



## Times (19. Okt 2016)

Hey,

in der Klasse "BackgroundMeshLoader" fügst du deine ID's einer Liste hinzu und löschst sie anschliessend wieder, und beim löschen rufst du "GL30.glDeleteVertexArrays(i)" auf, wodurch natürlich die ID auch wieder freigegeben wird.

Codestellen:
Methode: start | Aufruf von loadOBJModel > in welcher wiederrum loadToVAO aufgerufen wird und in dieser wird die ID zur Liste vaoTOvboIDs hinzugefügt.
Methode: start | Ein stück unter loadOBJModel  rufst du clearLists auf, wodurch das Problem auftritt.

Ich denke das da der Fehler liegt, wenn nicht... dann.. such ich weiter


----------



## eMmiE (28. Okt 2016)

Hi,

hat leider ein bisschen länger gedauert mit der Antwort... 

Das mit den Listen hätte ich vllt noch genauer erklären müssen.
Die sind Teil des "Passivladens".
Ich will ja, dass die BackgroundLoader immer mal wieder laufen, aber nicht zeitlich an die Logik geknüpft.
Deswegen schreibe ich die Objekte (Texturen & Meshes) in die Load bzw. Unload-Listen.
Wenn der BackgroundLoader dann angestoßen wird, soll er alle Objekte in der Unload-Liste aus dem Speicher werfen und die aus der Load-Liste in den Speicher laden.
clearLists() löscht jetzt nur die Load-/Unload-Listen, aber nicht die geladenen Objekte.

Ich werde wohl noch ein oder zwei Stellen haben, wo noch aktiv geladen wird, aber das sollte keine doppelten VAO IDs hervorrufen.

Auf jeden Fall schonmal vielen Dank für deine Antwort, 
aber leider muss das Problem wo anders stecken.

Gruß,
eMmiE


----------



## eMmiE (28. Okt 2016)

Eine Link zu einer guten Doku von der GL30 Klasse oder der ganzen LWJGL würden mir auch immens weiterhelfen


----------

