diff --git a/jars/joml-1.8.2.jar b/jars/joml-1.8.2.jar new file mode 100644 index 00000000..cdacef21 Binary files /dev/null and b/jars/joml-1.8.2.jar differ diff --git a/pom.xml b/pom.xml index 9ec1006b..ba1d9ad1 100644 --- a/pom.xml +++ b/pom.xml @@ -203,6 +203,11 @@ json 20201115 + + org.joml + joml + 1.8.2 + diff --git a/src/main/java/org/qme/client/vis/gl/Mesh.java b/src/main/java/org/qme/client/vis/gl/Mesh.java new file mode 100644 index 00000000..81540f6d --- /dev/null +++ b/src/main/java/org/qme/client/vis/gl/Mesh.java @@ -0,0 +1,128 @@ +package org.qme.client.vis.gl; + +import org.lwjgl.system.MemoryUtil; +import org.qme.client.vis.tex.TextureManager; +import org.qme.io.Logger; +import org.qme.io.Severity; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import static org.lwjgl.opengl.GL33.*; + +/** + * A mesh with texture coordinates, position coordinates, etc. This contains the + * data OpenGL actually uses to render stuff. + */ +public final class Mesh { + + /** + * A bunch of IDs! vaoID is the ID of all of the data's "linking point", so + * to speak. All of the xxxVboID are the IDs are various sets of data GL + * uses. + */ + private final int vaoId, + posVboId, idxVboId, texVboId, + vertexCount + ; + + /** + * Set up all of the arrays into buffers and such. + * @param positions the locations that things get drawn at + * @param indices the indices of the position buffer to draw, so we don't + * repeat data. + * @param texPositions the texture coordinates to use. + */ + public Mesh(float[] positions, int[] indices, float[] texPositions) { + + // The buffers we'll place the data in + FloatBuffer verticesBuffer, texBuffer; + IntBuffer indexBuffer; + + vertexCount = indices.length; + + vaoId = glGenVertexArrays(); + glBindVertexArray(vaoId); + + // Vertices + posVboId = glGenBuffers(); + verticesBuffer = MemoryUtil.memAllocFloat(positions.length); + verticesBuffer.put(positions).flip(); + glBindBuffer(GL_ARRAY_BUFFER, posVboId); + glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + MemoryUtil.memFree(verticesBuffer); + + // Indices + idxVboId = glGenBuffers(); + indexBuffer = MemoryUtil.memAllocInt(indices.length); + indexBuffer.put(indices).flip(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxVboId); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL_STATIC_DRAW); + MemoryUtil.memFree(indexBuffer); + + // Texture coordinates + texVboId = glGenBuffers(); + texBuffer = MemoryUtil.memAllocFloat(texPositions.length); + texBuffer.put(texPositions).flip(); + glBindBuffer(GL_ARRAY_BUFFER, texVboId); + glBufferData(GL_ARRAY_BUFFER, texBuffer, GL_STATIC_DRAW); + glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0); + + // We're not placing data into the vao anymore + glBindVertexArray(0); + + } + + /** + * Draw this mesh using the texture given. + * TODO: optimize this so we don't load and unload the same texture 3721897482394 times. + * @param textureName which texture + */ + public void render(String textureName) { + + // Activate first texture unit + glActiveTexture(GL_TEXTURE0); + // Bind the texture (this line is expensive to run) + Integer texID = TextureManager.getTexture(textureName); + if (texID == null) { + Logger.log("Attempted to load nonexistent texture! Texture: " + textureName, Severity.FATAL); + } + glBindTexture(GL_TEXTURE_2D, texID); + + // Draw the mesh + glBindVertexArray(vaoId); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glDrawElements(GL_TRIANGLES, vertexCount, GL_UNSIGNED_INT, 0); + + // Clear everything for next mesh + glDisableVertexAttribArray(0); + glBindVertexArray(0); + + } + + /** + * Clear all of the mesh data. Because OpenGL deals with this, Java does not + * garbage collect so please call this manually. + */ + public void delete() { + + glDisableVertexAttribArray(0); + + // Clear the VBO + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Flush all data + glDeleteBuffers(posVboId); + glDeleteBuffers(idxVboId); + glDeleteBuffers(texVboId); + + // Clear the VAO + glBindVertexArray(0); + glDeleteVertexArrays(vaoId); + + } + +} diff --git a/src/main/java/org/qme/client/vis/gl/Shader.java b/src/main/java/org/qme/client/vis/gl/Shader.java new file mode 100644 index 00000000..472967cf --- /dev/null +++ b/src/main/java/org/qme/client/vis/gl/Shader.java @@ -0,0 +1,188 @@ +package org.qme.client.vis.gl; + +import org.joml.Matrix4f; +import org.lwjgl.system.MemoryStack; + +import java.nio.FloatBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import static org.lwjgl.opengl.GL33.*; + +/** + * Yeah, Cameron, I know you're going to be reading this frantically trying to + * figure out how shaders work. Or someone else, maybe. So I'm writing it here + * the way I understand. + * + * OpenGL can compile GLSL code into GPU-assembly language. You can then bind + * the shader so sets of vertices are rendered with it. OpenGL renders shaders + * by compiling a String of source code, which we generate on-the-fly from a + * file. The vertex shader (_vert.glsl) is first, which produces the position of + * the vertex being processed. I *think* the fragment shader makes the output + * color? Or something? Idk. The "real" rendering work is done by meshes. + * + * @author adamhutchings + * @since 0.4 + */ +public class Shader { + + private final int programId, vertId, fragId; + + private final Map uniforms = new HashMap<>(); + + /** + * Load a string from a file. + * @param fileName the path to the file + */ + public static String loadFileAsString(String fileName) throws Exception { + return Files.readString(Path.of(fileName)); + } + + /** + * Load a shader from files. + * @param fileBase load vertex code from src/shader/fileBase_vert.glsl + * and src/shader/fileBase_frag.glsl + */ + public Shader(String fileBase) throws Exception { + + programId = glCreateProgram(); + if (programId == 0) { + throw new Exception("Unable to create parent shader"); + } + + vertId = createShader( + loadFileAsString("src/shader/" + fileBase + "_vert.glsl"), GL_VERTEX_SHADER + ); + fragId = createShader( + loadFileAsString("src/shader/" + fileBase + "_frag.glsl"), GL_FRAGMENT_SHADER + ); + + link(); + + } + + /** + * Link everything together. + */ + private void link() throws Exception { + + glLinkProgram(programId); + if (glGetProgrami(programId, GL_LINK_STATUS) == 0) { + throw new Exception("Error linking shader code: " + glGetProgramInfoLog(programId, 1024)); + } + + // These pieces of shaders aren't "needed" anymore, so we can detach + // them from the final linked program. + if (vertId != 0) { + glDetachShader(programId, vertId); + } + if (fragId != 0) { + glDetachShader(programId, fragId); + } + + glValidateProgram(programId); + if (glGetProgrami(programId, GL_VALIDATE_STATUS) == 0) { + // THESE WARNINGS ARE NOT NECESSARILY A SIGN OF A FAILING COMPILATION. + // Just so y'all know :D + System.err.println("Warning validating Shader code: " + glGetProgramInfoLog(programId, 1024)); + } + + } + + /** + * Create an individual shader of a given type. + */ + private int createShader(String code, int type) throws Exception { + + int shaderId = glCreateShader(type); + + if (shaderId == 0) { + throw new Exception("Could not create shader of type " + type); + } + + glShaderSource(shaderId, code); + glCompileShader(shaderId); + + if (glGetShaderi(shaderId, GL_COMPILE_STATUS) == 0) { + throw new Exception("Error compiling shader code: " + glGetShaderInfoLog(shaderId, 1024)); + } + + glAttachShader(programId, shaderId); + + return shaderId; + + } + + /** + * Set this as the shader to be used. + */ + public void bind() { + glUseProgram(programId); + } + + /** + * Stop using this shader. + */ + public void unbind() { + glUseProgram(0); + } + + /** + * Call this to delete the shader. + */ + public void cleanup() { + unbind(); + // This check should technically be unnecessary. + if (programId != 0) { + glDeleteProgram(programId); + } + } + + /** + * For us to pass data into the shader from the program, we need to create a + * "uniform", which like all other OpenGL code is set using integers. Here + * we interface with it using strings so that we can access it more intuitively + * and easily. + * @param uniformName the name of the new uniform to create + * @throws Exception if the uniform is not defined *in the shader program*. + */ + public void createUniform(String uniformName) throws Exception { + int uniformLocation = glGetUniformLocation(programId, + uniformName); + if (uniformLocation < 0) { + throw new Exception("Could not find uniform:" + + uniformName); + } + uniforms.put(uniformName, uniformLocation); + } + + /** + * Set a uniform to an int value + * @param uniformName which uniform + * @param value the value to set it to + */ + public void setUniform(String uniformName, int value) { + glUniform1i(uniforms.get(uniformName), value); + } + + /** + * Set a uniform to an float value + * @param uniformName which uniform + * @param value the value to set it to + */ + public void setUniform(String uniformName, float value) { + glUniform1f(uniforms.get(uniformName), value); + } + + /** + * Set a uniform to an float array value + * @param uniformName which uniform + * @param value the value to set it to + */ + public void setUniform(String uniformName, float[] value) { + glUniform2fv(uniforms.get(uniformName), value); + } + +}