242 lines
8.6 KiB
Kotlin
242 lines
8.6 KiB
Kotlin
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()
|
|
}
|
|
} |