LWJGL / lwjgl3

LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan, bgfx), audio (OpenAL, Opus), parallel computing (OpenCL, CUDA) and XR (OpenVR, LibOVR, OpenXR) applications.
https://www.lwjgl.org
BSD 3-Clause "New" or "Revised" License
4.75k stars 635 forks source link

Assimp trying to load 3ds File #982

Open Verschwiegener opened 4 months ago

Verschwiegener commented 4 months ago

Version

3.3.3

Platform

Linux x64

JDK

OpenJDK-17-Amd64

Module

Assimp

Bug description

Im trying to load a .3ds File with Assimp but the code crashes in Native Code, the Model Load code is a slightly changed Version of the Code in the Assimp Example. I checked that the Path to the 3ds File is valid and the Code does work with OBJ Files.

Any help would be appreciated :)

Model Load Code:

static AIFileIO fileIo = AIFileIO.create().OpenProc((pFileIO, fileName, openMode) -> {
        ByteBuffer data;
        String fileNameUtf8 = memUTF8(fileName);
        try {
            data = ioResourceToByteBuffer(fileNameUtf8, 8192);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("Could not open file: " + fileNameUtf8);
        }

        return AIFile.create().ReadProc((pFile, pBuffer, size, count) -> {
            long max = Math.min(data.remaining() / size, count);
            memCopy(memAddress(data), pBuffer, max * size);
            data.position(data.position() + (int) (max * size));
            return max;
        }).SeekProc((pFile, offset, origin) -> {
            if (origin == Assimp.aiOrigin_CUR) {
                data.position(data.position() + (int) offset);
            } else if (origin == Assimp.aiOrigin_SET) {
                data.position((int) offset);
            } else if (origin == Assimp.aiOrigin_END) {
                data.position(data.limit() + (int) offset);
            }
            return 0;
        }).FileSizeProc(pFile -> data.limit()).address();
    }).CloseProc((pFileIO, pFile) -> {
        AIFile aiFile = AIFile.create(pFile);
        aiFile.ReadProc().free();
        aiFile.SeekProc().free();
        aiFile.FileSizeProc().free();
    });

    public static Object[] loadObject(String path) {
        AIScene scene = aiImportFileEx(path,
                aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_FixInfacingNormals, fileIo);

        if (scene == null)
            System.err.println("Couldn't load model at: " + path);

        assert scene != null;

        AIMesh[] meshes = new AIMesh[scene.mNumMeshes()];
        Object[] internal = new Object[scene.mNumMeshes()];

        for (int i = 0; i < scene.mNumMeshes(); i++) {
            meshes[i] = AIMesh.create(scene.mMeshes().get(i));
            int vertexCount = meshes[i].mNumVertices();

            AIVector3D.Buffer vertices = meshes[i].mVertices();
            AIVector3D.Buffer normals = meshes[i].mNormals();

            Vertex[] vertexList = new Vertex[vertexCount];
            for (int j = 0; j < vertexCount; j++) {
                AIVector3D vertex = vertices.get(j);
                Vector3f meshVertex = new Vector3f(vertex.x(), vertex.y(), vertex.z());

                AIVector3D normal = normals.get(j);
                Vector3f meshNormal = new Vector3f(normal.x(), normal.y(), normal.z());

                Vector2f meshTextureCoord = new Vector2f();
                if (meshes[i].mNumUVComponents().get(0) != 0) {
                    AIVector3D texture = meshes[i].mTextureCoords(0).get(j);
                    meshTextureCoord.x = texture.x();
                    meshTextureCoord.y = texture.y();
                }

                vertexList[j] = new Vertex(meshVertex, new Vector3f(1, 1, 1), meshTextureCoord, meshNormal);
            }

            int faceCount = meshes[i].mNumFaces();
            AIFace.Buffer indices = meshes[i].mFaces();
            int[] indicesList = new int[faceCount * 3];
            for (int k = 0; k < faceCount; k++) {
                AIFace face = indices.get(k);
                indicesList[k * 3] = face.mIndices().get(0);
                indicesList[k * 3 + 1] = face.mIndices().get(1);
                indicesList[k * 3 + 2] = face.mIndices().get(2);
            }

            internal[i] = new Object(vertexList, indicesList);
        }
        return internal;
    }

    public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) throws IOException {
        ByteBuffer buffer;
        URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
        if (url == null)
            throw new IOException("Classpath resource not found: " + resource);
        File file = new File(url.getFile());
        if (file.isFile()) {
            FileInputStream fis = new FileInputStream(file);
            FileChannel fc = fis.getChannel();
            buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
            fc.close();
            fis.close();
        } else {
            buffer = BufferUtils.createByteBuffer(bufferSize);
            InputStream source = url.openStream();
            if (source == null)
                throw new FileNotFoundException(resource);
            try {
                byte[] buf = new byte[8192];
                while (true) {
                    int bytes = source.read(buf, 0, buf.length);
                    if (bytes == -1)
                        break;
                    if (buffer.remaining() < bytes)
                        buffer = resizeBuffer(buffer, Math.max(buffer.capacity() * 2, buffer.capacity() - buffer.remaining() + bytes));
                    buffer.put(buf, 0, bytes);
                }
                buffer.flip();
            } finally {
                source.close();
            }
        }
        return buffer;
    }

    private static ByteBuffer resizeBuffer(ByteBuffer buffer, int newCapacity) {
        ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCapacity);
        buffer.flip();
        newBuffer.put(buffer);
        return newBuffer;
    }

