diff --git a/src/main/kotlin/org/ja4/vaxel/Main.kt b/src/main/kotlin/org/ja4/vaxel/Main.kt index 8d082ed..27fc0e6 100644 --- a/src/main/kotlin/org/ja4/vaxel/Main.kt +++ b/src/main/kotlin/org/ja4/vaxel/Main.kt @@ -7,7 +7,7 @@ import org.lwjgl.system.MemoryUtil.* import kotlin.system.exitProcess fun main() { - if (!glfwInit()) { + /*if (!glfwInit()) { exitProcess(1) } @@ -37,9 +37,12 @@ fun main() { glDrawArrays(GL_TRIANGLES, 0, 3) glfwSwapBuffers(window) } - glfwTerminate() -} + glfwTerminate()*/ + Render.run() + +} +/* private fun createWindow(): Long { glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE) val window = glfwCreateWindow(800, 600, "Window Title", NULL, NULL) @@ -47,3 +50,4 @@ private fun createWindow(): Long { createCapabilities() return window } +*/ \ No newline at end of file diff --git a/src/main/kotlin/org/ja4/vaxel/Render.kt b/src/main/kotlin/org/ja4/vaxel/Render.kt new file mode 100644 index 0000000..0293379 --- /dev/null +++ b/src/main/kotlin/org/ja4/vaxel/Render.kt @@ -0,0 +1,242 @@ +package org.ja4.vaxel + +import org.lwjgl.BufferUtils +import org.lwjgl.PointerBuffer +import org.lwjgl.glfw.GLFW.* +import org.lwjgl.glfw.GLFWKeyCallback +import org.lwjgl.opengl.GL +import org.lwjgl.opengl.GL20.* +import org.lwjgl.opengl.GL30.glBindVertexArray +import org.lwjgl.opengl.GL30.glGenVertexArrays +import org.lwjgl.opengl.GLUtil +import org.lwjgl.stb.STBImage.stbi_image_free +import org.lwjgl.stb.STBImage.stbi_load_from_memory +import org.lwjgl.system.Callback +import org.lwjgl.system.MemoryStack +import org.lwjgl.system.MemoryUtil.* +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.IntBuffer +import java.nio.channels.FileChannel + +object Render { + + var width = 1024 + var height = 768 + var window: Long? = null + var program: Int? = null + var quadProgramInputPosition: Int? = null + var quadProgramInputTextureCoords: Int? = null + var vao: Int? = null + var debugProc: Callback? = null + var keyCallback: GLFWKeyCallback? = null + + fun init() { + glfwInit() + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0) + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE) + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE) + window = glfwCreateWindow(width, height, "Simple quad", NULL, NULL) + keyCallback = object : GLFWKeyCallback() { + override fun invoke(window: Long, key: Int, scancode: Int, action: Int, mods: Int) { + if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) + glfwSetWindowShouldClose(window, true) + } + } + glfwSetKeyCallback(window!!, keyCallback) + glfwMakeContextCurrent(window!!) + glfwShowWindow(window!!) + MemoryStack.stackPush().use {frame -> + val framebufferSize = frame.mallocInt(2) + nglfwGetFramebufferSize(window!!, memAddress(framebufferSize), memAddress(framebufferSize) + 4) + width = framebufferSize.get(0) + height = framebufferSize.get(1) + } + val caps = GL.createCapabilities() + val debugProc = GLUtil.setupDebugMessageCallback() + createTexture() + createQuadProgram() + createFullScreenQuad() + } + + fun resizeBuffer(buffer: ByteBuffer, newCapacity: Int): ByteBuffer { + val newBuffer = BufferUtils.createByteBuffer(newCapacity) + buffer.flip() + newBuffer.put(buffer) + return newBuffer + } + + fun ioResourceToByteBuffer(resource: String, bufferSize: Int): ByteBuffer { + var buffer: ByteBuffer + val url = Thread.currentThread().contextClassLoader.getResource(resource) + ?: throw IOException("Classpath resource not found: $resource") + val file = File(url.file) + if (file.isFile) { + val fis = FileInputStream(file) + val fc = fis.channel + buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) + fc.close() + fis.close() + } else { + buffer = BufferUtils.createByteBuffer(bufferSize) + val source = url.openStream() ?: throw FileNotFoundException(resource) + source.use { source -> + val buf = ByteArray(8192) + while (true) { + val bytes = source.read(buf, 0, buf.size) + 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() + } + } + return buffer + } + + fun createShader(resource: String, type: Int): Int { + return createShader(resource, type, null) + } + + fun createShader(resource: String, type: Int, version: String?): Int { + val shader = glCreateShader(type) + val source = ioResourceToByteBuffer(resource, 8192) + val strings: PointerBuffer + val lengths: IntBuffer + if (version == null) { + strings = BufferUtils.createPointerBuffer(1) + lengths = BufferUtils.createIntBuffer(1) + + strings.put(0, source) + lengths.put(0, source.remaining()) + } else { + strings = BufferUtils.createPointerBuffer(2) + lengths = BufferUtils.createIntBuffer(2) + + val preamble = memUTF8("#version $version\n", false) + + strings.put(0, preamble) + lengths.put(0, preamble.remaining()) + + strings.put(1, source) + lengths.put(1, source.remaining()) + } + glShaderSource(shader, strings, lengths) + glCompileShader(shader) + val compiled = glGetShaderi(shader, GL_COMPILE_STATUS) + val shaderLog = glGetShaderInfoLog(shader) + if (shaderLog.trim().isNotEmpty()) { + System.err.println(shaderLog) + } + if (compiled == 0) { + throw AssertionError("Could not compile shader") + } + return shader + } + + fun createQuadProgram() { + program = glCreateProgram() + val vshader = createShader("texturedQuad.vs", GL_VERTEX_SHADER) + val fshader = createShader("texturedQuad.fs", GL_FRAGMENT_SHADER) + glAttachShader(program!!, vshader) + glAttachShader(program!!, fshader) + glLinkProgram(program!!) + val linked = glGetProgrami(program!!, GL_LINK_STATUS) + val programLog = glGetProgramInfoLog(program!!) + if (programLog.trim().isNotEmpty()) { + System.err.println(programLog) + } + if (linked == 0) { + throw AssertionError("Could not link program") + } + glUseProgram(program!!) + val texLocation = glGetUniformLocation(program!!, "tex") + glUniform1i(texLocation, 0) + quadProgramInputPosition = glGetAttribLocation(program!!, "position") + quadProgramInputTextureCoords = glGetAttribLocation(program!!, "texCoords") + glUseProgram(0) + } + + fun createFullScreenQuad() { + vao = glGenVertexArrays() + glBindVertexArray(vao!!) + val positionVbo = glGenBuffers() + var fb = BufferUtils.createFloatBuffer(2 * 6) + fb.put(-1.0f).put(-1.0f) + fb.put(1.0f).put(-1.0f) + fb.put(1.0f).put(1.0f) + fb.put(1.0f).put(1.0f) + fb.put(-1.0f).put(1.0f) + fb.put(-1.0f).put(-1.0f) + fb.flip() + glBindBuffer(GL_ARRAY_BUFFER, positionVbo) + glBufferData(GL_ARRAY_BUFFER, fb, GL_STATIC_DRAW) + glVertexAttribPointer(quadProgramInputPosition!!, 2, GL_FLOAT, false, 0, 0L) + glEnableVertexAttribArray(quadProgramInputPosition!!) + val texCoordsVbo = glGenBuffers() + fb = BufferUtils.createFloatBuffer(2 * 6) + fb.put(0.0f).put(1.0f) + fb.put(1.0f).put(1.0f) + fb.put(1.0f).put(0.0f) + fb.put(1.0f).put(0.0f) + fb.put(0.0f).put(0.0f) + fb.put(0.0f).put(1.0f) + fb.flip() + glBindBuffer(GL_ARRAY_BUFFER, texCoordsVbo) + glBufferData(GL_ARRAY_BUFFER, fb, GL_STATIC_DRAW) + glVertexAttribPointer(quadProgramInputTextureCoords!!, 2, GL_FLOAT, true, 0, 0L) + glEnableVertexAttribArray(quadProgramInputTextureCoords!!) + glBindBuffer(GL_ARRAY_BUFFER, 0) + glBindVertexArray(0) + } + + fun createTexture() { + val width = BufferUtils.createIntBuffer(1) + val height = BufferUtils.createIntBuffer(1) + val components = BufferUtils.createIntBuffer(1) + val data = stbi_load_from_memory(ioResourceToByteBuffer("environment.jpg", 1024), width, height, components, 4)?: throw AssertionError("Unable to load file") + val id = glGenTextures() + glBindTexture(GL_TEXTURE_2D, id) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width.get(), height.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data) + stbi_image_free(data) + } + + fun render() { + glClear(GL_COLOR_BUFFER_BIT) + glUseProgram(program!!) + glBindVertexArray(vao!!) + glDrawArrays(GL_TRIANGLES, 0, 6) + glBindVertexArray(0) + glUseProgram(0) + } + + fun loop() { + while (!glfwWindowShouldClose(window!!)) { + glfwPollEvents() + glViewport(0, 0, width, height) + render() + glfwSwapBuffers(window!!) + } + } + + fun run() { + init() + loop() + if (debugProc != null) { + debugProc!!.free() + } + keyCallback!!.free() + glfwDestroyWindow(window!!) + glfwTerminate() + } +} \ No newline at end of file diff --git a/src/main/resources/environment.jpg b/src/main/resources/environment.jpg new file mode 100644 index 0000000..548c041 Binary files /dev/null and b/src/main/resources/environment.jpg differ diff --git a/src/main/resources/texturedQuad.fs b/src/main/resources/texturedQuad.fs new file mode 100644 index 0000000..1166d21 --- /dev/null +++ b/src/main/resources/texturedQuad.fs @@ -0,0 +1,10 @@ +#version 130 + +uniform sampler2D tex; + +in vec2 texCoordsVarying; +out vec4 color; + +void main() { + color = texture(tex, texCoordsVarying); +} \ No newline at end of file diff --git a/src/main/resources/texturedQuad.vs b/src/main/resources/texturedQuad.vs new file mode 100644 index 0000000..d69636c --- /dev/null +++ b/src/main/resources/texturedQuad.vs @@ -0,0 +1,10 @@ +#version 130 + +in vec3 position; +in vec2 texCoords; +out vec2 texCoordsVarying; + +void main() { + texCoordsVarying = texCoords; + gl_Position = vec4(position, 1.0); +} \ No newline at end of file