package ru.dbotthepony.kstarbound.client.gl import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.api.IStruct4f import ru.dbotthepony.kstarbound.client.freetype.FreeType import ru.dbotthepony.kstarbound.math.Matrix4f import ru.dbotthepony.kstarbound.math.Matrix4fStack import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.TileRenderers import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.util.Color import java.io.File import java.io.FileNotFoundException import java.lang.ref.Cleaner import java.util.concurrent.ThreadFactory import kotlin.reflect.KProperty private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) { operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Boolean { return value } operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Boolean) { glStateTracker.ensureSameThread() if (value == this.value) return if (value) { glEnable(enum) } else { glDisable(enum) } checkForGLError() this.value = value } } private class GLStateGenericTracker(private var value: T, private val lambda: (T) -> Unit) { operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): T { return value } operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: T) { glStateTracker.ensureSameThread() if (value == this.value) return lambda.invoke(value) checkForGLError() this.value = value } } open class GLTransformableProgram(state: GLStateTracker, vararg shaders: GLShader) : GLShaderProgram(state, *shaders) { init { link() } val transform = this["_transform"]!! init { transform.set(Matrix4f.IDENTITY) } } open class GLTransformableColorableProgram(state: GLStateTracker, vararg shaders: GLShader) : GLTransformableProgram(state, *shaders) { val color = this["_color"]!! init { color.set(Color.WHITE) } } interface GLCleanable : Cleaner.Cleanable { /** * Выставляет флаг на то, что объект был удалён вручную и вызывает clean() */ fun cleanManual(): Unit } interface GLStreamBuilderList { val small: StreamVertexBuilder val statefulSmall: StatefulStreamVertexBuilder } class GLStateTracker { init { // This line is critical for LWJGL's interoperation with GLFW's // OpenGL context, or any context that is managed externally. // LWJGL detects the context that is current in the current thread, // creates the GLCapabilities instance and makes the OpenGL // bindings available for use. GL.createCapabilities() } private var cleanerHits = ArrayList<() -> Unit>() private val cleaner = Cleaner.create(object : ThreadFactory { override fun newThread(r: Runnable): Thread { val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this)) thread.priority = 2 return thread } }) fun registerCleanable(ref: Any, fn: (Int) -> Unit, name: String, nativeRef: Int): GLCleanable { var cleanManual = false val cleanable = cleaner.register(ref) { if (!cleanManual) LOGGER.error("{} with ID {} got leaked.", name, nativeRef) cleanerHits.add { fn(nativeRef) checkForGLError() } } return object : GLCleanable { override fun cleanManual() { cleanManual = true clean() } override fun clean() = cleanable.clean() } } fun cleanup() { val copy = cleanerHits cleanerHits = ArrayList() for (lambda in copy) { lambda.invoke() } } var blend by GLStateSwitchTracker(GL_BLEND) var depthTest by GLStateSwitchTracker(GL_DEPTH_TEST) var VBO: GLVertexBufferObject? = null set(value) { ensureSameThread() if (field === value) return field = value if (value == null) { glBindBuffer(GL_ARRAY_BUFFER, 0) checkForGLError() return } if (!value.isArray) throw IllegalArgumentException("Provided buffer object is not of Array type") glBindBuffer(GL_ARRAY_BUFFER, value.pointer) checkForGLError() } var EBO: GLVertexBufferObject? = null set(value) { ensureSameThread() if (field === value) return field = value if (value == null) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) checkForGLError() return } if (!value.isElementArray) throw IllegalArgumentException("Provided buffer object is not of Array type") glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, value.pointer) checkForGLError() } var VAO: GLVertexArrayObject? = null set(value) { ensureSameThread() if (field === value) return field = value if (value == null) { glBindVertexArray(0) checkForGLError() return } glBindVertexArray(value.pointer) checkForGLError() } var program: GLShaderProgram? = null private set var texture2D: GLTexture2D? = null set(value) { ensureSameThread() if (field === value) return field = value if (value == null) return glBindTexture(GL_TEXTURE_2D, value.pointer) checkForGLError() } var activeTexture = 0 set(value) { ensureSameThread() if (field == value) return require(value >= 0) { "Invalid texture block $value" } require(value < 80) { "Too big texture block index $value, OpenGL 4.6 guarantee only 80!" } field = value glActiveTexture(GL_TEXTURE0 + value) checkForGLError() } var clearColor by GLStateGenericTracker(Color.WHITE) { val (r, g, b, a) = it glClearColor(r, g, b, a) } init { glActiveTexture(GL_TEXTURE0) checkForGLError() } val thread = Thread.currentThread() val tileRenderers = TileRenderers(this) fun ensureSameThread() { if (thread !== Thread.currentThread()) { throw IllegalAccessException("Trying to access $this outside of $thread!") } } fun isSameThread() = thread === Thread.currentThread() fun program(vararg shaders: GLShader): GLShaderProgram { return GLShaderProgram(this, *shaders) } fun newVBO(type: VBOType = VBOType.ARRAY): GLVertexBufferObject { return GLVertexBufferObject(this, type) } fun newEBO() = newVBO(VBOType.ELEMENT_ARRAY) fun newVAO() = GLVertexArrayObject(this) fun newTexture(name: String = "") = GLTexture2D(this, name) private val named2DTextures = HashMap() fun loadNamedTexture(path: String, memoryFormat: Int = GL_RGBA, fileFormat: Int = GL_RGBA): GLTexture2D { return named2DTextures.computeIfAbsent(path) { if (!Starbound.pathExists(path)) { throw FileNotFoundException("Unable to locate $path") } return@computeIfAbsent newTexture(path).upload(Starbound.readDirect(path), memoryFormat, fileFormat).generateMips() } } private var loadedEmptyTexture = false private val missingTexturePath = "/assetmissing.png" fun loadNamedTextureSafe(path: String, memoryFormat: Int = GL_RGBA, fileFormat: Int = GL_RGBA): GLTexture2D { if (!loadedEmptyTexture) { loadedEmptyTexture = true named2DTextures[missingTexturePath] = newTexture(missingTexturePath).upload(Starbound.readDirect(missingTexturePath), memoryFormat, fileFormat).generateMips() } return named2DTextures.computeIfAbsent(path) { if (!Starbound.pathExists(path)) { LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath) return@computeIfAbsent named2DTextures[missingTexturePath]!! } return@computeIfAbsent newTexture(path).upload(Starbound.readDirect(path), memoryFormat, fileFormat).generateMips() } } fun bind(obj: GLVertexBufferObject): GLVertexBufferObject { if (obj.type == VBOType.ARRAY) VBO = obj else EBO = obj return obj } fun unbind(obj: GLVertexBufferObject): GLVertexBufferObject { if (obj.type == VBOType.ARRAY) if (obj == VBO) VBO = null else if (obj == EBO) EBO = null return obj } fun bind(obj: GLVertexArrayObject): GLVertexArrayObject { VAO = obj return obj } fun unbind(obj: GLVertexArrayObject): GLVertexArrayObject { if (obj == VAO) VAO = null return obj } fun use(obj: GLShaderProgram): GLShaderProgram { ensureSameThread() if (obj == program) { return obj } check(obj.linked) { "Program is not linked!" } program = obj glUseProgram(obj.pointer) checkForGLError() return obj } val shaderVertexTexture: GLTransformableProgram val shaderVertexTextureColor: GLTransformableColorableProgram init { val textureF = GLShader.internalFragment("shaders/fragment/texture.glsl") val textureColorF = GLShader.internalFragment("shaders/fragment/texture_color.glsl") val textureV = GLShader.internalVertex("shaders/vertex/texture.glsl") shaderVertexTexture = GLTransformableProgram(this, textureF, textureV) shaderVertexTextureColor = GLTransformableColorableProgram(this, textureColorF, textureV) textureF.unlink() textureColorF.unlink() textureV.unlink() } val fontProgram: GLTransformableColorableProgram init { val vertex = GLShader.internalVertex("shaders/vertex/font.glsl") val fragment = GLShader.internalFragment("shaders/fragment/font.glsl") fontProgram = GLTransformableColorableProgram(this, vertex, fragment) vertex.unlink() fragment.unlink() } val flatProgram: GLTransformableColorableProgram init { val vertex = GLShader.internalVertex("shaders/vertex/flat_vertex_2d.glsl") val fragment = GLShader.internalFragment("shaders/fragment/flat_color.glsl") flatProgram = GLTransformableColorableProgram(this, vertex, fragment) vertex.unlink() fragment.unlink() } val flat2DQuads = object : GLStreamBuilderList { override val small by lazy { return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS, 1024) } override val statefulSmall by lazy { return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small) } } val flat2DTexturedQuads = object : GLStreamBuilderList { override val small by lazy { return@lazy StreamVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS, 1024) } override val statefulSmall by lazy { return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small) } } val flat2DQuadLines = object : GLStreamBuilderList { override val small by lazy { return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS_AS_LINES, 1024) } override val statefulSmall by lazy { return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small) } } val flat2DQuadWireframe = object : GLStreamBuilderList { override val small by lazy { return@lazy StreamVertexBuilder(GLFlatAttributeList.VEC2F, VertexType.QUADS_AS_LINES_WIREFRAME, 1024) } override val statefulSmall by lazy { return@lazy StatefulStreamVertexBuilder(this@GLStateTracker, small) } } val matrixStack = Matrix4fStack() val freeType = FreeType() val font = Font(this) inline fun quadWireframe(color: Color = Color.WHITE, lambda: (StreamVertexBuilder) -> Unit) { val stateful = flat2DQuadWireframe.statefulSmall val builder = stateful.builder builder.begin() lambda.invoke(builder) stateful.upload() flatProgram.use() flatProgram.color.set(color) flatProgram.transform.set(matrixStack.last) stateful.draw(GL_LINES) } inline fun quadWireframe(value: AABB, color: Color = Color.WHITE, chain: (StreamVertexBuilder) -> Unit = {}) { quadWireframe(color) { it.quad(value.mins.x.toFloat(), value.mins.y.toFloat(), value.maxs.x.toFloat(), value.maxs.y.toFloat()) chain(it) } } companion object { private val LOGGER = LogManager.getLogger(GLStateTracker::class.java) } }