Stacktrace or crash log output

---------------  S U M M A R Y ------------

Command Line: -XX:+ShowCodeDetailsInExceptionMessages -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:35597 -javaagent:/home/julius/eclipse/java-2021-12/eclipse/configuration/org.eclipse.osgi/216/0/.cp/lib/javaagent-shaded.jar -Dfile.encoding=UTF-8 de.verschwiegener.zeustracking.Management

Host: AMD Ryzen 7 1700X Eight-Core Processor, 16 cores, 31G, Ubuntu 22.04.4 LTS
Time: Fri May 24 04:29:41 2024 CEST elapsed time: 2.074218 seconds (0d 0h 0m 2s)

---------------  T H R E A D  ---------------

Current thread (0x00007f5ca88e51e0):  JavaThread "GUI_RENDERER" [_thread_in_native, id=76861, stack(0x00007f5ce8078000,0x00007f5ce8178000)]

Stack: [0x00007f5ce8078000,0x00007f5ce8178000],  sp=0x00007f5ce8175120,  free space=1012k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  [libassimp.so+0x26dba0]

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  org.lwjgl.system.JNI.invokePPP(JIJJ)J+0
j  org.lwjgl.assimp.Assimp.naiImportFileEx(JIJ)J+26
j  org.lwjgl.assimp.Assimp.aiImportFileEx(Ljava/lang/CharSequence;ILorg/lwjgl/assimp/AIFileIO;)Lorg/lwjgl/assimp/AIScene;+30
j  com.spinyowl.legui.system.renderer.openGL.Model.loadObject(Ljava/lang/String;)[Lcom/spinyowl/legui/system/renderer/openGL/mesh/Object;+7
j  de.verschwiegener.zeustracking.MVREntity.initialize(Lcom/spinyowl/legui/system/renderer/openGL/engine/Engine;)V+2
j  com.spinyowl.legui.system.renderer.openGL.engine.Engine.update(Z)V+54
j  com.spinyowl.legui.system.renderer.nvg.component.NvgEngineRenderer.renderSelf(Lcom/spinyowl/legui/component/EngineComponent;Lcom/spinyowl/legui/system/context/Context;J)V+259
j  com.spinyowl.legui.system.renderer.nvg.component.NvgEngineRenderer.renderSelf(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;J)V+7
J 2097 c1 com.spinyowl.legui.system.renderer.nvg.component.NvgDefaultComponentRenderer.renderComponent(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;J)V (44 bytes) @ 0x00007f5d599d34a4 [0x00007f5d599d3080+0x0000000000000424]
J 2095 c1 com.spinyowl.legui.system.renderer.nvg.NvgComponentRenderer.renderComponent(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;)V (94 bytes) @ 0x00007f5d599d2594 [0x00007f5d599d1fe0+0x00000000000005b4]
J 2314 c1 com.spinyowl.legui.system.renderer.nvg.component.NvgDefaultComponentRenderer.renderChildComponents(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;J)V (56 bytes) @ 0x00007f5d59a2f7f4 [0x00007f5d59a2ee60+0x0000000000000994]
J 2097 c1 com.spinyowl.legui.system.renderer.nvg.component.NvgDefaultComponentRenderer.renderComponent(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;J)V (44 bytes) @ 0x00007f5d599d356c [0x00007f5d599d3080+0x00000000000004ec]
J 2095 c1 com.spinyowl.legui.system.renderer.nvg.NvgComponentRenderer.renderComponent(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;)V (94 bytes) @ 0x00007f5d599d2594 [0x00007f5d599d1fe0+0x00000000000005b4]
J 2094 c1 com.spinyowl.legui.system.renderer.ComponentRenderer.render(Lcom/spinyowl/legui/component/Component;Lcom/spinyowl/legui/system/context/Context;)V (7 bytes) @ 0x00007f5d599ca154 [0x00007f5d599ca040+0x0000000000000114]
j  com.spinyowl.legui.system.renderer.AbstractRenderer.render(Lcom/spinyowl/legui/component/Frame;Lcom/spinyowl/legui/system/context/Context;)V+44
j  com.spinyowl.legui.Window.render()V+254
j  com.spinyowl.legui.Window$$Lambda$90+0x00007f5cec0c5c70.run()V+4
j  java.lang.Thread.run()V+11 java.base@17.0.10
v  ~StubRoutines::call_stub

siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000000

Registers:
RAX=0x00007f5c4cd2d200, RBX=0x00007f5d77d6e600, RCX=0x0000000000000003, RDX=0x00007f5ca88bf1c8
RSP=0x00007f5ce8175118, RBP=0x00007f5c509c4630, RSI=0x00007f5ce81750f0, RDI=0x00007f5c509b5bd0
R8 =0x00007f5d31057e90, R9 =0x0000000000000004, R10=0x00007f5d48063248, R11=0x00007f5d48063210
R12=0x00007f5ce8175180, R13=0x00007f5c509b6140, R14=0x00000000000bef4e, R15=0x00007f5ce81755f0
RIP=0x0000000000000000, EFLAGS=0x0000000000010293, CSGSFS=0x002b000000000033, ERR=0x0000000000000014
  TRAPNO=0x000000000000000e

Register to memory mapping:

RAX=0x00007f5c4cd2d200: <offset 0x00000000008ae200> in /tmp/lwjgl_julius/3.3.3+5/x64/libassimp.so at 0x00007f5c4c47f000
RBX=0x00007f5d77d6e600: _ZNSs4_Rep20_S_empty_rep_storageE+0x0000000000000000 in /lib/x86_64-linux-gnu/libstdc++.so.6 at 0x00007f5d77b43000
RCX=0x0000000000000003 is an unknown value
RDX=0x00007f5ca88bf1c8 points into unknown readable memory: 0x0000000000000105 | 05 01 00 00 00 00 00 00
RSP=0x00007f5ce8175118 is pointing into the stack for thread: 0x00007f5ca88e51e0
RBP=0x00007f5c509c4630 points into unknown readable memory: 0x00007f5c4cd2db90 | 90 db d2 4c 5c 7f 00 00
RSI=0x00007f5ce81750f0 is pointing into the stack for thread: 0x00007f5ca88e51e0
RDI=0x00007f5c509b5bd0 points into unknown readable memory: 0x00007f5d74000d60 | 60 0d 00 74 5d 7f 00 00
R8 ={method} {0x00007f5d31057e90} 'callback' '(JJ)V' in 'org/lwjgl/assimp/AIFileTellProcI'
R9 =0x0000000000000004 is an unknown value
R10=0x00007f5d48063248: <offset 0x0000000000058248> in /tmp/lwjgl_julius/3.3.3+5/x64/liblwjgl.so at 0x00007f5d4800b000
R11=0x00007f5d48063210: <offset 0x0000000000058210> in /tmp/lwjgl_julius/3.3.3+5/x64/liblwjgl.so at 0x00007f5d4800b000
R12=0x00007f5ce8175180 is pointing into the stack for thread: 0x00007f5ca88e51e0
R13=0x00007f5c509b6140 points into unknown readable memory: 0x00007f5c00000000 | 00 00 00 00 5c 7f 00 00
R14=0x00000000000bef4e is an unknown value
R15=0x00007f5ce81755f0 is pointing into the stack for thread: 0x00007f5ca88e51e0
moomba42 commented 4 months ago

Similar issue here, but with loading an .obj file

TheIncgi commented 3 weeks ago

I just ran into this issue (or at least a very similar one) trying to load an .obj file and having the JVM crash when trying to do

memCopy(memAddress(data), pBuffer, max * size);

I managed to fix my issue with it crashing by making my ByteBuffer with MemoryUtil.memAlloc (instead of ByteBuffer.wrap which I was using before) Here's what my equivilant of public static ByteBuffer ioResourceToByteBuffer(String resource, int bufferSize) looks like

/** Allocates a new byte buffer which must be freed with MemoryUtil.memFree */
public static ByteBuffer resourceToByteBuffer(String fileName) throws FileNotFoundException, IOException {
    try(InputStream in = Main.class.getResourceAsStream(fileName)) {        
        var buf = MemoryUtil.memAlloc( in.available() );
        while(in.available() > 0)
            buf.put(in.readNBytes(8192));
        return buf;
    }
}

If you do this then you also have to call MemoryUtil.memFree, so I changed my AIFileIO to track open files by address with a HashMap so I can make sure it frees the memory So now it looks like this:

protected static AIFileIO makeFileIO() {
        // keep track of file addresses & buffers
    HashMap<Long, ByteBuffer> opened = new HashMap<>();

    return AIFileIO.create()
            .OpenProc((pFileIO, fileName, openMode) -> {
                ByteBuffer data;
                String fileNameUtf8 = memUTF8(fileName);

                try {
                    data = FileUtils.resourceToByteBuffer(fileNameUtf8);
                    data.position(0); //File read error when reading the next file if you don't set this to 0
                } catch (IOException e) {
                    throw new RuntimeException("Could not open file: " + fileNameUtf8);
                }

                var address = AIFile.create()
                    .ReadProc((pFile, pBuffer, size, count) -> {
                        long max = Math.min(data.remaining() / size, count);

                        memCopy(memAddress(data), pBuffer, max * size);
                        data.position(data.position() + (int) (max * size));
                        return max;
                    })
                    .SeekProc((pFile, offset, origin) -> {
                        if (origin == Assimp.aiOrigin_CUR) {
                            data.position(data.position() + (int) offset);
                        } else if (origin == Assimp.aiOrigin_SET) {
                            data.position((int) offset);
                        } else if (origin == Assimp.aiOrigin_END) {
                            data.position(data.limit() + (int) offset);
                        }
                        return 0;
                    })
                    .FileSizeProc(pFile -> data.limit())
                    .address();
                opened.put(address, data);
                return address;
            })
            .CloseProc((pFileIO, pFile) -> {
                AIFile aiFile = AIFile.create(pFile);
                // find and free buffer
                var buf = opened.get(pFile);
                if(buf != null)
                    MemoryUtil.memFree(buf);
                else
                    System.err.println("Buffer not closed!");

                aiFile.ReadProc().free();
                aiFile.SeekProc().free();
                aiFile.FileSizeProc().free();
            });
}

If there's a better way to handle the buffers/AIFileIO I'm curious to know, but I'm happy I got it working at least.